", "\[row\]")
- text = replacetext(text, "", "\[cell\]")
- text = replacetext(text, " ", "\[logo\]")
- text = replacetext(text, " ", "\[syndielogo\]")
- return text
-
-/datum/html_split_holder
- var/list/opening
- var/inner_text
- var/list/closing
-
-/datum/html_split_holder/New()
- opening = list()
- inner_text = ""
- closing = list()
-
-/proc/split_html(raw_text="")
- // gently borrowed and re-purposed from code/modules/pda/utilities.dm
- // define a datum to hold our result
- var/datum/html_split_holder/s = new()
-
- // copy the raw_text to get started
- var/text = copytext_char(raw_text, 1)
-
- // search for tag brackets
- var/tag_start = findtext_char(text, "<")
- var/tag_stop = findtext_char(text, ">")
-
- // until we run out of opening tags
- while((tag_start != 0) && (tag_stop != 0))
- // if the tag isn't at the beginning of the string
- if(tag_start > 1)
- // we've found our text, so copy it out
- s.inner_text = copytext_char(text, 1, tag_start)
- // and chop the text for the next round
- text = copytext_char(text, tag_start)
- break
- // otherwise, we found an opening tag, so add it to the list
- var/tag = copytext_char(text, tag_start, tag_stop+1)
- s.opening.Add(tag)
- // and chop the text for the next round
- text = copytext_char(text, tag_stop+1)
- // look for the next tag in what's left
- tag_start = findtext(text, "<")
- tag_stop = findtext(text, ">")
-
- // search for tag brackets
- tag_start = findtext(text, "<")
- tag_stop = findtext(text, ">")
-
- // until we run out of closing tags
- while((tag_start != 0) && (tag_stop != 0))
- // we found a closing tag, so add it to the list
- var/tag = copytext_char(text, tag_start, tag_stop+1)
- s.closing.Add(tag)
- // and chop the text for the next round
- text = copytext_char(text, tag_stop+1)
- // look for the next tag in what's left
- tag_start = findtext(text, "<")
- tag_stop = findtext(text, ">")
-
- // return the split html object to the caller
- return s
-
-//Used for applying byonds text macros to strings that are loaded at runtime
-/proc/apply_text_macros(string)
- var/next_backslash = findtext(string, "\\")
- if(!next_backslash)
- return string
-
- var/leng = length(string)
-
- var/next_space = findtext(string, " ", next_backslash + length(string[next_backslash]))
- if(!next_space)
- next_space = leng - next_backslash
-
- if(!next_space) //trailing bs
- return string
-
- var/base = next_backslash == 1 ? "" : copytext(string, 1, next_backslash)
- var/macro = lowertext(copytext(string, next_backslash + length(string[next_backslash]), next_space))
- var/rest = next_backslash > leng ? "" : copytext(string, next_space + length(string[next_space]))
-
- //See https://secure.byond.com/docs/ref/info.html#/DM/text/macros
- switch(macro)
- //prefixes/agnostic
- if("the")
- rest = "\the [rest]"
- if("a")
- rest = "\a [rest]"
- if("an")
- rest = "\an [rest]"
- if("proper")
- rest = "\proper [rest]"
- if("improper")
- rest = "\improper [rest]"
- if("roman")
- rest = "\roman [rest]"
- //postfixes
- if("th")
- base = "[rest]\th"
- if("s")
- base = "[rest]\s"
- if("he")
- base = "[rest]\he"
- if("she")
- base = "[rest]\she"
- if("his")
- base = "[rest]\his"
- if("himself")
- base = "[rest]\himself"
- if("herself")
- base = "[rest]\herself"
- if("hers")
- base = "[rest]\hers"
-
- . = base
- if(rest)
- . += .(rest)
-
-/**
- * Proc to generate a "rank colour" from a client
- *
- * This takes the client and looks at various factors in order, such as patreon status, staff rank, and more
- * Arguments:
- * * C - The client were looking up
- */
-/proc/client2rankcolour(client/C)
- // First check if end user is an admin
- if(C.holder)
- if(C.holder.rank in GLOB.configuration.admin.rank_colour_map)
- // Return their rank colour if they are in here
- return GLOB.configuration.admin.rank_colour_map[C.holder.rank]
-
- // If they arent an admin, see if they are a patreon. Just accept any level
- if(C.donator_level)
- return "#e67e22" // Patreon orange
- return null
-
-
-/proc/starts_with_vowel(text)
- var/start_char = copytext(text, 1, 2)
- switch(lowertext(start_char))
- if("a", "e", "i", "o", "u")
- return TRUE
- else
- return FALSE
-
-/**
- * Formats num with an SI prefix.
- *
- * Returns a string formatted with a multiple of num and an SI prefix corresponding to an exponent of 10.
- * Only considers exponents that are multiples of 3 (deca, deci, hecto, and centi are not included).
- * A unit is not included in the string, the prefix is placed after the number with no spacing added anywhere.
- * Listing of prefixes: https://en.wikipedia.org/wiki/Metric_prefix#List_of_SI_prefixes
- */
-/proc/format_si_suffix(num)
- if(num == 0)
- return "[num]"
-
- var/exponent = round_down(log(10, abs(num)))
- var/ofthree = exponent / 3
- if(exponent < 0)
- ofthree = round(ofthree)
- else
- ofthree = round_down(ofthree)
- if(ofthree == 0)
- return "[num]"
- return "[num / (10 ** (ofthree * 3))][GLOB.si_suffixes[round(length(GLOB.si_suffixes) / 2) + ofthree + 1]]"
-
-/**
- * Creates a hyperlink for a specified wiki article.
- */
-/proc/wiki_link(article_name, link_text = null)
- var/url = "[GLOB.configuration.url.wiki_url]/index.php?title=[article_name]"
- return "[link_text ? link_text : url]"
diff --git a/code/__HELPERS/type2type.dm b/code/__HELPERS/type2type.dm
deleted file mode 100644
index 195112118952a..0000000000000
--- a/code/__HELPERS/type2type.dm
+++ /dev/null
@@ -1,340 +0,0 @@
-/*
- * Holds procs designed to change one type of value, into another.
- * Contains:
- * file2list
- * angle2dir
- * angle2text
- * worldtime2text
- */
-
-//Returns an integer value for R of R/G/B given a hex color input.
-/proc/color2R(hex)
- if(!(istext(hex)))
- return
-
- return hex2num(copytext(hex, 2, 4)) //Returning R
-
-//Returns an integer value for G of R/G/B given a hex color input.
-/proc/color2G(hex)
- if(!(istext(hex)))
- return
-
- return hex2num(copytext(hex, 4, 6)) //Returning G
-
-//Returns an integer value for B of R/G/B given a hex color input.
-/proc/color2B(hex)
- if(!(istext(hex)))
- return
-
- return hex2num(copytext(hex, 6, 8)) //Returning B
-
-/proc/text2numlist(text, delimiter="\n")
- var/list/num_list = list()
- for(var/x in splittext(text, delimiter))
- num_list += text2num(x)
- return num_list
-
-//Splits the text of a file at seperator and returns them in a list.
-/proc/file2list(filename, separator = "\n", no_empty = TRUE)
- var/list/result = list()
- for(var/line in splittext(return_file_text(filename), separator))
- var/text = trim(line)
- if(no_empty && !text)
- continue
- result += text
- return result
-
-/proc/dir2text(direction)
- switch(direction)
- if(1.0)
- return "north"
- if(2.0)
- return "south"
- if(4.0)
- return "east"
- if(8.0)
- return "west"
- if(5.0)
- return "northeast"
- if(6.0)
- return "southeast"
- if(9.0)
- return "northwest"
- if(10.0)
- return "southwest"
-
-//Turns text into proper directions
-/proc/text2dir(direction)
- switch(uppertext(direction))
- if("NORTH")
- return 1
- if("SOUTH")
- return 2
- if("EAST")
- return 4
- if("WEST")
- return 8
- if("NORTHEAST")
- return 5
- if("NORTHWEST")
- return 9
- if("SOUTHEAST")
- return 6
- if("SOUTHWEST")
- return 10
-
-//Converts an angle (degrees) into an ss13 direction
-/proc/angle2dir(degree)
- degree = (degree + 22.5) % 360
- if(degree < 45) return NORTH
- if(degree < 90) return NORTHEAST
- if(degree < 135) return EAST
- if(degree < 180) return SOUTHEAST
- if(degree < 225) return SOUTH
- if(degree < 270) return SOUTHWEST
- if(degree < 315) return WEST
- return NORTH|WEST
-
-/proc/angle2dir_cardinal(angle)
- switch(round(angle, 0.1))
- if(315.5 to 360, 0 to 45.5)
- return NORTH
- if(45.6 to 135.5)
- return EAST
- if(135.6 to 225.5)
- return SOUTH
- if(225.6 to 315.5)
- return WEST
-
-//returns the north-zero clockwise angle in degrees, given a direction
-
-/proc/dir2angle(D)
- switch(D)
- if(NORTH) return 0
- if(SOUTH) return 180
- if(EAST) return 90
- if(WEST) return 270
- if(NORTHEAST) return 45
- if(SOUTHEAST) return 135
- if(NORTHWEST) return 315
- if(SOUTHWEST) return 225
- else return null
-
-//Returns the angle in english
-/proc/angle2text(degree)
- return dir2text(angle2dir(degree))
-
-//Converts a blend_mode constant to one acceptable to icon.Blend()
-/proc/blendMode2iconMode(blend_mode)
- switch(blend_mode)
- if(BLEND_MULTIPLY) return ICON_MULTIPLY
- if(BLEND_ADD) return ICON_ADD
- if(BLEND_SUBTRACT) return ICON_SUBTRACT
- else return ICON_OVERLAY
-
-//Converts a rights bitfield into a string
-/proc/rights2text(rights,seperator="")
- if(rights & R_BUILDMODE) . += "[seperator]+BUILDMODE"
- if(rights & R_ADMIN) . += "[seperator]+ADMIN"
- if(rights & R_BAN) . += "[seperator]+BAN"
- if(rights & R_EVENT) . += "[seperator]+EVENT"
- if(rights & R_SERVER) . += "[seperator]+SERVER"
- if(rights & R_DEBUG) . += "[seperator]+DEBUG"
- if(rights & R_POSSESS) . += "[seperator]+POSSESS"
- if(rights & R_PERMISSIONS) . += "[seperator]+PERMISSIONS"
- if(rights & R_STEALTH) . += "[seperator]+STEALTH"
- if(rights & R_REJUVINATE) . += "[seperator]+REJUVINATE"
- if(rights & R_VAREDIT) . += "[seperator]+VAREDIT"
- if(rights & R_SOUNDS) . += "[seperator]+SOUND"
- if(rights & R_SPAWN) . += "[seperator]+SPAWN"
- if(rights & R_PROCCALL) . += "[seperator]+PROCCALL"
- if(rights & R_MOD) . += "[seperator]+MODERATOR"
- if(rights & R_MENTOR) . += "[seperator]+MENTOR"
- if(rights & R_VIEWRUNTIMES) . += "[seperator]+VIEWRUNTIMES"
- if(rights & R_MAINTAINER) . += "[seperator]+MAINTAINER"
- if(rights & R_DEV_TEAM) . += "[seperator]+DEV_TEAM"
-
-/proc/ui_style2icon(ui_style)
- switch(ui_style)
- if("Retro")
- return 'icons/mob/screen_retro.dmi'
- if("Plasmafire")
- return 'icons/mob/screen_plasmafire.dmi'
- if("Slimecore")
- return 'icons/mob/screen_slimecore.dmi'
- if("Operative")
- return 'icons/mob/screen_operative.dmi'
- if("White")
- return 'icons/mob/screen_white.dmi'
- if("Midnight")
- return 'icons/mob/screen_midnight.dmi'
- else
- return 'icons/mob/screen_midnight.dmi'
-
-//colour formats
-/proc/rgb2hsl(red, green, blue)
- red /= 255;green /= 255;blue /= 255;
- var/max = max(red,green,blue)
- var/min = min(red,green,blue)
- var/range = max-min
-
- var/hue=0;var/saturation=0;var/lightness=0;
- lightness = (max + min)/2
- if(range != 0)
- if(lightness < 0.5) saturation = range/(max+min)
- else saturation = range/(2-max-min)
-
- var/dred = ((max-red)/(6*max)) + 0.5
- var/dgreen = ((max-green)/(6*max)) + 0.5
- var/dblue = ((max-blue)/(6*max)) + 0.5
-
- if(max==red) hue = dblue - dgreen
- else if(max==green) hue = dred - dblue + (1/3)
- else hue = dgreen - dred + (2/3)
- if(hue < 0) hue++
- else if(hue > 1) hue--
-
- return list(hue, saturation, lightness)
-
-/proc/hsl2rgb(hue, saturation, lightness)
- var/red;var/green;var/blue;
- if(saturation == 0)
- red = lightness * 255
- green = red
- blue = red
- else
- var/a;var/b;
- if(lightness < 0.5) b = lightness*(1+saturation)
- else b = (lightness+saturation) - (saturation*lightness)
- a = 2*lightness - b
-
- red = round(255 * hue2rgb(a, b, hue+(1/3)))
- green = round(255 * hue2rgb(a, b, hue))
- blue = round(255 * hue2rgb(a, b, hue-(1/3)))
-
- return list(red, green, blue)
-
-/proc/hue2rgb(a, b, hue)
- if(hue < 0) hue++
- else if(hue > 1) hue--
- if(6*hue < 1) return (a+(b-a)*6*hue)
- if(2*hue < 1) return b
- if(3*hue < 2) return (a+(b-a)*((2/3)-hue)*6)
- return a
-
-/proc/num2septext(theNum, sigFig = 7, sep=",") // default sigFig (1,000,000)
- var/finalNum = num2text(theNum, sigFig)
-
- // Start from the end, or from the decimal point
- var/end = findtextEx(finalNum, ".") || length(finalNum) + 1
-
- // Moving towards start of string, insert comma every 3 characters
- for(var/pos = end - 3, pos > 1, pos -= 3)
- finalNum = copytext(finalNum, 1, pos) + sep + copytext(finalNum, pos)
-
- return finalNum
-
-
-// heat2color functions. Adapted from: http://www.tannerhelland.com/4435/convert-temperature-rgb-algorithm-code/
-/proc/heat2color(temp)
- return rgb(heat2color_r(temp), heat2color_g(temp), heat2color_b(temp))
-
-/proc/heat2color_r(temp)
- temp /= 100
- if(temp <= 66)
- . = 255
- else
- . = max(0, min(255, 329.698727446 * (temp - 60) ** -0.1332047592))
-
-/proc/heat2color_g(temp)
- temp /= 100
- if(temp <= 66)
- . = max(0, min(255, 99.4708025861 * log(max(temp, 1)) - 161.1195681661))
- else
- . = max(0, min(255, 288.1221695283 * ((temp - 60) ** -0.0755148492)))
-
-/proc/heat2color_b(temp)
- temp /= 100
- if(temp >= 66)
- . = 255
- else
- if(temp <= 16)
- . = 0
- else
- . = max(0, min(255, 138.5177312231 * log(max(temp - 10, 1)) - 305.0447927307))
-
-//Argument: Give this a space-separated string consisting of 6 numbers. Returns null if you don't
-/proc/text2matrix(matrixtext)
- var/list/matrixtext_list = splittext(matrixtext, " ")
- var/list/matrix_list = list()
- for(var/item in matrixtext_list)
- var/entry = text2num(item)
- if(entry == null)
- return null
- matrix_list += entry
- if(length(matrix_list) < 6)
- return null
- var/a = matrix_list[1]
- var/b = matrix_list[2]
- var/c = matrix_list[3]
- var/d = matrix_list[4]
- var/e = matrix_list[5]
- var/f = matrix_list[6]
- return matrix(a, b, c, d, e, f)
-
-
-//This is a weird one:
-//It returns a list of all var names found in the string
-//These vars must be in the [var_name] format
-//It's only a proc because it's used in more than one place
-
-//Takes a string and a datum
-//The string is well, obviously the string being checked
-//The datum is used as a source for var names, to check validity
-//Otherwise every single word could technically be a variable!
-/proc/string2listofvars(t_string, datum/var_source)
- if(!t_string || !var_source)
- return list()
-
- . = list()
-
- var/var_found = findtext(t_string, "\[") //Not the actual variables, just a generic "should we even bother" check
- if(var_found)
- //Find var names
-
- // "A dog said hi [name]!"
- // splittext() --> list("A dog said hi ","name]!"
- // jointext() --> "A dog said hi name]!"
- // splittext() --> list("A","dog","said","hi","name]!")
-
- t_string = replacetext(t_string, "\[", "\[ ")//Necessary to resolve "word[var_name]" scenarios
- var/list/list_value = splittext(t_string, "\[")
- var/intermediate_stage = jointext(list_value, null)
-
- list_value = splittext(intermediate_stage, " ")
- for(var/value in list_value)
- if(findtext(value, "]"))
- value = splittext(value, "]") //"name]!" --> list("name","!")
- for(var/A in value)
- if(var_source.vars.Find(A))
- . += A
-
-/proc/type2parent(child)
- var/string_type = "[child]"
- var/last_slash = findlasttext(string_type, "/")
- if(last_slash == 1)
- switch(child)
- if(/datum)
- return null
- if(/obj, /mob)
- return /atom/movable
- if(/area, /turf)
- return /atom
- else
- return /datum
- return text2path(copytext(string_type, 1, last_slash))
-
-/proc/text2bool(input)
- if(input == "true")
- return TRUE
- return FALSE //
diff --git a/code/_compile_options.dm b/code/_compile_options.dm
deleted file mode 100644
index 0990e5bd29c63..0000000000000
--- a/code/_compile_options.dm
+++ /dev/null
@@ -1,48 +0,0 @@
-#define DEBUG
-//#define TESTING
-
-// Uncomment the following line to compile unit tests on a local server. The output will be in a test_run-[DATE].log file in the ./data folder.
-// #define LOCAL_GAME_TESTS
-
-// Uncomment the following line to enable Tracy profiling.
-// DO NOT DO THIS UNLESS YOU UNDERSTAND THE IMPLICATIONS
-// Your data directory will grow by about a gigabyte every time you launch the server, as well as introducing potential instabilities over multiple BYOND versions.
-// #define ENABLE_BYOND_TRACY
-
-// Uncomment this to enable support for multiple instances
-// #define MULTIINSTANCE
-
-#ifdef LOCAL_GAME_TESTS
-#define GAME_TESTS
-#endif
-
-#ifdef CIBUILDING
-#define GAME_TESTS
-#endif
-
-#if defined(CIBUILDING) && defined(LOCAL_GAME_TESTS)
-#error CIBUILDING and LOCAL_GAME_TESTS should not be enabled at the same time!
-#endif
-
-/***** All toggles for the GC ref finder *****/
-
-// #define REFERENCE_TRACKING // Uncomment to enable ref finding
-
-// #define GC_FAILURE_HARD_LOOKUP //makes paths that fail to GC call find_references before del'ing.
-
-// #define FIND_REF_NO_CHECK_TICK //Sets world.loop_checks to false and prevents find references from sleeping
-
-// #define FIND_REF_NOTIFY_ON_COMPLETE // Throw a windows notification toast when the ref finding process is done
-
-/***** End toggles for the GC ref finder *****/
-
-#define IS_MODE_COMPILED(MODE) (ispath(text2path("/datum/game_mode/"+(MODE))))
-
-//Update this whenever you need to take advantage of more recent byond features
-#define MIN_COMPILER_VERSION 515
-#define MIN_COMPILER_BUILD 1619
-#if DM_VERSION < MIN_COMPILER_VERSION || DM_BUILD < MIN_COMPILER_BUILD
-//Don't forget to update this part
-#error Your version of BYOND is too out-of-date to compile this project. Go to secure.byond.com/download and update.
-#error You need version 515.1619 or higher
-#endif
diff --git a/code/_globalvars/bitfields.dm b/code/_globalvars/bitfields.dm
deleted file mode 100644
index e165eabdb7059..0000000000000
--- a/code/_globalvars/bitfields.dm
+++ /dev/null
@@ -1,216 +0,0 @@
-GLOBAL_LIST_INIT(bitfields, generate_bitfields())
-
-/// Specifies a bitfield for smarter debugging
-/datum/bitfield
- /// The variable name that contains the bitfield
- var/variable
- /// An associative list of the readable flag and its true value
- var/list/flags
-
-/datum/bitfield/can_vv_delete()
- return FALSE
-
-/datum/bitfield/vv_edit_var(var_name, var_value)
- return FALSE // no.
-
-/// Turns /datum/bitfield subtypes into a list for use in debugging
-/proc/generate_bitfields()
- var/list/bitfields = list()
- for(var/_bitfield in subtypesof(/datum/bitfield))
- var/datum/bitfield/bitfield = new _bitfield
- bitfields[bitfield.variable] = bitfield.flags
- return bitfields
-
-/proc/translate_bitfield(variable_type, variable_name, variable_value)
- if(variable_type != VV_BITFIELD)
- return variable_value
-
- var/list/flags = list()
- for(var/flag in GLOB.bitfields[variable_name])
- if(variable_value & GLOB.bitfields[variable_name][flag])
- flags += flag
- if(length(flags))
- return jointext(flags, ", ")
- return "NONE"
-
-/proc/input_bitfield(mob/user, bitfield, current_value)
- if(!user || !(bitfield in GLOB.bitfields))
- return
- var/list/currently_checked = list()
- for(var/name in GLOB.bitfields[bitfield])
- currently_checked[name] = (current_value & GLOB.bitfields[bitfield][name])
-
- var/list/result = tgui_input_checkbox_list(user, "Editing bitfield for [bitfield].", "Editing bitfield", currently_checked)
- if(isnull(result) || !islist(result))
- return
-
- var/new_result = 0
- for(var/name in GLOB.bitfields[bitfield])
- if(result[name])
- new_result |= GLOB.bitfields[bitfield][name]
- return new_result
-
-// MARK: Default byond bitfields
-
-DEFINE_BITFIELD(appearance_flags, list(
- "KEEP_APART" = KEEP_APART,
- "KEEP_TOGETHER" = KEEP_TOGETHER,
- "LONG_GLIDE" = LONG_GLIDE,
- "NO_CLIENT_COLOR" = NO_CLIENT_COLOR,
- "PIXEL_SCALE" = PIXEL_SCALE,
- "PLANE_MASTER" = PLANE_MASTER,
- "RESET_ALPHA" = RESET_ALPHA,
- "RESET_COLOR" = RESET_COLOR,
- "RESET_TRANSFORM" = RESET_TRANSFORM,
- "TILE_BOUND" = TILE_BOUND,
- "PASS_MOUSE" = PASS_MOUSE,
- "TILE_MOVER" = TILE_MOVER,
-))
-
-DEFINE_BITFIELD(sight, list(
- "BLIND" = BLIND,
- "SEE_BLACKNESS" = SEE_BLACKNESS,
- "SEE_INFRA" = SEE_INFRA,
- "SEE_MOBS" = SEE_MOBS,
- "SEE_OBJS" = SEE_OBJS,
- "SEE_PIXELS" = SEE_PIXELS,
- "SEE_SELF" = SEE_SELF,
- "SEE_THRU" = SEE_THRU,
- "SEE_TURFS" = SEE_TURFS,
-))
-
-DEFINE_BITFIELD(vis_flags, list(
- "VIS_HIDE" = VIS_HIDE,
- "VIS_INHERIT_DIR" = VIS_INHERIT_DIR,
- "VIS_INHERIT_ICON" = VIS_INHERIT_ICON,
- "VIS_INHERIT_ICON_STATE" = VIS_INHERIT_ICON_STATE,
- "VIS_INHERIT_ID" = VIS_INHERIT_ID,
- "VIS_INHERIT_LAYER" = VIS_INHERIT_LAYER,
- "VIS_INHERIT_PLANE" = VIS_INHERIT_PLANE,
- "VIS_UNDERLAY" = VIS_UNDERLAY,
-))
-
-
-// MARK: Other bitfields
-
-DEFINE_BITFIELD(flags, list(
- "STOPSPRESSUREDMAGE" = STOPSPRESSUREDMAGE,
- "NODROP" = NODROP,
- "NOBLUDGEON" = NOBLUDGEON,
- "AIRTIGHT" = AIRTIGHT,
- "HANDSLOW" = HANDSLOW,
- "CONDUCT" = CONDUCT,
- "ABSTRACT" = ABSTRACT,
- "ON_BORDER" = ON_BORDER,
- "PREVENT_CLICK_UNDER" = PREVENT_CLICK_UNDER,
- "NODECONSTRUCT" = NODECONSTRUCT,
- "EARBANGPROTECT" = EARBANGPROTECT,
- "HEADBANGPROTECT" = HEADBANGPROTECT,
- "BLOCK_GAS_SMOKE_EFFECT" = BLOCK_GAS_SMOKE_EFFECT,
- "THICKMATERIAL" = THICKMATERIAL,
- "DROPDEL" = DROPDEL,
- "NO_SCREENTIPS" = NO_SCREENTIPS,
-))
-
-DEFINE_BITFIELD(flags_2, list(
- "SLOWS_WHILE_IN_HAND_2" = SLOWS_WHILE_IN_HAND_2,
- "NO_EMP_WIRES_2" = NO_EMP_WIRES_2,
- "HOLOGRAM_2" = HOLOGRAM_2,
- "FROZEN_2" = FROZEN_2,
- "STATIONLOVING_2" = STATIONLOVING_2,
- "INFORM_ADMINS_ON_RELOCATE_2" = INFORM_ADMINS_ON_RELOCATE_2,
- "BANG_PROTECT_2" = BANG_PROTECT_2,
- "BLOCKS_LIGHT_2" = BLOCKS_LIGHT_2,
- "DECAL_INIT_UPDATE_EXPERIENCED_2" = DECAL_INIT_UPDATE_EXPERIENCED_2,
- "OMNITONGUE_2" = OMNITONGUE_2,
- "SHOCKED_2" = SHOCKED_2,
- "NO_MAT_REDEMPTION_2" = NO_MAT_REDEMPTION_2,
- "LAVA_PROTECT_2" = LAVA_PROTECT_2,
- "OVERLAY_QUEUED_2" = OVERLAY_QUEUED_2,
- "RAD_PROTECT_CONTENTS_2" = RAD_PROTECT_CONTENTS_2,
- "RAD_NO_CONTAMINATE_2" = RAD_NO_CONTAMINATE_2,
- "IMMUNE_TO_SHUTTLECRUSH_2" = IMMUNE_TO_SHUTTLECRUSH_2,
- "NO_MALF_EFFECT_2" = NO_MALF_EFFECT_2,
- "CRITICAL_ATOM_2" = CRITICAL_ATOM_2,
- "RANDOM_BLOCKER_2" = RANDOM_BLOCKER_2,
- "ALLOW_BELT_NO_JUMPSUIT_2" = ALLOW_BELT_NO_JUMPSUIT_2,
-))
-
-DEFINE_BITFIELD(flags_ricochet, list(
- "RICOCHET_SHINY" = RICOCHET_SHINY,
- "RICOCHET_HARD" = RICOCHET_HARD,
-))
-
-DEFINE_BITFIELD(clothing_flags, list(
- "HAS_UNDERWEAR" = HAS_UNDERWEAR,
- "HAS_UNDERSHIRT" = HAS_UNDERSHIRT,
- "HAS_SOCKS" = HAS_SOCKS,
-))
-
-DEFINE_BITFIELD(bodyflags, list(
- "HAS_HEAD_ACCESSORY" = HAS_HEAD_ACCESSORY,
- "HAS_TAIL" = HAS_TAIL,
- "TAIL_OVERLAPPED" = TAIL_OVERLAPPED,
- "HAS_SKIN_TONE" = HAS_SKIN_TONE,
- "HAS_ICON_SKIN_TONE" = HAS_ICON_SKIN_TONE,
- "HAS_SKIN_COLOR" = HAS_SKIN_COLOR,
- "HAS_HEAD_MARKINGS" = HAS_HEAD_MARKINGS,
- "HAS_BODY_MARKINGS" = HAS_BODY_MARKINGS,
- "HAS_TAIL_MARKINGS" = HAS_TAIL_MARKINGS,
- "TAIL_WAGGING" = TAIL_WAGGING,
- "NO_EYES" = NO_EYES,
- "HAS_ALT_HEADS" = HAS_ALT_HEADS,
- "HAS_WING" = HAS_WING,
- "HAS_BODYACC_COLOR" = HAS_BODYACC_COLOR,
- "BALD" = BALD,
- "ALL_RPARTS" = ALL_RPARTS,
- "SHAVED" = SHAVED,
-))
-
-DEFINE_BITFIELD(pass_flags, list(
- "PASSTABLE" = PASSTABLE,
- "PASSGLASS" = PASSGLASS,
- "PASSGRILLE" = PASSGRILLE,
- "PASSBLOB" = PASSBLOB,
- "PASSMOB" = PASSMOB,
- "LETPASSTHROW" = LETPASSTHROW,
- "PASSFENCE" = PASSFENCE,
- "PASSDOOR" = PASSDOOR,
- "PASSGIRDER" = PASSGIRDER,
- "PASSBARRICADE" = PASSBARRICADE,
- "PASSTAKE" = PASSTAKE,
-))
-
-DEFINE_BITFIELD(pass_flags_self, list(
- "PASSTAKE" = PASSTAKE,
- "LETPASSTHROW" = LETPASSTHROW,
-))
-
-DEFINE_BITFIELD(resistance_flags, list(
- "LAVA_PROOF" = LAVA_PROOF,
- "FIRE_PROOF" = FIRE_PROOF,
- "FLAMMABLE" = FLAMMABLE,
- "ON_FIRE" = ON_FIRE,
- "UNACIDABLE" = UNACIDABLE,
- "ACID_PROOF" = ACID_PROOF,
- "INDESTRUCTIBLE" = INDESTRUCTIBLE,
- "FREEZE_PROOF" = FREEZE_PROOF,
-))
-
-DEFINE_BITFIELD(mobility_flags, list(
- "MOBILITY_MOVE" = MOBILITY_MOVE,
- "MOBILITY_STAND" = MOBILITY_STAND,
- "MOBILITY_PICKUP" = MOBILITY_PICKUP,
- "MOBILITY_USE" = MOBILITY_USE,
- "MOBILITY_PULL" = MOBILITY_PULL,
-))
-
-DEFINE_BITFIELD(status_flags, list(
- "CANSTUN" = CANSTUN,
- "CANWEAKEN" = CANWEAKEN,
- "CANPARALYSE" = CANPARALYSE,
- "CANPUSH" = CANPUSH,
- "PASSEMOTES" = PASSEMOTES,
- "GODMODE" = GODMODE,
- "TERMINATOR_FORM" = TERMINATOR_FORM,
-))
diff --git a/code/_globalvars/lists/flavor_misc.dm b/code/_globalvars/lists/flavor_misc.dm
deleted file mode 100644
index 71b013826a794..0000000000000
--- a/code/_globalvars/lists/flavor_misc.dm
+++ /dev/null
@@ -1,92 +0,0 @@
-//Preferences stuff
- //Head accessory styles
-GLOBAL_LIST_EMPTY(head_accessory_styles_list) //stores /datum/sprite_accessory/head_accessory indexed by name
- //Marking styles
-GLOBAL_LIST_EMPTY(marking_styles_list) //stores /datum/sprite_accessory/body_markings indexed by name
- //Hairstyles
-GLOBAL_LIST_EMPTY(hair_styles_public_list) //stores /datum/sprite_accessory/hair indexed by name
-GLOBAL_LIST_EMPTY(hair_styles_male_list)
-GLOBAL_LIST_EMPTY(hair_styles_female_list)
-GLOBAL_LIST_EMPTY(hair_styles_full_list) //fluff hair styles
-GLOBAL_LIST_EMPTY(facial_hair_styles_list) //stores /datum/sprite_accessory/facial_hair indexed by name
-GLOBAL_LIST_EMPTY(facial_hair_styles_male_list)
-GLOBAL_LIST_EMPTY(facial_hair_styles_female_list)
-GLOBAL_LIST_EMPTY(hair_gradients_list) //stores /datum/sprite_accessory/hair_gradient indexed by name
- //Underwear
-GLOBAL_LIST_EMPTY(underwear_list) //stores /datum/sprite_accessory/underwear indexed by name
-GLOBAL_LIST_EMPTY(underwear_m) //stores only underwear name
-GLOBAL_LIST_EMPTY(underwear_f) //stores only underwear name
- //Undershirts
-GLOBAL_LIST_EMPTY(undershirt_list) //stores /datum/sprite_accessory/undershirt indexed by name
-GLOBAL_LIST_EMPTY(undershirt_m) //stores only undershirt name
-GLOBAL_LIST_EMPTY(undershirt_f) //stores only undershirt name
- //Socks
-GLOBAL_LIST_EMPTY(socks_list) //stores /datum/sprite_accessory/socks indexed by name
-GLOBAL_LIST_EMPTY(socks_m) //stores only socks name
-GLOBAL_LIST_EMPTY(socks_f) //stores only socks name
- //Alt Heads
-GLOBAL_LIST_EMPTY(alt_heads_list) //stores /datum/sprite_accessory/alt_heads indexed by name
-
-// Reference list for disposal sort junctions. Set the sort_type_txt variable on disposal sort junctions to
-// the index of the sort department that you want. For example, adding "2" to sort_type_txt will reroute all packages
-// tagged for the Cargo Bay. Multiple destinations can be added by separating them with ;, like "2;8" for Cargo Bay and Security.
-
-//If you don't want to fuck up disposals, add to this list, and don't change the order.
-//If you insist on changing the order, you'll have to change every sort junction to reflect the new order. --Pete
-
-GLOBAL_LIST_INIT(TAGGERLOCATIONS, list("Disposals",
- "Cargo Bay", "QM Office", "Engineering", "CE Office",
- "Atmospherics", "HoS Office", "Security", "Medbay",
- "CMO Office", "Chemistry", "Research", "RD Office",
- "Robotics", "HoP Office", "Library", "Chapel", "Captain's Office",
- "Bar", "Kitchen", "Hydroponics", "Janitor Closet","Genetics", "Detective", "Morgue"))
-
-GLOBAL_LIST_INIT(greek_letters, list("Alpha", "Beta", "Gamma", "Delta",
- "Epsilon", "Zeta", "Eta", "Theta", "Iota", "Kappa", "Lambda", "Mu",
- "Nu", "Xi", "Omicron", "Pi", "Rho", "Sigma", "Tau", "Upsilon", "Phi",
- "Chi", "Psi", "Omega"))
-
-GLOBAL_LIST_INIT(phonetic_alphabet, list("Alpha", "Bravo", "Charlie",
- "Delta", "Echo", "Foxtrot", "Golf", "Hotel", "India", "Juliet",
- "Kilo", "Lima", "Mike", "November", "Oscar", "Papa", "Quebec",
- "Romeo", "Sierra", "Tango", "Uniform", "Victor", "Whiskey", "X-ray",
- "Yankee", "Zulu"))
-
-//Backpacks
-#define GBACKPACK "Grey Backpack"
-#define GSATCHEL "Grey Satchel"
-#define GDUFFLEBAG "Grey Dufflebag"
-#define LSATCHEL "Leather Satchel"
-#define DBACKPACK "Department Backpack"
-#define DSATCHEL "Department Satchel"
-#define DDUFFLEBAG "Department Dufflebag"
-GLOBAL_LIST_INIT(backbaglist, list(DBACKPACK, DSATCHEL, DDUFFLEBAG, GBACKPACK, GSATCHEL, GDUFFLEBAG, LSATCHEL))
-
-//Chooseable brain types for borgs
-#define MMI_BORG "MMI"
-#define ROBOBRAIN_BORG "Robobrain"
-#define POSITRONIC_BORG "Positronic"
-GLOBAL_LIST_INIT(borg_brain_choices, list(MMI_BORG, ROBOBRAIN_BORG, POSITRONIC_BORG))
-GLOBAL_PROTECT(borg_brain_choices)
-
-//Chooseable ringtones
-//Due to database reasons, the name should be 16 characters long maximum
-GLOBAL_LIST_INIT(pda_ringtone_choices, list("beep" = 'sound/machines/twobeep.ogg',
- "boop" = 'sound/machines/boop.ogg',
- "electronic" = 'sound/machines/notif1.ogg',
- "chime" = 'sound/machines/notif2.ogg',
- "slip" = 'sound/misc/slip.ogg',
- "honk" = 'sound/items/bikehorn.ogg',
- "SKREE" = 'sound/voice/shriek1.ogg',
- "holy" = 'sound/items/PDA/ambicha4-short.ogg',
- "boom" = 'sound/effects/explosionfar.ogg',
- "gavel" = 'sound/items/gavel.ogg',
- "xeno" = 'sound/voice/hiss1.ogg',
- "smoke" = 'sound/magic/smoke.ogg',
- "shatter" = 'sound/effects/pylon_shatter.ogg',
- "energy" = 'sound/weapons/egloves.ogg',
- "flare" = 'sound/goonstation/misc/matchstick_light.ogg',
- "interference" = 'sound/misc/interference.ogg',
- "zap" = 'sound/effects/eleczap.ogg',
- "disgusting" = 'sound/effects/blobattack.ogg',
- "hungry" = 'sound/weapons/bite.ogg'))
diff --git a/code/_globalvars/lists/maint_loot_tables.dm b/code/_globalvars/lists/maint_loot_tables.dm
deleted file mode 100644
index 229024bd9e461..0000000000000
--- a/code/_globalvars/lists/maint_loot_tables.dm
+++ /dev/null
@@ -1,208 +0,0 @@
-GLOBAL_LIST_INIT(maintenance_loot_tier_0, list(
- list(
- // Tools
- /obj/effect/spawner/random/engineering/tools = 4,
-
- // Materials
- /obj/effect/spawner/random/engineering/materials = 4,
- // Plushies
- /obj/effect/spawner/random/plushies = 1,
- ) = 6,
-
- list(
- // Spawners for easily found items
- /obj/effect/spawner/random/bureaucracy,
- /obj/effect/spawner/random/dice,
- /obj/effect/spawner/random/book,
-
- // Other worthless/easily found items
- /obj/item/camera_film,
- /obj/item/camera,
- /obj/item/caution,
- /obj/item/clothing/head/cone,
- /obj/item/light/bulb,
- /obj/item/light/tube,
- /obj/item/poster/random_contraband,
- /obj/item/poster/random_official,
- /obj/item/reagent_containers/drinks/drinkingglass,
- /obj/item/reagent_containers/glass/beaker/waterbottle,
- /obj/item/reagent_containers/glass/beaker/waterbottle/empty,
- /obj/item/scissors,
- /obj/item/storage/box,
- /obj/item/storage/fancy/crayons,
- /obj/item/storage/fancy/matches,
- ) = 2,
-
- list(
- // Emergency items
- /obj/item/extinguisher,
- /obj/item/flashlight,
- ) = 1,
-))
-
-GLOBAL_LIST_INIT(maintenance_loot_tier_1, list(
- list(
- // Sub-spawners
- /obj/effect/spawner/random/engineering/toolbox,
- /obj/effect/spawner/random/snacks,
-
- // Assemblies and cells
- /obj/item/assembly/prox_sensor,
- /obj/item/assembly/timer,
- /obj/item/assembly/signaler,
- /obj/item/assembly/voice,
- /obj/item/assembly/voice/noise,
- /obj/item/stock_parts/cell,
-
- // Clothing
- /obj/item/clothing/glasses/sunglasses,
- /obj/item/clothing/gloves/color/black,
- /obj/item/clothing/gloves/color/fyellow,
- /obj/item/clothing/gloves/color/yellow/fake,
- /obj/item/clothing/head/hardhat,
- /obj/item/clothing/head/hardhat/red,
- /obj/item/clothing/head/that,
- /obj/item/clothing/head/ushanka,
- /obj/item/clothing/mask/gas,
- /obj/item/clothing/shoes/black,
- /obj/item/clothing/suit/storage/hazardvest,
- /obj/item/clothing/under/color/black,
- /obj/item/clothing/under/misc/vice,
-
- // Medical supplies / chemistry items
- /obj/item/reagent_containers/dropper,
- /obj/item/reagent_containers/glass/beaker,
- /obj/item/reagent_containers/glass/beaker/large,
- /obj/item/reagent_containers/syringe,
- /obj/item/stack/medical/bruise_pack/advanced,
- /obj/item/stack/medical/ointment/advanced,
-
- // Common items
- /obj/item/bodybag,
- /obj/item/cultivator,
- /obj/item/flashlight/pen,
- /obj/item/radio/off,
- /obj/item/reagent_containers/drinks/mug,
- /obj/item/reagent_containers/glass/bucket,
- /obj/item/reagent_containers/spray/pestspray,
- /obj/item/relic,
- /obj/item/restraints/handcuffs/toy,
- /obj/item/scratch,
- /obj/item/seeds/ambrosia,
- /obj/item/seeds/ambrosia/deus,
- /obj/item/stack/sheet/cardboard,
- /obj/item/stack/sheet/cloth,
- /obj/item/storage/bag/plasticbag,
- /obj/item/storage/box/cups,
- /obj/item/storage/box/donkpockets,
- /obj/item/storage/box/lights/mixed,
- /obj/item/storage/fancy/cigarettes/dromedaryco,
- /obj/item/tank/internals/emergency_oxygen,
- /obj/item/tank/internals/emergency_oxygen/engi,
- /obj/item/vending_refill/cola,
- ) = 85,
-
- list(
- /obj/item/storage/wallet,
- /obj/item/storage/wallet/random,
- ) = 5,
-
- list(
- // Small chance of tier 1 stock parts
- /obj/item/stock_parts/capacitor,
- /obj/item/stock_parts/manipulator,
- /obj/item/stock_parts/matter_bin,
- /obj/item/stock_parts/micro_laser,
- /obj/item/stock_parts/scanning_module,
-
- // Coins
- /obj/item/coin/silver,
- /obj/item/coin/twoheaded,
- ) = 2,
-))
-
-GLOBAL_LIST_INIT(maintenance_loot_tier_2, list(
- list(
- // Rarer items
- /obj/effect/spawner/random/mod_maint,
- /obj/item/clothing/glasses/meson,
- /obj/item/clothing/head/welding,
- /obj/item/crowbar/red,
- /obj/item/storage/belt/utility,
- ) = 45,
-
- list(
- // Contraband and Syndicate items
- /obj/item/ammo_box/magazine/m10mm,
- /obj/item/clothing/mask/chameleon,
- /obj/item/clothing/mask/chameleon/voice_change,
- /obj/item/clothing/mask/gas/voice_modulator,
- /obj/item/clothing/mask/gas/voice_modulator/chameleon,
- /obj/item/clothing/shoes/chameleon/noslip,
- /obj/item/clothing/suit/jacket/bomber/syndicate,
- /obj/item/clothing/suit/storage/iaa/blackjacket/armored,
- /obj/item/clothing/under/chameleon,
- /obj/item/deck/cards/syndicate,
- /obj/item/grenade/clown_grenade,
- /obj/item/grenade/smokebomb,
- /obj/item/gun/syringe/syndicate,
- /obj/item/melee/knuckleduster/syndie,
- /obj/item/mod/construction/broken_core,
- /obj/item/multitool/ai_detect,
- /obj/item/seeds/ambrosia/cruciatus,
- /obj/item/soap/syndie,
- /obj/item/stamp/chameleon,
- /obj/item/storage/backpack/duffel/syndie/med/surgery_fake,
- /obj/item/storage/backpack/satchel_flat,
- /obj/item/storage/belt/military/traitor,
- /obj/item/storage/fancy/cigarettes/cigpack_syndicate,
- /obj/item/storage/pill_bottle/fakedeath,
- /obj/item/storage/secure/briefcase/syndie,
- /obj/item/storage/toolbox/syndicate,
- /obj/item/suppressor,
- /obj/item/weaponcrafting/receiver,
- ) = 45,
-
- list(
- // Health/repair kits
- /obj/item/storage/firstaid/regular,
- /obj/item/storage/firstaid/machine,
-
- // Rarer departmental items
- /obj/item/reagent_scanner/adv,
- /obj/item/robotanalyzer,
- /obj/item/stack/nanopaste,
- /obj/item/whetstone,
-
- // Other rare but useful items
- /obj/item/radio/headset,
- /obj/item/melee/knuckleduster,
- ) = 3,
-))
-
-GLOBAL_LIST_INIT(maintenance_loot_tier_3, list(
- list(
- // Coveted items
- /obj/item/clothing/gloves/color/yellow,
- ) = 7,
-
- list(
- // Rare Syndicate items
- /obj/item/gun/projectile/automatic/pistol,
- /obj/item/dnascrambler,
- /obj/item/bio_chip_implanter/storage,
- /obj/item/reagent_containers/spray/sticky_tar,
- /obj/item/storage/box/syndie_kit/space,
- ) = 3,
-))
-
-GLOBAL_LIST_INIT(maintenance_loot_tables, list(
- list(
- GLOB.maintenance_loot_tier_0 = 490,
- GLOB.maintenance_loot_tier_1 = 390,
- GLOB.maintenance_loot_tier_2 = 114,
- GLOB.maintenance_loot_tier_3 = 6,
- ) = 75,
-
- /obj/effect/spawner/random/trash = 25,
-))
diff --git a/code/_globalvars/lists/reagents_lists.dm b/code/_globalvars/lists/reagents_lists.dm
deleted file mode 100644
index 447e4783475f6..0000000000000
--- a/code/_globalvars/lists/reagents_lists.dm
+++ /dev/null
@@ -1,54 +0,0 @@
-// Standard medicines
-GLOBAL_LIST_INIT(standard_medicines, list("charcoal","toxin","cyanide","morphine","epinephrine","space_drugs",
- "mutadone","mutagen","teporone","lexorin","silver_sulfadiazine",
- "salbutamol","perfluorodecalin","omnizine","synaptizine","haloperidol",
- "potass_iodide","pen_acid","mannitol","oculine","styptic_powder","happiness",
- "methamphetamine","spaceacillin","carpotoxin","lsd","ethanol","ammonia",
- "diethylamine","antihol","pancuronium","lipolicide","condensedcapsaicin",
- "frostoil","amanitin","psilocybin","nothing","salglu_solution","neurotoxin"))
-// Rare medicines
-GLOBAL_LIST_INIT(rare_medicines, list("syndicate_nanites","minttoxin","blood", "xenomicrobes"))
-// Drinks
-GLOBAL_LIST_INIT(drinks, list("beer2","hot_coco","orangejuice","tomatojuice","limejuice","carrotjuice",
- "berryjuice","poisonberryjuice","watermelonjuice","lemonjuice","banana", "bungojuice",
- "nothing","potato","milk","soymilk","cream","coffee","tea","icecoffee",
- "icetea","cola","nuka_cola","spacemountainwind","thirteenloko","dr_gibb",
- "space_up","lemon_lime","beer","whiskey","gin","rum","vodka","holywater",
- "tequila","vermouth","wine","tonic","kahlua","cognac","ale","sodawater",
- "ice","bilk","atomicbomb","threemileisland","goldschlager","patron","gintonic",
- "cubalibre","whiskeycola","martini","vodkamartini","whiterussian","screwdrivercocktail",
- "booger","bloodymary","gargleblaster","bravebull","tequilasunrise","toxinsspecial",
- "beepskysmash","salglu_solution","irishcream","manlydorf","longislandicedtea",
- "moonshine","b52","irishcoffee","margarita","blackrussian","manhattan",
- "manhattan_proj","whiskeysoda","adminfreeze","antifreeze","barefoot","snowwhite","demonsblood",
- "vodkatonic","ginfizz","bahama_mama","singulo","sbiten","devilskiss","red_mead",
- "mead","iced_beer","grog","aloe","andalusia","alliescocktail","soy_latte",
- "cafe_latte","acidspit","amasec","neurotoxin","hippiesdelight","bananahonk",
- "silencer","changelingsting","irishcarbomb","syndicatebomb","erikasurprise","driestmartini", "flamingmoe",
- "arnold_palmer","gimlet","sidecar","whiskeysour","mintjulep","pinacolada","sontse","ahdomaieclipse",
- "beachfeast","fyrsskartears","junglevox","slimemold","dieseife","aciddreams","islaywhiskey","ultramatter",
- "howler", "dionasmash"))
-
-//Liver Toxins list
-GLOBAL_LIST_INIT(liver_toxins, list("toxin", "plasma", "sacid", "facid", "cyanide","amanitin", "carpotoxin"))
-
-//General chem blacklist. This is for the really good stuff that we just want to restrict from things like bees and odysseus syringe gun duping
-GLOBAL_LIST_INIT(blocked_chems, list("polonium", "initropidril", "concentrated_initro",
- "sodium_thiopental", "ketamine",
- "adminordrazine", "nanites", "hell_water",
- "mutationtoxin", "amutationtoxin", "venom",
- "spore", "stimulants", "stimulative_agent",
- "syndicate_nanites", "ripping_tendrils", "boiling_oil",
- "envenomed_filaments", "lexorin_jelly", "kinetic",
- "cryogenic_liquid", "liquid_dark_matter", "b_sorium",
- "reagent", "drink", "medicine", "plantnutrient", "consumable", "dragonsbreath",
- "nanocalcium", "xenomicrobes", "nanomachines", "gibbis", "prions",
- "spidereggs", "heartworms", "bacon_grease",
- "fungalspores", "jagged_crystals", "salmonella",
- "lavaland_extract", "stable_mutagen", "beer2",
- "curare", "gluttonytoxin", "smoke_powder", "stimulative_cling",
- "teslium_paste", "omnizine_no_addiction", "zombiecure1",
- "zombiecure2", "zombiecure3", "zombiecure4",
- "admincleaner_all", "admincleaner_item", "admincleaner_mob",
- "synthetic_omnizine_no_addiction", "surge_plus"
- ))
diff --git a/code/_onclick/click.dm b/code/_onclick/click.dm
deleted file mode 100644
index c108b54e38cf5..0000000000000
--- a/code/_onclick/click.dm
+++ /dev/null
@@ -1,517 +0,0 @@
-/*
- Click code cleanup
- ~Sayu
-*/
-
-//Delays the mob's next action by num deciseconds
-// eg: 10-3 = 7 deciseconds of delay
-// eg: 10*0.5 = 5 deciseconds of delay
-// DOES NOT EFFECT THE BASE 1 DECISECOND DELAY OF NEXT_CLICK
-
-/mob/proc/changeNext_move(num)
- next_move = world.time + ((num+next_move_adjust)*next_move_modifier)
-
-//Delays the mob's next click by num deciseconds
-// eg: 10-3 = 7 deciseconds of delay
-// eg: 10*0.5 = 5 deciseconds of delay
-// DOES NOT EFFECT THE BASE 1 DECISECOND DELAY OF NEXT_CLICK
-
-/mob/proc/changeNext_click(num)
- next_click = world.time + ((num+next_click_adjust)*next_click_modifier)
-
-
-/*
- Before anything else, defer these calls to a per-mobtype handler. This allows us to
- remove istype() spaghetti code, but requires the addition of other handler procs to simplify it.
-
- Alternately, you could hardcode every mob's variation in a flat ClickOn() proc; however,
- that's a lot of code duplication and is hard to maintain.
-
- Note that this proc can be overridden, and is in the case of screen objects.
-*/
-/atom/Click(location,control,params)
- usr.ClickOn(src, params)
-/atom/DblClick(location,control,params)
- usr.DblClickOn(src,params)
-
-/*
- Standard mob ClickOn()
- Handles exceptions: Buildmode, middle click, modified clicks, mech actions
-
- After that, mostly just check your state, check whether you're holding an item,
- check whether you're adjacent to the target, then pass off the click to whoever
- is recieving it.
- The most common are:
- * mob/UnarmedAttack(atom,adjacent) - used here only when adjacent, with no item in hand; in the case of humans, checks gloves
- * atom/attackby(item,user) - used only when adjacent
- * item/afterattack__legacy__attackchain(atom,user,adjacent,params) - used both ranged and adjacent
- * mob/RangedAttack(atom,params) - used only ranged, only used for tk and laser eyes but could be changed
-*/
-/mob/proc/ClickOn(atom/A, params)
- if(QDELETED(A))
- return
-
- if(client?.click_intercept)
- client.click_intercept.InterceptClickOn(src, params, A)
- return
-
- if(next_click > world.time)
- return
- changeNext_click(1)
-
- var/list/modifiers = params2list(params)
- var/dragged = modifiers["drag"]
- if(dragged && !modifiers[dragged])
- return
- if(IsFrozen(A) && !is_admin(usr))
- to_chat(usr, "Interacting with admin-frozen players is not permitted.")
- return
- if(modifiers["middle"] && modifiers["shift"] && modifiers["ctrl"])
- MiddleShiftControlClickOn(A)
- return
- if(modifiers["middle"] && modifiers["shift"])
- MiddleShiftClickOn(A)
- return
- if(modifiers["shift"] && modifiers["ctrl"])
- CtrlShiftClickOn(A)
- return
- if(modifiers["shift"] && modifiers["alt"])
- AltShiftClickOn(A)
- return
- if(modifiers["middle"])
- MiddleClickOn(A)
- return
- if(modifiers["shift"])
- ShiftClickOn(A)
- return
- if(modifiers["alt"]) // alt and alt-gr (rightalt)
- AltClickOn(A)
- return
- if(modifiers["ctrl"])
- CtrlClickOn(A)
- return
-
- if(incapacitated(ignore_restraints = 1, ignore_grab = 1))
- return
-
- face_atom(A)
-
- if(next_move > world.time) // in the year 2000...
- return
-
- if(!modifiers["catcher"] && A.IsObscured())
- return
-
- if(ismecha(loc))
- if(!locate(/turf) in list(A,A.loc)) // Prevents inventory from being drilled
- return
- var/obj/mecha/M = loc
- return M.click_action(A, src, params)
-
- if(restrained())
- RestrainedClickOn(A)
- return
-
- if(in_throw_mode)
- throw_item(A)
- return
-
- if(isLivingSSD(A))
- if(client && client.send_ssd_warning(A))
- return
-
- var/obj/item/W = get_active_hand()
-
- if(W == A)
- if(W.new_attack_chain)
- W.activate_self(src)
- else
- W.attack_self__legacy__attackchain(src)
- if(hand)
- update_inv_l_hand()
- else
- update_inv_r_hand()
- return
-
- // operate three levels deep here (item in backpack in src; item in box in backpack in src, not any deeper)
- if(A in direct_access())
- if(W)
- W.melee_attack_chain(src, A, params)
- else
- if(ismob(A))
- changeNext_move(CLICK_CD_MELEE)
- UnarmedAttack(A, 1)
- return
-
- if(!isturf(loc)) // This is going to stop you from telekinesing from inside a closet, but I don't shed many tears for that
- return TRUE
-
- if(can_reach(A, W))
- if(W)
- W.melee_attack_chain(src, A, params)
- else
- if(ismob(A))
- changeNext_move(CLICK_CD_MELEE)
- UnarmedAttack(A, 1)
- else
- if(W)
- if(W.new_attack_chain)
- A.base_ranged_item_interaction(src, W, params)
- else
- W.afterattack__legacy__attackchain(A, src, 0, params) // 0: not Adjacent
- else
- RangedAttack(A, params)
-
-
-/**
- * A backwards depth-limited breadth-first-search to see if the target is
- * logically "in" anything adjacent to us.
- */
-/atom/movable/proc/can_reach(atom/ultimate_target, obj/item/tool, view_only = FALSE) //This might break mod storage. If it does, we hardcode mods / funny bag in here
- var/list/direct_access = direct_access()
- var/depth = 1 + (view_only ? STORAGE_VIEW_DEPTH : INVENTORY_DEPTH)
-
- var/list/closed = list()
- var/list/checking = list(ultimate_target)
-
- while(length(checking) && depth > 0)
- var/list/next = list()
- --depth
-
- for(var/atom/target in checking) // will filter out nulls
- if(closed[target] || isarea(target)) // avoid infinity situations
- continue
-
- if(isturf(target) || isturf(target.loc) || (target in direct_access) || !(target.IsObscured()) || istype(target.loc, /obj/item/storage)) //Directly accessible atoms
- if(target.Adjacent(src) || (tool && CheckToolReach(src, target, tool.reach))) //Adjacent or reaching attacks
- return TRUE
-
- closed[target] = TRUE
-
- if(!target.loc)
- continue
-
- if(istype(target.loc, /obj/item/storage))
- next += target.loc
-
- checking = next
- return FALSE
-
-/atom/movable/proc/direct_access()
- return list(src, loc)
-
-/mob/direct_access(atom/target)
- return ..() + contents
-
-/mob/living/direct_access(atom/target)
- return ..() + get_contents()
-
-/proc/CheckToolReach(atom/movable/here, atom/movable/there, reach)
- if(!here || !there)
- return FALSE
- switch(reach)
- if(0, 1)
- return FALSE //here.Adjacent(there)
- if(2 to INFINITY)
- var/obj/dummy = new(get_turf(here))
- dummy.pass_flags |= PASSTABLE
- dummy.invisibility = 120
- for(var/i in 1 to reach) //Limit it to that many tries
- var/turf/T = get_step(dummy, get_dir(dummy, there))
- if(dummy.can_reach(there))
- qdel(dummy)
- return TRUE
- if(!dummy.Move(T)) //we're blocked!
- qdel(dummy)
- return FALSE
- qdel(dummy)
-
-/// Can this mob use keybinded click actions? (Altclick, Ctrlclick, ect)
-/mob/proc/can_use_clickbinds()
- return TRUE
-
-//Is the atom obscured by a PREVENT_CLICK_UNDER_1 object above it
-/atom/proc/IsObscured()
- if(!isturf(loc)) //This only makes sense for things directly on turfs for now
- return FALSE
- var/turf/T = get_turf_pixel(src)
- if(!T)
- return FALSE
- for(var/atom/movable/AM in T)
- if(AM.flags & PREVENT_CLICK_UNDER && AM.density && AM.layer > layer)
- return TRUE
- return FALSE
-
-/turf/IsObscured()
- for(var/atom/movable/AM in src)
- if(AM.flags & PREVENT_CLICK_UNDER && AM.density)
- return TRUE
- return FALSE
-
-// Default behavior: ignore double clicks, consider them normal clicks instead
-/mob/proc/DblClickOn(atom/A, params)
- return
-
-/*
- Translates into attack_hand, etc.
-
- Note: proximity_flag here is used to distinguish between normal usage (flag=1),
- and usage when clicking on things telekinetically (flag=0). This proc will
- not be called at ranged except with telekinesis.
-
- proximity_flag is not currently passed to attack_hand, and is instead used
- in human click code to allow glove touches only at melee range.
-*/
-/mob/proc/UnarmedAttack(atom/A, proximity_flag)
- if(ismob(A))
- changeNext_move(CLICK_CD_MELEE)
- return
-
-/*
- Ranged unarmed attack:
-
- This currently is just a default for all mobs, involving
- laser eyes and telekinesis. You could easily add exceptions
- for things like ranged glove touches, spitting alien acid/neurotoxin,
- animals lunging, etc.
-*/
-/mob/proc/RangedAttack(atom/A, params)
- if(SEND_SIGNAL(src, COMSIG_MOB_ATTACK_RANGED, A, params) & COMPONENT_CANCEL_ATTACK_CHAIN)
- return TRUE
- if(SEND_SIGNAL(A, COMSIG_ATOM_RANGED_ATTACKED, src) & COMPONENT_CANCEL_ATTACK_CHAIN)
- return TRUE
-
-/*
- Restrained ClickOn
- Used when you are handcuffed and click things.
- Not currently used by anything but could easily be.
-*/
-/mob/proc/RestrainedClickOn(atom/A)
- return
-
-/*
- Middle click
- Only used for swapping hands
-*/
-/mob/proc/MiddleClickOn(atom/A)
- pointed(A)
- return
-
-// See click_override.dm
-/mob/living/MiddleClickOn(atom/A)
- . = SEND_SIGNAL(src, COMSIG_MOB_MIDDLECLICKON, A, src)
- if(. & COMSIG_MOB_CANCEL_CLICKON)
- return
- if(middleClickOverride)
- middleClickOverride.onClick(A, src)
- else
- ..()
-
-
-/*
- Middle shift-click
- Makes the mob face the direction of the clicked thing
-*/
-/mob/proc/MiddleShiftClickOn(atom/A)
- return
-
-/mob/living/MiddleShiftClickOn(atom/A)
- if(incapacitated())
- return
- var/face_dir = get_cardinal_dir(src, A)
- if(!face_dir || forced_look == face_dir || A == src)
- clear_forced_look()
- return
- set_forced_look(A, FALSE)
-
-/*
- Middle shift-control-click
- Makes the mob constantly face the object (until it's out of sight)
-*/
-/mob/proc/MiddleShiftControlClickOn(atom/A)
- return
-
-/mob/living/MiddleShiftControlClickOn(atom/A)
- if(incapacitated())
- return
- var/face_uid = A.UID()
- if(forced_look == face_uid || A == src)
- clear_forced_look()
- return
- set_forced_look(A, TRUE)
-
-// In case of use break glass
-/*
-/atom/proc/MiddleClick(mob/M as mob)
- return
-*/
-
-/*
- Shift click
- For most mobs, examine.
- This is overridden in ai.dm
-*/
-/mob/proc/ShiftClickOn(atom/A)
- A.ShiftClick(src)
- return
-/atom/proc/ShiftClick(mob/user)
- if(user.client && get_turf(user.client.eye) == get_turf(user))
- user.examinate(src)
- return
-
-/*
- Ctrl click
- For most objects, pull
-*/
-/mob/proc/CtrlClickOn(atom/A)
- A.CtrlClick(src)
- return
-
-/atom/proc/CtrlClick(mob/user)
- SEND_SIGNAL(src, COMSIG_CLICK_CTRL, user)
- var/mob/living/ML = user
- if(istype(ML))
- ML.pulled(src)
-
-/*
- Alt click
-*/
-/mob/proc/AltClickOn(atom/A)
- A.AltClick(src)
- return
-
-// See click_override.dm
-/mob/living/AltClickOn(atom/A)
- . = SEND_SIGNAL(src, COMSIG_MOB_ALTCLICKON, A, src)
- if(. & COMSIG_MOB_CANCEL_CLICKON)
- return
- if(middleClickOverride && middleClickOverride.onClick(A, src))
- return
- ..()
-
-/atom/proc/AltClick(mob/user)
- if(SEND_SIGNAL(src, COMSIG_CLICK_ALT, user) & COMPONENT_CANCEL_ALTCLICK)
- return
- var/turf/T = get_turf(src)
- if(T && (isturf(loc) || isturf(src)) && user.TurfAdjacent(T))
- user.set_listed_turf(T)
-
-/// Use this instead of [/mob/proc/AltClickOn] where you only want turf content listing without additional atom alt-click interaction
-/atom/proc/AltClickNoInteract(mob/user, atom/A)
- var/turf/T = get_turf(A)
- if(T && user.TurfAdjacent(T))
- user.set_listed_turf(T)
-
-/mob/proc/TurfAdjacent(turf/T)
- return T.Adjacent(src)
-
-/*
- Control+Shift/Alt+Shift click
- Unused except for AI
-*/
-/mob/proc/CtrlShiftClickOn(atom/A)
- A.CtrlShiftClick(src)
- return
-
-/atom/proc/CtrlShiftClick(mob/user)
- return
-
-/mob/proc/AltShiftClickOn(atom/A)
- A.AltShiftClick(src)
- return
-
-/mob/proc/ShiftMiddleClickOn(atom/A)
- return
-
-/atom/proc/AltShiftClick(mob/user)
- return
-
-/*
- Misc helpers
-
- Laser Eyes: as the name implies, handles this since nothing else does currently
- face_atom: turns the mob towards what you clicked on
-*/
-/mob/proc/LaserEyes(atom/A)
- return
-
-/mob/living/LaserEyes(atom/A)
- changeNext_move(CLICK_CD_RANGE)
- var/turf/T = get_turf(src)
- var/turf/U = get_turf(A)
-
- var/obj/item/projectile/beam/LE = new /obj/item/projectile/beam(loc)
- LE.icon = 'icons/effects/genetics.dmi'
- LE.icon_state = "eyelasers"
- playsound(usr.loc, 'sound/weapons/taser2.ogg', 75, 1)
-
- LE.firer = src
- LE.firer_source_atom = src
- LE.def_zone = ran_zone(zone_selected)
- LE.original = A
- LE.current = T
- LE.yo = U.y - T.y
- LE.xo = U.x - T.x
- LE.fire()
-
-// Simple helper to face what you clicked on, in case it should be needed in more than one place
-/mob/proc/face_atom(atom/A)
- if(stat || buckled || !A || !x || !y || !A.x || !A.y) return
- var/dx = A.x - x
- var/dy = A.y - y
- if(!dx && !dy) return
-
- var/direction
- if(abs(dx) < abs(dy))
- if(dy > 0) direction = NORTH
- else direction = SOUTH
- else
- if(dx > 0) direction = EAST
- else direction = WEST
- dir = direction
-
-/atom/movable/screen/click_catcher
- icon = 'icons/mob/screen_gen.dmi'
- icon_state = "catcher"
- plane = CLICKCATCHER_PLANE
- mouse_opacity = MOUSE_OPACITY_OPAQUE
- screen_loc = "CENTER"
-
-/atom/movable/screen/click_catcher/MouseEntered(location, control, params)
- return
-
-/atom/movable/screen/click_catcher/MouseExited(location, control, params)
- return
-
-#define MAX_SAFE_BYOND_ICON_SCALE_TILES (MAX_SAFE_BYOND_ICON_SCALE_PX / world.icon_size)
-#define MAX_SAFE_BYOND_ICON_SCALE_PX (33 * 32) //Not using world.icon_size on purpose.
-
-/atom/movable/screen/click_catcher/proc/UpdateGreed(view_size_x = 15, view_size_y = 15)
- var/icon/newicon = icon('icons/mob/screen_gen.dmi', "catcher")
- var/ox = min(MAX_SAFE_BYOND_ICON_SCALE_TILES, view_size_x)
- var/oy = min(MAX_SAFE_BYOND_ICON_SCALE_TILES, view_size_y)
- var/px = view_size_x * world.icon_size
- var/py = view_size_y * world.icon_size
- var/sx = min(MAX_SAFE_BYOND_ICON_SCALE_PX, px)
- var/sy = min(MAX_SAFE_BYOND_ICON_SCALE_PX, py)
- newicon.Scale(sx, sy)
- icon = newicon
- screen_loc = "CENTER-[(ox-1)*0.5],CENTER-[(oy-1)*0.5]"
- var/matrix/M = new
- M.Scale(px/sx, py/sy)
- transform = M
-
-/atom/movable/screen/click_catcher/Click(location, control, params)
- var/list/modifiers = params2list(params)
- if(modifiers["middle"] && iscarbon(usr))
- var/mob/living/carbon/C = usr
- C.swap_hand()
- else
- var/turf/click_turf = parse_caught_click_modifiers(modifiers, get_turf(usr.client ? usr.client.eye : usr), usr.client)
- if(click_turf)
- modifiers["catcher"] = TRUE
- click_turf.Click(location, control, list2params(modifiers))
- . = 1
-
-#undef MAX_SAFE_BYOND_ICON_SCALE_TILES
-#undef MAX_SAFE_BYOND_ICON_SCALE_PX
diff --git a/code/_onclick/click_override.dm b/code/_onclick/click_override.dm
deleted file mode 100644
index 8a377d6706b68..0000000000000
--- a/code/_onclick/click_override.dm
+++ /dev/null
@@ -1,129 +0,0 @@
-/*
- Click Overrides
-
- These are overrides for a living mob's middle and alt clicks.
- If the mob in question has their middleClickOverride var set to one of these datums, when they middle or alt click the onClick proc for the datum their clickOverride var is
- set equal to will be called.
- See click.dm 251 and 196.
-
- If you have any questions, contact me on the Paradise forums.
- - DaveTheHeacrab
- */
-
-/datum/middle_click_override/
-
-/datum/middle_click_override/proc/onClick(atom/A, mob/living/user)
- user.middleClickOverride = null
- return TRUE
- /* Note, when making a new click override it is ABSOLUTELY VITAL that you set the source's clickOverride to null at some point if you don't want them to be stuck with it forever.
- Calling the super will do this for you automatically, but if you want a click override to NOT clear itself after the first click, you must do it at some other point in the code*/
-
-/obj/item/badmin_book/
- name = "old book"
- desc = "An old, leather bound tome."
- icon = 'icons/obj/library.dmi'
- icon_state = "book"
- var/datum/middle_click_override/clickBehavior = new /datum/middle_click_override/badmin_clicker
-
-/obj/item/badmin_book/attack_self__legacy__attackchain(mob/living/user as mob)
- if(user.middleClickOverride)
- to_chat(user, "You try to draw power from [src], but you cannot hold the power at this time!")
- return
- user.middleClickOverride = clickBehavior
- to_chat(user, "You draw a bit of power from [src], you can use middle click or alt click to release the power!")
-
-/datum/middle_click_override/badmin_clicker
- var/summon_path = /obj/item/food/cookie
-
-/datum/middle_click_override/badmin_clicker/onClick(atom/A, mob/living/user)
- var/atom/movable/newObject = new summon_path
- newObject.loc = get_turf(A)
- to_chat(user, "You release the power you had stored up, summoning \a [newObject.name]!")
- usr.loc.visible_message("[user] waves [user.p_their()] hand and summons \a [newObject.name]!")
- ..()
-
-/datum/middle_click_override/shock_implant
-
-/datum/middle_click_override/shock_implant/onClick(atom/A, mob/living/carbon/human/user)
- if(user.incapacitated() || A == user)
- return FALSE
- var/obj/item/bio_chip/shock/P = locate() in user
- if(!P)
- return
- if(!COOLDOWN_FINISHED(P, last_shocked))
- to_chat(user, "The powerchip is still recharging.")
- return FALSE
- var/turf/T = get_turf(user)
- var/obj/structure/cable/C = locate() in T
- if(!P.unlimited_power)
- if(!C || !istype(C))
- to_chat(user, "There is no cable here to power the bio-chip.")
- return FALSE
- var/turf/target_turf = get_turf(A)
- if(get_dist(T, target_turf) > P.shock_range)
- to_chat(user, "The target is too far away.")
- return FALSE
- target_turf.hotspot_expose(2000, 1)
- playsound(user.loc, 'sound/effects/eleczap.ogg', 40, 1)
-
- var/atom/beam_from = user
- var/atom/target_atom = A
-
- for(var/i in 0 to 2) //3 attempts. Shocks at the clicked source, tries to find a mob in 1 tile, then choses a random tile 1 away to try again. As such, can only hit a mob 2 tiles away from the click
- beam_from.Beam(target_atom, icon_state = "lightning[rand(1, 12)]", icon = 'icons/effects/effects.dmi', time = 6)
- if(isliving(target_atom))
- var/mob/living/L = target_atom
- var/powergrid = C.get_available_power() //We want available power, so the station being conservative doesn't mess with the power biochip / dark bundle users
- if(user.a_intent == INTENT_DISARM || user.a_intent == INTENT_HELP)
- add_attack_logs(user, L, "shocked with power bio-chip.")
- L.apply_damage(60, STAMINA)
- L.Jitter(10 SECONDS)
- var/atom/throw_target = get_edge_target_turf(user, get_dir(user, get_step_away(L, user)))
- if(ishuman(L))
- var/mob/living/carbon/human/H = L
- if(H.gloves && H.gloves.siemens_coefficient == 0) //No throwing with insulated gloves (you still get stamina however)
- break
- L.throw_at(throw_target, powergrid / (150 KW), powergrid / (300 KW)) //150 kW in grid throws 1 tile, 300 throws 2, etc.
-
- else
- add_attack_logs(user, L, "electrocuted with[P.unlimited_power ? " unlimited" : null] power bio-chip")
- if(P.unlimited_power)
- L.electrocute_act(1000, P, flags = SHOCK_NOGLOVES) //Just kill them
- else
- electrocute_mob(L, C.powernet, P)
- break
- var/list/next_shocked = list()
- for(var/mob/M in range(1, target_atom)) //Try to jump to a mob first
- if(M == user || isobserver(M))
- continue
- next_shocked.Add(M)
- break //Break this so it gets the closest, thank you
- if(!length(next_shocked)) //No mob? Random bullshit go, try to get closer to a mob with luck
- for(var/atom/movable/AM in orange(1, target_atom))
- if(AM == user || iseffect(AM) || isobserver(AM))
- continue
- next_shocked.Add(AM)
- beam_from = target_atom
- target_atom = pick(next_shocked)
- A = target_atom
- next_shocked.Cut()
-
- COOLDOWN_START(P, last_shocked, P.shock_delay)
- return TRUE
-
-/**
- * # Callback invoker middle click override datum
- *
- * Middle click override which accepts a callback as an arugment in the `New()` proc.
- * When the living mob that has this datum middle-clicks or alt-clicks on something, the callback will be invoked.
- */
-/datum/middle_click_override/callback_invoker
- var/datum/callback/callback
-
-/datum/middle_click_override/callback_invoker/New(datum/callback/_callback)
- . = ..()
- callback = _callback
-
-/datum/middle_click_override/callback_invoker/onClick(atom/A, mob/living/user)
- if(callback.Invoke(user, A))
- return TRUE
diff --git a/code/_onclick/cyborg.dm b/code/_onclick/cyborg.dm
deleted file mode 100644
index 7e5da141d5a10..0000000000000
--- a/code/_onclick/cyborg.dm
+++ /dev/null
@@ -1,213 +0,0 @@
-/*
- Cyborg ClickOn()
-
- Cyborgs have no range restriction on attack_robot(), because it is basically an AI click.
- However, they do have a range restriction on item use, so they cannot do without the
- adjacency code.
-*/
-
-/mob/living/silicon/robot/ClickOn(atom/A, params)
- if(client?.click_intercept)
- client.click_intercept.InterceptClickOn(src, params, A)
- return
-
- if(next_click > world.time)
- return
- changeNext_click(1)
-
- if(is_ventcrawling(src)) // To stop drones interacting with anything while ventcrawling
- return
- if(stat == DEAD || lockcharge || IsWeakened() || IsStunned() || IsParalyzed() || low_power_mode)
- return
-
- var/list/modifiers = params2list(params)
- if(modifiers["shift"] && modifiers["ctrl"])
- CtrlShiftClickOn(A)
- return
- if(modifiers["shift"] && modifiers["alt"])
- AltShiftClickOn(A)
- return
- if(modifiers["middle"] && modifiers["ctrl"])
- CtrlMiddleClickOn(A)
- return
- if(modifiers["shift"] && modifiers["middle"])
- ShiftMiddleClickOn(A)
- return
- if(modifiers["middle"])
- MiddleClickOn(A)
- return
- if(modifiers["shift"])
- ShiftClickOn(A)
- return
- if(modifiers["alt"]) // alt and alt-gr (rightalt)
- AltClickOn(A)
- return
- if(modifiers["ctrl"])
- CtrlClickOn(A)
- return
-
- if(incapacitated())
- return
-
- if(next_move >= world.time)
- return
-
- face_atom(A) // change direction to face what you clicked on
-
- if(aiCamera)
- if(aiCamera.in_camera_mode)
- aiCamera.camera_mode_off()
- if(is_component_functioning("camera"))
- aiCamera.captureimage(A, usr)
- else
- to_chat(src, "Your camera isn't functional.")
- return
-
- /*
- cyborg restrained() currently does nothing
- if(restrained())
- RestrainedClickOn(A)
- return
- */
-
- var/obj/item/W = get_active_hand()
-
- // Cyborgs have no range-checking unless there is item use
- if(!W)
- A.add_hiddenprint(src)
- A.attack_robot(src)
- return
-
- // buckled cannot prevent machine interlinking but stops arm movement
- if(buckled)
- return
-
- if(W == A)
- if(W.new_attack_chain)
- W.activate_self(src)
- else
- W.attack_self__legacy__attackchain(src)
-
- return
-
- // cyborgs are prohibited from using storage items so we can I think safely remove (A.loc in contents)
- if(A == loc || (A in loc) || (A in contents))
- W.melee_attack_chain(src, A, params)
- return
-
- if(!isturf(loc))
- return
-
- // cyborgs are prohibited from using storage items so we can I think safely remove (A.loc && isturf(A.loc.loc))
- if(can_reach(A, W))
- W.melee_attack_chain(src, A, params)
- return
- W.afterattack__legacy__attackchain(A, src, 0, params)
- return
-
-/mob/living/silicon/robot/MiddleShiftControlClickOn(atom/A)
- return
-
-//Ctrl+Middle click cycles through modules
-/mob/living/silicon/robot/proc/CtrlMiddleClickOn(atom/A)
- cycle_modules()
- return
-
-//Middle click points
-/mob/living/silicon/robot/MiddleClickOn(atom/A)
- if(isdrone(src))
- // Drones cannot point.
- return
- pointed(A)
- return
-
-//Give cyborgs hotkey clicks without breaking existing uses of hotkey clicks
-// for non-doors/apcs
-/mob/living/silicon/robot/ShiftClickOn(atom/A)
- A.BorgShiftClick(src)
-
-/mob/living/silicon/robot/CtrlClickOn(atom/A)
- A.BorgCtrlClick(src)
-
-/mob/living/silicon/robot/AltClickOn(atom/A)
- A.BorgAltClick(src)
-
-/mob/living/silicon/robot/CtrlShiftClickOn(atom/A)
- A.BorgCtrlShiftClick(src)
-
-/mob/living/silicon/robot/AltShiftClickOn(atom/A)
- A.BorgAltShiftClick(src)
-
-/mob/living/silicon/robot/ShiftMiddleClickOn(atom/A)
- A.BorgShiftMiddleClick(src)
-
-/atom/proc/BorgShiftClick(mob/user)
- if(user.client && user.client.eye == user)
- user.examinate(src)
- return
-
-/atom/proc/BorgCtrlClick(mob/living/silicon/robot/user) //forward to human click if not overriden
- CtrlClick(user)
-
-/atom/proc/BorgAltClick(mob/living/silicon/robot/user)
- AltClick(user)
- return
-
-/atom/proc/BorgCtrlShiftClick(mob/user) // Examines
- if(user.client && user.client.eye == user)
- user.examinate(src)
- return
-
-/atom/proc/BorgAltShiftClick()
- return
-
-/atom/proc/BorgShiftMiddleClick()
- return
-
-// AIRLOCKS
-
-/obj/machinery/door/airlock/BorgShiftClick(mob/living/silicon/robot/user) // Opens and closes doors! Forwards to AI code.
- AIShiftClick(user)
-
-/obj/machinery/door/airlock/BorgCtrlClick(mob/living/silicon/robot/user) // Bolts doors. Forwards to AI code.
- AICtrlClick(user)
-
-/obj/machinery/door/airlock/BorgAltClick(mob/living/silicon/robot/user) // Eletrifies doors. Forwards to AI code.
- AIAltClick(user)
-
-/obj/machinery/door/airlock/BorgAltShiftClick(mob/living/silicon/robot/user) // Enables emergency override on doors! Forwards to AI code.
- AIAltShiftClick(user)
-
-/obj/machinery/door/airlock/BorgShiftMiddleClick(mob/living/silicon/robot/user) //Toggles door timing. Forwards to AI code.
- AIShiftMiddleClick(user)
-
-// APC
-
-/obj/machinery/power/apc/BorgCtrlClick(mob/living/silicon/robot/user) // turns off/on APCs. Forwards to AI code.
- AICtrlClick(user)
-
-// TURRETCONTROL
-
-/obj/machinery/turretid/BorgCtrlClick(mob/living/silicon/robot/user) //turret control on/off. Forwards to AI code.
- AICtrlClick(user)
-
-/obj/machinery/turretid/BorgAltClick(mob/living/silicon/robot/user) //turret lethal on/off. Forwards to AI code.
- AIAltClick(user)
-
-/*
- As with AI, these are not used in click code,
- because the code for robots is specific, not generic.
-
- If you would like to add advanced features to robot
- clicks, you can do so here, but you will have to
- change attack_robot() above to the proper function
-*/
-/mob/living/silicon/robot/UnarmedAttack(atom/A)
- A.attack_robot(src)
-
-/mob/living/silicon/robot/RangedAttack(atom/A, params)
- A.attack_robot(src)
-
-/atom/proc/attack_robot(mob/user)
- attack_ai(user)
- return
diff --git a/code/_onclick/hud/blob_overmind.dm b/code/_onclick/hud/blob_overmind.dm
deleted file mode 100644
index 94279aac05de1..0000000000000
--- a/code/_onclick/hud/blob_overmind.dm
+++ /dev/null
@@ -1,201 +0,0 @@
-/atom/movable/screen/blob
- icon = 'icons/mob/blob.dmi'
-
-/atom/movable/screen/blob/MouseEntered(location, control, params)
- . = ..()
- openToolTip(usr,src,params,title = name,content = desc, theme = "blob")
-
-/atom/movable/screen/blob/MouseExited()
- closeToolTip(usr)
- return ..()
-
-/atom/movable/screen/blob/blob_help
- icon_state = "ui_help"
- name = "Blob Help"
- desc = "Help on playing blob!"
-
-/atom/movable/screen/blob/blob_help/Click()
- if(isovermind(usr))
- var/mob/camera/blob/B = usr
- B.blob_help()
-
-/atom/movable/screen/blob/jump_to_node
- icon_state = "ui_tonode"
- name = "Jump to Node"
- desc = "Moves your camera to a selected blob node."
-
-/atom/movable/screen/blob/jump_to_node/Click()
- if(isovermind(usr))
- var/mob/camera/blob/B = usr
- B.jump_to_node()
-
-/atom/movable/screen/blob/jump_to_core
- icon_state = "ui_tocore"
- name = "Jump to Core"
- desc = "Moves your camera to your blob core."
-
-/atom/movable/screen/blob/jump_to_core/MouseEntered(location, control, params)
- if(hud && hud.mymob && isovermind(hud.mymob))
- name = initial(name)
- desc = initial(desc)
- return ..()
-
-/atom/movable/screen/blob/jump_to_core/Click()
- if(isovermind(usr))
- var/mob/camera/blob/B = usr
- B.transport_core()
-
-/atom/movable/screen/blob/blobbernaut
- icon_state = "ui_blobbernaut"
- name = "Produce Blobbernaut (60)"
- desc = "Produces a strong, intelligent blobbernaut from a factory blob for 60 resources. The factory blob will be destroyed in the process."
-
-/atom/movable/screen/blob/blobbernaut/Click()
- if(isovermind(usr))
- var/mob/camera/blob/B = usr
- B.create_blobbernaut()
-
-/atom/movable/screen/blob/storage_blob
- icon_state = "ui_storage"
- name = "Produce Storage Blob (40)"
- desc = "Produces a storage blob for 40 resources. Storage blobs will raise your max resource cap by 50."
-
-/atom/movable/screen/blob/storage_blob/Click()
- if(isovermind(usr))
- var/mob/camera/blob/B = usr
- B.create_storage()
-
-/atom/movable/screen/blob/resource_blob
- icon_state = "ui_resource"
- name = "Produce Resource Blob (40)"
- desc = "Produces a resource blob for 40 resources. Resource blobs will give you resources every few seconds."
-
-/atom/movable/screen/blob/resource_blob/Click()
- if(isovermind(usr))
- var/mob/camera/blob/B = usr
- B.create_resource()
-
-/atom/movable/screen/blob/node_blob
- icon_state = "ui_node"
- name = "Produce Node Blob (60)"
- desc = "Produces a node blob for 60 resources. Node blobs will expand and activate nearby resource and factory blobs."
-
-/atom/movable/screen/blob/node_blob/Click()
- if(isovermind(usr))
- var/mob/camera/blob/B = usr
- B.create_node()
-
-/atom/movable/screen/blob/factory_blob
- icon_state = "ui_factory"
- name = "Produce Factory Blob (60)"
- desc = "Produces a factory blob for 60 resources. Factory blobs will produce spores every few seconds."
-
-/atom/movable/screen/blob/factory_blob/Click()
- if(isovermind(usr))
- var/mob/camera/blob/B = usr
- B.create_factory()
-
-/atom/movable/screen/blob/readapt_chemical
- icon_state = "ui_chemswap"
- name = "Readapt Chemical (50)"
- desc = "Randomly rerolls your chemical for 50 resources."
-
-/atom/movable/screen/blob/readapt_chemical/MouseEntered(location, control, params)
- if(hud && hud.mymob && isovermind(hud.mymob))
- name = initial(name)
- desc = initial(desc)
- return ..()
-
-/atom/movable/screen/blob/readapt_chemical/Click()
- if(isovermind(usr))
- var/mob/camera/blob/B = usr
- B.chemical_reroll()
-
-/atom/movable/screen/blob/relocate_core
- icon_state = "ui_swap"
- name = "Relocate Core (80)"
- desc = "Swaps a node and your core for 80 resources."
-
-/atom/movable/screen/blob/relocate_core/Click()
- if(isovermind(usr))
- var/mob/camera/blob/B = usr
- B.relocate_core()
-
-/atom/movable/screen/blob/split
- icon_state = "ui_split"
- name = "Split consciousness (100)"
- desc = "Creates another Blob Overmind at the targeted node. One use only. Offspring are unable to use this ability."
-
-/atom/movable/screen/blob/split/Click()
- if(isovermind(usr))
- var/mob/camera/blob/B = usr
- B.split_consciousness()
- if(B.split_used) // Destroys split proc if the split is succesfully used
- qdel(src)
-
-/datum/hud/blob_overmind/New(mob/user)
- ..()
- var/atom/movable/screen/using
-
- blobpwrdisplay = new /atom/movable/screen()
- blobpwrdisplay.name = "blob power"
- blobpwrdisplay.icon_state = "block"
- blobpwrdisplay.screen_loc = ui_health
- blobpwrdisplay.mouse_opacity = MOUSE_OPACITY_TRANSPARENT
- blobpwrdisplay.layer = ABOVE_HUD_LAYER
- blobpwrdisplay.plane = ABOVE_HUD_PLANE
- static_inventory += blobpwrdisplay
-
- blobhealthdisplay = new /atom/movable/screen()
- blobhealthdisplay.name = "blob health"
- blobhealthdisplay.icon_state = "block"
- blobhealthdisplay.screen_loc = ui_internal
- static_inventory += blobhealthdisplay
-
- using = new /atom/movable/screen/blob/blob_help()
- using.screen_loc = "WEST:6,NORTH:-3"
- static_inventory += using
-
- using = new /atom/movable/screen/blob/jump_to_node()
- using.screen_loc = ui_inventory
- static_inventory += using
-
- using = new /atom/movable/screen/blob/jump_to_core()
- using.screen_loc = ui_zonesel
- using.hud = src
- static_inventory += using
-
- using = new /atom/movable/screen/blob/blobbernaut()
- using.screen_loc = ui_id
- static_inventory += using
-
- using = new /atom/movable/screen/blob/storage_blob()
- using.screen_loc = ui_belt
- static_inventory += using
-
- using = new /atom/movable/screen/blob/resource_blob()
- using.screen_loc = ui_back
- static_inventory += using
-
- using = new /atom/movable/screen/blob/node_blob()
- using.screen_loc = using.screen_loc = ui_rhand
- static_inventory += using
-
- using = new /atom/movable/screen/blob/factory_blob()
- using.screen_loc = using.screen_loc = ui_lhand
- static_inventory += using
-
- using = new /atom/movable/screen/blob/readapt_chemical()
- using.screen_loc = ui_storage1
- using.hud = src
- static_inventory += using
-
- using = new /atom/movable/screen/blob/relocate_core()
- using.screen_loc = ui_storage2
- static_inventory += using
-
- var/mob/camera/blob/B = user
- if(!B.is_offspring) // Checks if the blob is an offspring, to not create split button if it is
- using = new /atom/movable/screen/blob/split()
- using.screen_loc = ui_acti
- static_inventory += using
diff --git a/code/_onclick/hud/guardian_hud.dm b/code/_onclick/hud/guardian_hud.dm
deleted file mode 100644
index b460f02f50de7..0000000000000
--- a/code/_onclick/hud/guardian_hud.dm
+++ /dev/null
@@ -1,97 +0,0 @@
-/datum/hud/guardian/New(mob/owner)
- ..()
- var/atom/movable/screen/using
-
- guardianhealthdisplay = new /atom/movable/screen/healths/guardian()
- infodisplay += guardianhealthdisplay
-
- using = new /atom/movable/screen/act_intent/guardian()
- using.icon_state = mymob.a_intent
- static_inventory += using
- action_intent = using
-
- using = new /atom/movable/screen/guardian/manifest()
- using.screen_loc = ui_rhand
- static_inventory += using
-
- using = new /atom/movable/screen/guardian/recall()
- using.screen_loc = ui_lhand
- static_inventory += using
-
- using = new /atom/movable/screen/guardian/toggle_mode()
- using.screen_loc = ui_storage1
- static_inventory += using
-
- using = new /atom/movable/screen/guardian/toggle_light()
- using.screen_loc = ui_inventory
- static_inventory += using
-
- using = new /atom/movable/screen/guardian/communicate()
- using.screen_loc = ui_back
- static_inventory += using
-
-
-//HUD BUTTONS
-
-/atom/movable/screen/guardian
- icon = 'icons/mob/guardian.dmi'
- icon_state = "base"
-
-/atom/movable/screen/guardian/manifest
- icon_state = "manifest"
- name = "Manifest"
- desc = "Spring forth into battle!"
-
-/atom/movable/screen/guardian/manifest/Click()
- if(isguardian(usr))
- var/mob/living/simple_animal/hostile/guardian/G = usr
- var/summoner_loc = G.summoner.loc
- if(istype(summoner_loc, /obj/machinery/atmospherics))
- to_chat(G, "You can not manifest while in these pipes!")
- return
- if(istype(summoner_loc, /obj/structure/closet/cardboard/agent))
- to_chat(G, "You can not manifest while inside an active Stealth Implant!")
- return
- if(G.loc == G.summoner)
- G.Manifest()
-
-/atom/movable/screen/guardian/recall
- icon_state = "recall"
- name = "Recall"
- desc = "Return to your user."
-
-/atom/movable/screen/guardian/recall/Click()
- if(isguardian(usr))
- var/mob/living/simple_animal/hostile/guardian/G = usr
- G.Recall()
-
-/atom/movable/screen/guardian/toggle_mode
- icon_state = "toggle"
- name = "Toggle Mode"
- desc = "Switch between ability modes."
-
-/atom/movable/screen/guardian/toggle_mode/Click()
- if(isguardian(usr))
- var/mob/living/simple_animal/hostile/guardian/G = usr
- G.ToggleMode()
-
-/atom/movable/screen/guardian/communicate
- icon_state = "communicate"
- name = "Communicate"
- desc = "Communicate telepathically with your user."
-
-/atom/movable/screen/guardian/communicate/Click()
- if(isguardian(usr))
- var/mob/living/simple_animal/hostile/guardian/G = usr
- G.Communicate()
-
-
-/atom/movable/screen/guardian/toggle_light
- icon_state = "light"
- name = "Toggle Light"
- desc = "Glow like star dust."
-
-/atom/movable/screen/guardian/toggle_light/Click()
- if(isguardian(usr))
- var/mob/living/simple_animal/hostile/guardian/G = usr
- G.ToggleLight()
diff --git a/code/_onclick/hud/screen_objects.dm b/code/_onclick/hud/screen_objects.dm
deleted file mode 100644
index b681bff49259c..0000000000000
--- a/code/_onclick/hud/screen_objects.dm
+++ /dev/null
@@ -1,579 +0,0 @@
-/*
- Screen "objects"
- Todo: improve/re-implement
-
- Screen objects are only used for the hud and should not appear anywhere "in-game".
- They are not actually objects, which is counterintuitive to their name.
- They are used with the client/screen list and the screen_loc var.
- For more information, see the byond documentation on the screen_loc and screen vars.
-*/
-/atom/movable/screen
- name = ""
- icon = 'icons/mob/screen_gen.dmi'
- layer = HUD_LAYER
- plane = HUD_PLANE
- flags = NO_SCREENTIPS
- var/obj/master = null //A reference to the object in the slot. Grabs or items, generally.
- var/datum/hud/hud = null
- appearance_flags = NO_CLIENT_COLOR
- /**
- * Map name assigned to this object.
- * Automatically set by /client/proc/add_obj_to_map.
- */
- var/assigned_map
- /**
- * Mark this object as garbage-collectible after you clean the map
- * it was registered on.
- *
- * This could probably be changed to be a proc, for conditional removal.
- * But for now, this works.
- */
- var/del_on_map_removal = TRUE
-
-/atom/movable/screen/Destroy()
- master = null
- hud = null
- return ..()
-
-/atom/movable/screen/proc/component_click(atom/movable/screen/component_button/component, params)
- return
-
-/atom/movable/screen/text
- icon = null
- icon_state = null
- mouse_opacity = MOUSE_OPACITY_TRANSPARENT
- screen_loc = "CENTER-7,CENTER-7"
- maptext_height = 480
- maptext_width = 480
-
-/atom/movable/screen/close
- name = "close"
- layer = ABOVE_HUD_LAYER
- plane = ABOVE_HUD_PLANE
-
-/atom/movable/screen/close/Click()
- if(master)
- if(isstorage(master))
- var/obj/item/storage/S = master
- S.close(usr)
- return 1
-
-/atom/movable/screen/drop
- name = "drop"
- icon_state = "act_drop"
-
-/atom/movable/screen/drop/Click()
- usr.drop_item_v()
-
-/atom/movable/screen/grab
- name = "grab"
-
-/atom/movable/screen/grab/Click()
- var/obj/item/grab/G = master
- G.s_click(src)
- return 1
-
-/atom/movable/screen/grab/attack_hand()
- return
-
-/atom/movable/screen/grab/attackby__legacy__attackchain()
- return
-/atom/movable/screen/act_intent
- name = "intent"
- icon_state = "help"
- screen_loc = ui_acti
-
-/atom/movable/screen/act_intent/Click(location, control, params)
- if(ishuman(usr))
- var/_x = text2num(params2list(params)["icon-x"])
- var/_y = text2num(params2list(params)["icon-y"])
- if(_x<=16 && _y<=16)
- usr.a_intent_change(INTENT_HARM)
- else if(_x<=16 && _y>=17)
- usr.a_intent_change(INTENT_HELP)
- else if(_x>=17 && _y<=16)
- usr.a_intent_change(INTENT_GRAB)
- else if(_x>=17 && _y>=17)
- usr.a_intent_change(INTENT_DISARM)
- else
- usr.a_intent_change("right")
-
-/atom/movable/screen/act_intent/alien
- icon = 'icons/mob/screen_alien.dmi'
- screen_loc = ui_acti
-
-/atom/movable/screen/act_intent/robot
- icon = 'icons/mob/screen_robot.dmi'
- screen_loc = ui_borg_intents
-
-/atom/movable/screen/act_intent/robot/ai
- screen_loc = "SOUTH+1:6,EAST-1:32"
-
-/atom/movable/screen/mov_intent
- name = "run/walk toggle"
- icon_state = "running"
-
-/atom/movable/screen/act_intent/simple_animal
- icon = 'icons/mob/screen_simplemob.dmi'
- screen_loc = ui_acti
-
-/atom/movable/screen/act_intent/guardian
- icon = 'icons/mob/guardian.dmi'
- screen_loc = ui_acti
-
-/atom/movable/screen/mov_intent/Click()
- usr.toggle_move_intent()
-
-/atom/movable/screen/pull
- name = "stop pulling"
- icon_state = "pull"
-
-/atom/movable/screen/pull/Click()
- usr.stop_pulling()
-
-/atom/movable/screen/pull/update_icon_state()
- if(hud?.mymob?.pulling)
- icon_state = "pull"
- else
- icon_state = "pull0"
-
-/atom/movable/screen/resist
- name = "resist"
- icon = 'icons/mob/screen_midnight.dmi'
- icon_state = "act_resist"
-
-/atom/movable/screen/resist/Click()
- if(isliving(usr))
- var/mob/living/L = usr
- L.resist()
-
-/atom/movable/screen/throw_catch
- name = "throw/catch"
- icon = 'icons/mob/screen_midnight.dmi'
- icon_state = "act_throw_off"
-
-/atom/movable/screen/throw_catch/Click()
- if(iscarbon(usr))
- var/mob/living/carbon/C = usr
- C.toggle_throw_mode()
-
-/atom/movable/screen/storage
- name = "storage"
-
-/atom/movable/screen/storage/Click(location, control, params)
- if(world.time <= usr.next_move)
- return TRUE
- if(usr.incapacitated(ignore_restraints = TRUE))
- return TRUE
- if(ismecha(usr.loc)) // stops inventory actions in a mech
- return TRUE
- if(master)
- var/obj/item/I = usr.get_active_hand()
- if(I)
- master.attackby__legacy__attackchain(I, usr, params)
- return TRUE
-
-/atom/movable/screen/storage/proc/is_item_accessible(obj/item/I, mob/user)
- if(!user || !I)
- return FALSE
-
- var/storage_depth = I.storage_depth(user)
- if((I in user.loc) || (storage_depth != -1))
- return TRUE
-
- if(!isturf(user.loc))
- return FALSE
-
- var/storage_depth_turf = I.storage_depth_turf()
- if(isturf(I.loc) || (storage_depth_turf != -1))
- if(I.Adjacent(user))
- return TRUE
- return FALSE
-
-/atom/movable/screen/storage/MouseDrop_T(obj/item/I, mob/user)
- if(!user || !istype(I) || user.incapacitated(ignore_restraints = TRUE) || ismecha(user.loc) || !master)
- return
-
- var/obj/item/storage/S = master
- if(!S)
- return
-
- if(!is_item_accessible(I, user))
- log_game("[user.simple_info_line()] tried to abuse storage remote drag&drop with '[I]' at [atom_loc_line(I)] into '[S]' at [atom_loc_line(S)]")
- message_admins("[user.simple_info_line()] tried to abuse storage remote drag&drop with '[I]' at [atom_loc_line(I)] into '[S]' at [atom_loc_line(S)]")
- return
-
- if(I in S.contents) // If the item is already in the storage, move them to the end of the list
- if(S.contents[length(S.contents)] == I) // No point moving them at the end if they're already there!
- return
-
- var/list/new_contents = S.contents.Copy()
- if(S.display_contents_with_number)
- // Basically move all occurences of I to the end of the list.
- var/list/obj/item/to_append = list()
- for(var/obj/item/stored_item in S.contents)
- if(S.can_items_stack(stored_item, I))
- new_contents -= stored_item
- to_append += stored_item
-
- new_contents.Add(to_append)
- else
- new_contents -= I
- new_contents += I // oof
- S.contents = new_contents
-
- if(user.s_active == S)
- S.orient2hud(user)
- S.show_to(user)
- else // If it's not in the storage, try putting it inside
- S.attackby__legacy__attackchain(I, user)
- return TRUE
-
-/atom/movable/screen/zone_sel
- name = "damage zone"
- icon_state = "zone_sel"
- screen_loc = ui_zonesel
- var/overlay_file = 'icons/mob/zone_sel.dmi'
- var/selecting = "chest"
- var/static/list/hover_overlays_cache = list()
- var/hovering
-
-/atom/movable/screen/zone_sel/Click(location, control,params)
- if(isobserver(usr))
- return
-
- var/list/PL = params2list(params)
- var/icon_x = text2num(PL["icon-x"])
- var/icon_y = text2num(PL["icon-y"])
- var/choice = get_zone_at(icon_x, icon_y)
- if(!choice)
- return 1
-
- return set_selected_zone(choice, usr)
-
-/atom/movable/screen/zone_sel/MouseEntered(location, control, params)
- . = ..()
- MouseMove(location, control, params)
-
-/atom/movable/screen/zone_sel/MouseMove(location, control, params)
- if(isobserver(usr))
- return
-
- var/list/PL = params2list(params)
- var/icon_x = text2num(PL["icon-x"])
- var/icon_y = text2num(PL["icon-y"])
- var/choice = get_zone_at(icon_x, icon_y)
-
- if(hovering == choice)
- return
- cut_overlay(hover_overlays_cache[hovering])
- hovering = choice
-
- var/obj/effect/overlay/zone_sel/overlay_object = hover_overlays_cache[choice]
- if(!overlay_object)
- overlay_object = new
- overlay_object.icon_state = "[choice]"
- hover_overlays_cache[choice] = overlay_object
- add_overlay(overlay_object)
-
-
-/obj/effect/overlay/zone_sel
- icon = 'icons/mob/zone_sel.dmi'
- mouse_opacity = MOUSE_OPACITY_TRANSPARENT
- alpha = 128
- anchored = TRUE
- layer = ABOVE_HUD_LAYER
- plane = ABOVE_HUD_PLANE
-
-/atom/movable/screen/zone_sel/MouseExited(location, control, params)
- if(!isobserver(usr) && hovering)
- cut_overlay(hover_overlays_cache[hovering])
- hovering = null
- return ..()
-
-/atom/movable/screen/zone_sel/proc/get_zone_at(icon_x, icon_y)
- switch(icon_y)
- if(1 to 3) //Feet
- switch(icon_x)
- if(10 to 15)
- return "r_foot"
- if(17 to 22)
- return "l_foot"
- if(4 to 9) //Legs
- switch(icon_x)
- if(10 to 15)
- return "r_leg"
- if(17 to 22)
- return "l_leg"
- if(10 to 13) //Hands and groin
- switch(icon_x)
- if(8 to 11)
- return "r_hand"
- if(12 to 20)
- return "groin"
- if(21 to 24)
- return "l_hand"
- if(14 to 22) //Chest and arms to shoulders
- switch(icon_x)
- if(8 to 11)
- return "r_arm"
- if(12 to 20)
- return "chest"
- if(21 to 24)
- return "l_arm"
- if(23 to 30) //Head, but we need to check for eye or mouth
- if(icon_x in 12 to 20)
- switch(icon_y)
- if(23 to 24)
- if(icon_x in 15 to 17)
- return "mouth"
- if(26) //Eyeline, eyes are on 15 and 17
- if(icon_x in 14 to 18)
- return "eyes"
- if(25 to 27)
- if(icon_x in 15 to 17)
- return "eyes"
- return "head"
-
-/atom/movable/screen/zone_sel/proc/set_selected_zone(choice)
- if(!hud)
- return
- if(isobserver(hud.mymob))
- return
-
- if(choice != selecting)
- selecting = choice
- update_icon(UPDATE_OVERLAYS)
- return TRUE
-
-/atom/movable/screen/zone_sel/update_overlays()
- . = ..()
- var/image/sel = image(overlay_file, "[selecting]")
- sel.appearance_flags = RESET_COLOR
- . += sel
- hud.mymob.zone_selected = selecting
-
-/atom/movable/screen/zone_sel/alien
- icon = 'icons/mob/screen_alien.dmi'
- overlay_file = 'icons/mob/screen_alien.dmi'
-
-/atom/movable/screen/zone_sel/robot
- icon = 'icons/mob/screen_robot.dmi'
-
-/atom/movable/screen/craft
- name = "crafting menu"
- icon = 'icons/mob/screen_midnight.dmi'
- icon_state = "craft"
- screen_loc = ui_crafting
-
-/atom/movable/screen/craft/Click()
- if(!isliving(usr))
- return
- var/mob/living/M = usr
- M.OpenCraftingMenu()
-
-/atom/movable/screen/language_menu
- name = "language menu"
- icon = 'icons/mob/screen_midnight.dmi'
- icon_state = "talk_wheel"
- screen_loc = ui_language_menu
-
-/atom/movable/screen/language_menu/Click()
- var/mob/M = usr
- if(!istype(M))
- return
- M.check_languages()
-
-/atom/movable/screen/inventory
- var/slot_id //The indentifier for the slot. It has nothing to do with ID cards.
- var/list/object_overlays = list()
-
-/atom/movable/screen/inventory/MouseEntered()
- . = ..()
- add_overlays()
-
-/atom/movable/screen/inventory/MouseExited()
- ..()
- cut_overlay(object_overlays)
- object_overlays.Cut()
-
-/atom/movable/screen/inventory/proc/add_overlays()
- var/mob/user = hud?.mymob
-
- if(!user || user != usr)
- return
-
- if(!hud?.mymob || !slot_id || (slot_id & ITEM_SLOT_BOTH_HANDS))
- return
-
- var/obj/item/holding = user.get_active_hand()
-
- if(!holding || user.get_item_by_slot(slot_id))
- return
-
- var/image/item_overlay = image(holding)
- item_overlay.alpha = 92
-
- if(!user.can_equip(holding, slot_id, TRUE))
- item_overlay.color = "#ff0000"
- else
- item_overlay.color = "#00ff00"
-
- object_overlays += item_overlay
- add_overlay(object_overlays)
-
-/atom/movable/screen/inventory/MouseDrop(atom/over)
- cut_overlay(object_overlays)
- object_overlays.Cut()
- if(could_be_click_lag())
- Click()
- drag_start = 0
- return
- return ..()
-
-/atom/movable/screen/inventory/Click(location, control, params)
- // At this point in client Click() code we have passed the 1/10 sec check and little else
- // We don't even know if it's a middle click
- if(world.time <= usr.next_move)
- return 1
- if(usr.incapacitated())
- return 1
- if(ismecha(usr.loc)) // stops inventory actions in a mech
- return 1
-
- if(hud?.mymob && slot_id)
- var/obj/item/inv_item = hud.mymob.get_item_by_slot(slot_id)
- if(inv_item)
- return inv_item.Click(location, control, params)
-
- if(usr.attack_ui(slot_id))
- usr.update_inv_l_hand()
- usr.update_inv_r_hand()
- return TRUE
-
-/atom/movable/screen/inventory/hand
- var/image/active_overlay
- var/image/handcuff_overlay
- var/static/mutable_appearance/blocked_overlay = mutable_appearance('icons/mob/screen_gen.dmi', "blocked")
-
-/atom/movable/screen/inventory/hand/update_overlays()
- . = ..()
- if(!handcuff_overlay)
- var/state = (slot_id == ITEM_SLOT_RIGHT_HAND) ? "markus" : "gabrielle"
- handcuff_overlay = image("icon"='icons/mob/screen_gen.dmi', "icon_state"=state)
-
- if(hud && hud.mymob)
- if(iscarbon(hud.mymob))
- var/mob/living/carbon/C = hud.mymob
- if(C.handcuffed)
- . += handcuff_overlay
-
- var/obj/item/organ/external/hand = C.get_organ("[slot_id == ITEM_SLOT_LEFT_HAND ? "l" : "r"]_hand")
- if(!isalien(C) && (!hand || !hand.is_usable()))
- . += blocked_overlay
-
- if(slot_id == ITEM_SLOT_LEFT_HAND && hud.mymob.hand)
- . += "hand_active"
- else if(slot_id == ITEM_SLOT_RIGHT_HAND && !hud.mymob.hand)
- . += "hand_active"
-
-/atom/movable/screen/inventory/hand/Click()
- // At this point in client Click() code we have passed the 1/10 sec check and little else
- // We don't even know if it's a middle click
- if(world.time <= usr.next_move)
- return 1
- if(usr.incapacitated())
- return 1
- if(ismecha(usr.loc)) // stops inventory actions in a mech
- return 1
-
- if(ismob(usr))
- var/mob/M = usr
- switch(name)
- if("right hand", "r_hand")
- M.activate_hand(HAND_BOOL_RIGHT)
- if("left hand", "l_hand")
- M.activate_hand(HAND_BOOL_LEFT)
- return TRUE
-
-/atom/movable/screen/swap_hand
- name = "swap hand"
-
-/atom/movable/screen/swap_hand/Click()
- // At this point in client Click() code we have passed the 1/10 sec check and little else
- // We don't even know if it's a middle click
- if(world.time <= usr.next_move)
- return 1
-
- if(usr.incapacitated())
- return 1
-
- if(ismob(usr))
- var/mob/M = usr
- M.swap_hand()
- return 1
-
-/atom/movable/screen/healths
- name = "health"
- icon_state = "health0"
- screen_loc = ui_health
-
-/atom/movable/screen/healths/alien
- icon = 'icons/mob/screen_alien.dmi'
- screen_loc = ui_alien_health
-
-/atom/movable/screen/healths/bot
- icon = 'icons/mob/screen_bot.dmi'
- screen_loc = ui_borg_health
-
-/atom/movable/screen/healths/robot
- icon = 'icons/mob/screen_robot.dmi'
- screen_loc = ui_borg_health
-
-/atom/movable/screen/healths/corgi
- icon = 'icons/mob/screen_corgi.dmi'
-
-/atom/movable/screen/healths/slime
- icon = 'icons/mob/screen_slime.dmi'
- icon_state = "slime_health0"
- screen_loc = ui_slime_health
- mouse_opacity = MOUSE_OPACITY_TRANSPARENT
-
-/atom/movable/screen/healths/guardian
- name = "summoner health"
- icon = 'icons/mob/guardian.dmi'
- icon_state = "base"
- screen_loc = ui_health
- mouse_opacity = MOUSE_OPACITY_TRANSPARENT
-
-/atom/movable/screen/healthdoll
- name = "health doll"
- icon_state = "healthdoll_DEAD"
- screen_loc = ui_healthdoll
- var/list/cached_healthdoll_overlays = list() // List of icon states (strings) for overlays
-
-/atom/movable/screen/healthdoll/Click()
- if(ishuman(usr) && usr.stat != DEAD)
- var/mob/living/carbon/H = usr
- H.check_self_for_injuries()
-
-/atom/movable/screen/nutrition
- name = "nutrition"
- icon = 'icons/mob/screen_hunger.dmi'
- icon_state = null
- screen_loc = ui_nutrition
-
-/atom/movable/screen/component_button
- var/atom/movable/screen/parent
-
-/atom/movable/screen/component_button/Initialize(mapload, atom/movable/screen/new_parent)
- . = ..()
- parent = new_parent
-
-/atom/movable/screen/component_button/Click(params)
- if(parent)
- parent.component_click(src, params)
-
-/atom/movable/screen/healths/stamina
- icon_state = "stamina_0"
- screen_loc = ui_stamina
diff --git a/code/_onclick/item_attack.dm b/code/_onclick/item_attack.dm
deleted file mode 100644
index 625ea59cf738e..0000000000000
--- a/code/_onclick/item_attack.dm
+++ /dev/null
@@ -1,306 +0,0 @@
-/**
- * This is the proc that handles the order of an item_attack.
- *
- * The order of procs called is:
- * * [/atom/proc/base_item_interaction] on the target. If it returns ITEM_INTERACT_COMPLETE, the chain will be stopped.
- * If it returns ITEM_INTERACT_SKIP_TO_AFTER_ATTACK, all attack chain steps except after-attack will be skipped.
- * * [/obj/item/proc/pre_attack] on `src`. If this returns FINISH_ATTACK, the chain will be stopped.
- * * [/atom/proc/attack_by] on the target. If it returns FINISH_ATTACK, the chain will be stopped.
- * * [/obj/item/proc/after_attack]. The return value does not matter.
- *
- * Currently the return value of this proc is not checked anywhere, and is only used when short-circuiting the rest of the item attack.
- */
-/obj/item/proc/melee_attack_chain(mob/user, atom/target, params, proximity_flag = 1)
- // TODO: Look into whether proxy attackers are worth porting from /tg/: https://github.com/tgstation/tgstation/pull/83860
- var/list/modifiers = params2list(params)
-
- var/item_interact_result = target.base_item_interaction(user, src, modifiers)
- switch(item_interact_result)
- if(ITEM_INTERACT_COMPLETE)
- return
- if(ITEM_INTERACT_SKIP_TO_AFTER_ATTACK)
- __after_attack_core(user, target, params, proximity_flag)
- return
-
- // Attack phase
- if(pre_attack(target, user, params))
- return
-
- var/resolved = target.new_attack_chain \
- ? target.attack_by(src, user, params) \
- : target.attackby__legacy__attackchain(src, user, params)
-
- // We were asked to cancel the rest of the attack chain.
- if(resolved)
- return
-
- // At this point it means the attack was "successful", or at least
- // handled, in some way. This can mean nothing happened, this can mean the
- // target took damage, etc.
- __after_attack_core(user, target, params, proximity_flag)
-
-
-/// Called when the item is in the active hand, and clicked; alternately, there
-/// is an 'activate held object' verb or you can hit pagedown.
-/obj/item/proc/activate_self(mob/user)
- SHOULD_CALL_PARENT(TRUE)
-
- if(SEND_SIGNAL(src, COMSIG_ACTIVATE_SELF, user) & COMPONENT_CANCEL_ATTACK_CHAIN)
- return FINISH_ATTACK
-
-/**
- * Called on ourselves before we hit something. Return TRUE to cancel the remainder of the attack chain.
- *
- * Arguments:
- * * atom/A - The atom about to be hit
- * * mob/living/user - The mob doing the htting
- * * params - click params such as alt/shift etc
- *
- * See: [/obj/item/proc/melee_attack_chain]
- */
-/obj/item/proc/pre_attack(atom/A, mob/living/user, params)
- SHOULD_CALL_PARENT(TRUE)
-
- if(SEND_SIGNAL(src, COMSIG_PRE_ATTACK, A, user, params) & COMPONENT_CANCEL_ATTACK_CHAIN)
- return TRUE
-
- // TODO: Turn this into a component and have a sane implementation instead of extra-specific behavior in a core proc
- var/temperature = get_heat()
- if(temperature && A.reagents && !ismob(A) && !istype(A, /obj/item/clothing/mask/cigarette))
- var/reagent_temp = A.reagents.chem_temp
- var/time = (reagent_temp / 10) / (temperature / 1000)
- if(do_after_once(user, time, TRUE, user, TRUE, attempt_cancel_message = "You stop heating up [A]."))
- to_chat(user, "You heat [A] with [src].")
- A.reagents.temperature_reagents(temperature)
-
-/**
- * Called when mob `user` is hitting us with an item `attacking`.
- * Part of the [/obj/item/proc/melee_attack_chain].
- *
- * Handles calling [/atom/proc/attack] or [/obj/item/proc/attack_obj] as necessary.
- *
- * Arguments:
- * * obj/item/attacking_item - The item hitting this atom
- * * mob/user - The wielder of this item
- * * params - click params such as alt/shift etc
- *
- * Handles [COMSIG_ATTACK_BY] returning [COMPONENT_SKIP_AFTERATTACK].
- * Returns [FINISH_ATTACK] if the attack chain should stop here.
- */
-/atom/proc/attack_by(obj/item/attacking, mob/user, params)
- SHOULD_CALL_PARENT(TRUE)
-
- if(SEND_SIGNAL(src, COMSIG_ATTACK_BY, attacking, user, params) & COMPONENT_SKIP_AFTERATTACK)
- return FINISH_ATTACK
-
-/obj/attack_by(obj/item/attacking, mob/user, params)
- . = ..()
-
- if(.)
- return FINISH_ATTACK
-
- if(!can_be_hit)
- return
-
- return attacking.attack_obj(src, user, params)
-
-/mob/living/attack_by(obj/item/attacking, mob/living/user, params)
- if(..())
- return TRUE
- user.changeNext_move(CLICK_CD_MELEE)
- return attacking.attack(src, user, params)
-
-/**
- * Called when we are used by `user` to attack the living `target`.
- *
- * Returns `TRUE` if the rest of the attack chain should be cancelled. This may occur if the attack failed for some reason.
- * Returns `FALSE` if the attack was "successful" or "handled" in some way, and the rest of the attack chain should still fire.
- */
-/obj/item/proc/attack(mob/living/target, mob/living/user, params)
- SHOULD_CALL_PARENT(TRUE)
-
- var/signal_return = SEND_SIGNAL(src, COMSIG_ATTACK, target, user, params) \
- || SEND_SIGNAL(user, COMSIG_MOB_ITEM_ATTACK, target, user, params)
-
- if(signal_return & COMPONENT_CANCEL_ATTACK_CHAIN)
- return FINISH_ATTACK
-
- if(signal_return & COMPONENT_SKIP_ATTACK)
- return FALSE
-
- . = __attack_core(target, user)
-
- if(!target.new_attack_chain && .)
- return target.attacked_by__legacy__attackchain(src, user, /* def_zone */ null)
-
-/obj/item/proc/__after_attack_core(mob/user, atom/target, params, proximity_flag = 1)
- PRIVATE_PROC(TRUE)
-
- // TODO: `target` here should probably be another `!QDELETED` check.
- // Preserved for backwards compatibility, may be fixed post-migration.
- if(target && !QDELETED(src))
- if(new_attack_chain)
- after_attack(target, user, proximity_flag, params)
- else
- afterattack__legacy__attackchain(target, user, proximity_flag, params)
-
-/obj/item/proc/__attack_core(mob/living/target, mob/living/user)
- PRIVATE_PROC(TRUE)
-
- if(flags & (NOBLUDGEON))
- return FALSE
-
- // TODO: Migrate all of this to the proper objects so it's not clogging up a core proc and called at irrelevant times
- if((is_surgery_tool_by_behavior(src) || is_organ(src) || tool_behaviour) && user.a_intent == INTENT_HELP && on_operable_surface(target) && target != user)
- to_chat(user, "You don't want to harm the person you're trying to help!")
- return FALSE
-
- if(force && HAS_TRAIT(user, TRAIT_PACIFISM))
- to_chat(user, "You don't want to harm other living beings!")
- return FALSE
-
- if(!force)
- playsound(loc, 'sound/weapons/tap.ogg', get_clamped_volume(), TRUE, -1)
- else
- SEND_SIGNAL(target, COMSIG_ATTACK)
- add_attack_logs(user, target, "Attacked with [name] ([uppertext(user.a_intent)]) ([uppertext(damtype)])", (target.ckey && force > 0 && damtype != STAMINA) ? null : ATKLOG_ALMOSTALL)
- if(hitsound)
- playsound(loc, hitsound, get_clamped_volume(), TRUE, extrarange = stealthy_audio ? SILENCED_SOUND_EXTRARANGE : -1, falloff_distance = 0)
-
- target.lastattacker = user.real_name
- target.lastattackerckey = user.ckey
-
- user.do_attack_animation(target)
- add_fingerprint(user)
-
- return TRUE
-
-/// The equivalent of the standard version of [/obj/item/proc/attack] but for non mob targets.
-/obj/item/proc/attack_obj(obj/attacked_obj, mob/living/user, params)
- var/signal_return = SEND_SIGNAL(src, COMSIG_ATTACK_OBJ, attacked_obj, user) | SEND_SIGNAL(user, COMSIG_ATTACK_OBJ_LIVING, attacked_obj)
- if(signal_return & COMPONENT_SKIP_ATTACK)
- return TRUE
- if(signal_return & COMPONENT_CANCEL_ATTACK_CHAIN)
- return FALSE
- if(flags & NOBLUDGEON)
- return FALSE
-
- user.changeNext_move(CLICK_CD_MELEE)
- user.do_attack_animation(attacked_obj)
-
- if(attacked_obj.new_attack_chain)
- attacked_obj.attacked_by(src, user)
- else
- attacked_obj.attacked_by__legacy__attackchain(src, user)
-
-/**
- * Called *after* we have been attacked with the item `attacker` by `user`.
- *
- * Return value is ignored for purposes of the attack chain.
- */
-/atom/proc/attacked_by(obj/item/attacker, mob/living/user)
- return
-
-/obj/attacked_by(obj/item/attacker, mob/living/user)
- var/damage = attacker.force
- if(attacker.force)
- user.visible_message(
- "[user] has hit [src] with [attacker]!",
- "You hit [src] with [attacker]!",
- "You hear something being struck by a weapon!"
- )
-
- if(ishuman(user))
- var/mob/living/carbon/human/H = user
- damage += H.physiology.melee_bonus
- take_damage(damage, attacker.damtype, MELEE, 1)
-
-/mob/living/attacked_by(obj/item/attacker, mob/living/user, def_zone)
- send_item_attack_message(attacker, user)
- if(attacker.force)
- var/bonus_damage = 0
- if(ishuman(user))
- var/mob/living/carbon/human/H = user
- bonus_damage = H.physiology.melee_bonus
- apply_damage(attacker.force + bonus_damage, attacker.damtype, def_zone)
- if(attacker.damtype == BRUTE)
- if(prob(33))
- attacker.add_mob_blood(src)
- var/turf/location = get_turf(src)
- add_splatter_floor(location)
- if(get_dist(user, src) <= 1) //people with TK won't get smeared with blood
- user.add_mob_blood(src)
-
-/mob/living/simple_animal/attacked_by(obj/item/attacker, mob/living/user)
- if(!attacker.force)
- user.visible_message(
- "[user] gently taps [src] with [attacker].",
- "This weapon is ineffective, it does no damage!",
- "You hear a gentle tapping."
- )
-
- else if(attacker.force < force_threshold || attacker.damtype == STAMINA)
- visible_message(
- "[attacker] bounces harmlessly off of [src].",
- "[attacker] bounces harmlessly off of [src]!",
- "You hear something being struck by a weapon!"
- )
-
- else
- return ..()
-
-/**
- * Last proc in the [/obj/item/proc/melee_attack_chain].
- *
- * Arguments:
- * * atom/target - The thing that was hit
- * * mob/user - The mob doing the hitting
- * * proximity_flag - is 1 if this afterattack was called on something adjacent, in your square, or on your person.
- * * click_parameters - is the params string from byond [/atom/proc/Click] code, see that documentation.
- */
-/obj/item/proc/after_attack(atom/target, mob/user, proximity_flag, click_parameters)
- SHOULD_CALL_PARENT(TRUE)
-
- SEND_SIGNAL(src, COMSIG_AFTER_ATTACK, target, user, proximity_flag, click_parameters)
- SEND_SIGNAL(target, COMSIG_AFTER_ATTACKED_BY, src, user, proximity_flag, click_parameters)
-
-/obj/item/proc/get_clamped_volume()
- if(w_class)
- if(force)
- return clamp((force + w_class) * 4, 30, 100)// Add the item's force to its weight class and multiply by 4, then clamp the value between 30 and 100
- else
- return clamp(w_class * 6, 10, 100) // Multiply the item's weight class by 6, then clamp the value between 10 and 100
-
-/mob/living/proc/send_item_attack_message(obj/item/I, mob/living/user, hit_area)
- if(I.discrete)
- return
- var/message_verb = "attacked"
- if(I.attack_verb && length(I.attack_verb))
- message_verb = "[pick(I.attack_verb)]"
- else if(!I.force)
- return
- var/message_hit_area = ""
- if(hit_area)
- message_hit_area = " in the [hit_area]"
- var/attack_message = "[src] has been [message_verb][message_hit_area] with [I]."
- if(user in viewers(src, null))
- attack_message = "[user] has [message_verb] [src][message_hit_area] with [I]!"
- visible_message(
- "[attack_message]",
- "[attack_message]",
- "You hear someone being attacked with a weapon!"
- )
- return TRUE
-
-/// Used for signal registrars who wish to completely ignore all behavior
-/// in the attack chain from parent types. Should be used sparingly, as
-/// subtypes are meant to build on behavior from the parent type.
-/datum/proc/signal_cancel_activate_self(mob/user)
- return COMPONENT_CANCEL_ATTACK_CHAIN
-
-/// Used for signal registrars who wish to completely ignore all behavior
-/// in the attack chain from parent types calling `attack_by`. Should be
-/// used sparingly, as subtypes are meant to build on behavior from the parent
-/// type.
-/datum/proc/signal_cancel_attack_by(datum/source, obj/item/attacking, mob/user, params)
- return COMPONENT_SKIP_AFTERATTACK
diff --git a/code/_onclick/other_mobs.dm b/code/_onclick/other_mobs.dm
deleted file mode 100644
index c9f8fccfd1f70..0000000000000
--- a/code/_onclick/other_mobs.dm
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- Humans:
- Adds an exception for gloves, to allow special glove types like the ninja ones.
-
- Otherwise pretty standard.
-*/
-/mob/living/carbon/human/UnarmedAttack(atom/A, proximity)
- // Special glove functions:
- // If the gloves do anything, have them return 1 to stop
- // normal attack_hand() here.
- var/obj/item/clothing/gloves/G = gloves // not typecast specifically enough in defines
- if(proximity && istype(G) && G.Touch(A, 1))
- return
-
- if(HAS_TRAIT(src, TRAIT_HULK))
- if(proximity) //no telekinetic hulk attack
- if(A.attack_hulk(src))
- return
-
- if(buckled && isstructure(buckled))
- var/obj/structure/S = buckled
- if(S.prevents_buckled_mobs_attacking())
- return
-
- if(SEND_SIGNAL(A, COMSIG_HUMAN_MELEE_UNARMED_ATTACKBY, src) & COMPONENT_CANCEL_ATTACK_CHAIN)
- return
-
- A.attack_hand(src)
-
-/atom/proc/attack_hand(mob/user as mob)
- if(SEND_SIGNAL(src, COMSIG_ATOM_ATTACK_HAND, user) & COMPONENT_CANCEL_ATTACK_CHAIN)
- return TRUE
-
-/*
-/mob/living/carbon/human/RestrainedClickOn(atom/A) -- Handled by carbons
- return
-*/
-
-/mob/living/carbon/RestrainedClickOn(atom/A)
- return 0
-
-/mob/living/carbon/human/RangedAttack(atom/A, params)
- . = ..()
- if(.)
- return
- if(gloves)
- var/obj/item/clothing/gloves/G = gloves
- if(istype(G) && G.Touch(A, 0)) // for magic gloves
- return
-
- if(HAS_TRAIT(src, TRAIT_LASEREYES) && a_intent == INTENT_HARM)
- LaserEyes(A)
-
- if(HAS_TRAIT(src, TRAIT_TELEKINESIS) && telekinesis_range_check(src, A))
- A.attack_tk(src)
-
- if(isturf(A) && get_dist(src, A) <= 1)
- Move_Pulled(A)
-/*
- Animals & All Unspecified
-*/
-/mob/living/UnarmedAttack(atom/A)
- A.attack_animal(src)
-
-/mob/living/simple_animal/hostile/UnarmedAttack(atom/A)
- target = A
- AttackingTarget()
-
-/atom/proc/attack_animal(mob/user)
- return
-
-/mob/living/RestrainedClickOn(atom/A)
- return
-
-/*
- Aliens
- Defaults to same as monkey in most places
-*/
-/mob/living/carbon/alien/UnarmedAttack(atom/A)
- A.attack_alien(src)
-
-/atom/proc/attack_alien(mob/living/carbon/alien/user)
- attack_hand(user)
-
-/mob/living/carbon/alien/RestrainedClickOn(atom/A)
- return
-
-// Babby aliens
-/mob/living/carbon/alien/larva/UnarmedAttack(atom/A)
- A.attack_larva(src)
-
-/atom/proc/attack_larva(mob/user)
- return
-
-
-/*
- Slimes
- Nothing happening here
-*/
-/mob/living/simple_animal/slime/UnarmedAttack(atom/A)
- A.attack_slime(src)
-
-/atom/proc/attack_slime(mob/user)
- return
-
-/mob/living/simple_animal/slime/RestrainedClickOn(atom/A)
- return
-
-/*
- New Players:
- Have no reason to click on anything at all.
-*/
-/mob/new_player/ClickOn()
- return
-
-/mob/new_player/can_use_clickbinds()
- return FALSE
-
-// pAIs are not intended to interact with anything in the world
-/mob/living/silicon/pai/UnarmedAttack(atom/A)
- return
diff --git a/code/_onclick/telekinesis.dm b/code/_onclick/telekinesis.dm
deleted file mode 100644
index 90ef75e9d927f..0000000000000
--- a/code/_onclick/telekinesis.dm
+++ /dev/null
@@ -1,210 +0,0 @@
-/*
- Telekinesis
-
- This needs more thinking out, but I might as well.
-*/
-#define TK_MAXRANGE 15
-#define TK_COOLDOWN 1.5 SECONDS
-/*
- Telekinetic attack:
-
- By default, emulate the user's unarmed attack
-*/
-/atom/proc/attack_tk(mob/user)
- if(user.stat)
- return
- user.UnarmedAttack(src,0) // attack_hand, attack_paw, etc
- return
-
-/*
- Returns: True if the target is within the 15 tile range of telekinesis and on the same z-level, false otherwise.
-*/
-/proc/telekinesis_range_check(mob/living/carbon/human/user, atom/target)
- return (get_dist(user, target) <= TK_MAXRANGE && user.z == target.z)
-
-/*
- This is similar to item attack_self, but applies to anything
- that you can grab with a telekinetic grab.
-
- It is used for manipulating things at range, for example, opening and closing closets.
- There are not a lot of defaults at this time, add more where appropriate.
-*/
-/atom/proc/attack_self_tk(mob/user)
- return
-
-/obj/attack_tk(mob/user)
- if(user.stat)
- return
- if(anchored)
- ..()
- return
-
- var/obj/item/tk_grab/O = new(src)
- O.form_grab(user, src)
-
-/obj/item/attack_tk(mob/user)
- if(user.stat || !isturf(loc))
- return
- if(HAS_TRAIT(user, TRAIT_TELEKINESIS) && !user.get_active_hand()) // both should already be true to get here
- var/obj/item/tk_grab/O = new(src)
- O.form_grab(user, src)
- else
- warning("Strange attack_tk(): TK([user.dna?.GetSEState(GLOB.teleblock)]) empty hand([!user.get_active_hand()])")
-
-
-/mob/attack_tk(mob/user)
- return // needs more thinking about
-
-/*
- TK Grab Item (the workhorse of old TK)
-
- * If you have not grabbed something, do a normal tk attack
- * If you have something, throw it at the target. If it is already adjacent, do a normal attackby(, params)
- * If you click what you are holding, or attack_self(), do an attack_self_tk() on it.
- * Deletes itself if it is ever not in your hand, or if you should have no access to TK.
-*/
-/obj/item/tk_grab
- name = "Telekinetic Grab"
- desc = "Magic."
- icon = 'icons/obj/magic.dmi'//Needs sprites
- icon_state = "2"
- flags = NOBLUDGEON | ABSTRACT | DROPDEL
- //item_state = null
- w_class = WEIGHT_CLASS_GIGANTIC
- layer = ABOVE_HUD_LAYER
- plane = ABOVE_HUD_PLANE
-
- blocks_emissive = FALSE
- var/last_throw = 0
- var/atom/movable/focus = null
- var/mob/living/host = null
-
-/obj/item/tk_grab/Destroy()
- if(focus)
- release_object()
- // Focus is null now
- host = null
- return ..()
-
-/obj/item/tk_grab/dropped(mob/user)
- if(focus && user && loc != user && loc != user.loc) // drop_item() gets called when you tk-attack a table/closet with an item
- if(focus.Adjacent(loc))
- focus.forceMove(loc)
- . = ..()
-
-
- //stops TK grabs being equipped anywhere but into hands
-/obj/item/tk_grab/equipped(mob/user, slot)
- if(slot & ITEM_SLOT_BOTH_HANDS)
- return
- qdel(src)
-
-
-/obj/item/tk_grab/attack_self__legacy__attackchain(mob/user)
- if(focus)
- focus.attack_self_tk(user)
-
-/obj/item/tk_grab/override_throw(mob/user, atom/target)
- afterattack__legacy__attackchain(target, user)
- return TRUE
-
-/obj/item/tk_grab/afterattack__legacy__attackchain(atom/target , mob/living/user, proximity, params)
- if(!target || !user)
- return
- if(last_throw + TK_COOLDOWN > world.time)
- return
- if(!host || host != user)
- qdel(src)
- return
- if(!HAS_TRAIT(host, TRAIT_TELEKINESIS))
- qdel(src)
- return
- if(isobj(target) && !isturf(target.loc))
- return
-
- var/d = get_dist(user, target)
- if(focus)
- d = max(d,get_dist(user,focus)) // whichever is further
- if(d > TK_MAXRANGE || user.z != target.z)
- to_chat(user, "Your mind won't reach that far.")
- return
-
- if(!focus)
- focus_object(target, user)
- return
-
- if(target == focus)
- target.attack_self_tk(user)
- return // todo: something like attack_self not laden with assumptions inherent to attack_self
-
-
- if(isitem(focus) && target.Adjacent(focus) && !user.in_throw_mode)
- var/obj/item/I = focus
- var/resolved = target.attackby__legacy__attackchain(I, user, params)
- if(!resolved && target && I)
- I.afterattack__legacy__attackchain(target,user,1) // for splashing with beakers
-
-
- else
- if(focus.buckled_mobs)
- to_chat(user, "This object is too heavy to move with something buckled to it!")
- return
- if(length(focus.client_mobs_in_contents))
- to_chat(user, "This object is too heavy to move with something inside of it!")
- return
- apply_focus_overlay()
- focus.throw_at(target, 10, 1, user)
- last_throw = world.time
-
-/obj/item/tk_grab/attack__legacy__attackchain(mob/living/M, mob/living/user, def_zone)
- return
-
-/obj/item/tk_grab/is_equivalent(obj/item/I)
- . = ..()
- if(!.)
- return I == focus
-
-/obj/item/tk_grab/proc/focus_object(obj/target, mob/user)
- if(!isobj(target))
- return//Cant throw non objects atm might let it do mobs later
- if(target.anchored || !isturf(target.loc))
- qdel(src)
- return
- focus = target
- update_icon(UPDATE_OVERLAYS)
- apply_focus_overlay()
- // Make it behave like other equipment
- if(isitem(target))
- if(target in user.tkgrabbed_objects)
- // Release the old grab first
- user.drop_item_to_ground(user.tkgrabbed_objects[target])
- user.tkgrabbed_objects[target] = src
-
-/obj/item/tk_grab/proc/release_object()
- if(!focus)
- return
- if(isitem(focus))
- // Delete the key/value pair of item to TK grab
- host.tkgrabbed_objects -= focus
- focus = null
- update_icon(UPDATE_OVERLAYS)
-
-/obj/item/tk_grab/proc/apply_focus_overlay()
- if(!focus)
- return
- new /obj/effect/temp_visual/telekinesis(get_turf(focus))
-
-/obj/item/tk_grab/proc/form_grab(mob/user, obj/target)
- user.put_in_active_hand(src)
- host = user
- focus_object(target, user)
-
-
-/obj/item/tk_grab/update_overlays()
- . = ..()
- if(focus && focus.icon && focus.icon_state)
- . += icon(focus.icon,focus.icon_state)
-
-#undef TK_COOLDOWN
-
-#undef TK_MAXRANGE
diff --git a/code/controllers/configuration/sections/database_configuration.dm b/code/controllers/configuration/sections/database_configuration.dm
deleted file mode 100644
index 73a396c9e29e4..0000000000000
--- a/code/controllers/configuration/sections/database_configuration.dm
+++ /dev/null
@@ -1,43 +0,0 @@
-/// Config holder for all database related things
-/datum/configuration_section/database_configuration
- protection_state = PROTECTION_PRIVATE // NO! BAD!
- /// SQL enabled or not
- var/enabled = FALSE
- /// What SQL version are we on
- var/version = 0
- /// Address of the SQL server
- var/address = "127.0.0.1"
- /// Port of the SQL server
- var/port = 3306
- /// SQL usename
- var/username = "root"
- /// SQL password
- var/password = "root" // Dont do this in prod. Please......
- /// Database name
- var/db = "paradise_gamedb"
- /// Time in seconds for async queries to time out
- var/async_query_timeout = 10
- /// Thread limit for async queries
- var/async_thread_limit = 50
-
-/datum/configuration_section/database_configuration/load_data(list/data)
- // UNIT TESTS ARE DEFINED - USE CUSTOM CI VALUES
- #ifdef GAME_TESTS
-
- enabled = TRUE
- // This needs to happen in the CI environment to ensure the example SQL version gets updated.
- CONFIG_LOAD_NUM(version, data["sql_version"])
-
- #else
- // Load the normal config. Were not in CI mode
- // Use the load wrappers here. That way the default isnt made 'null' if you comment out the config line
- CONFIG_LOAD_BOOL(enabled, data["sql_enabled"])
- CONFIG_LOAD_NUM(version, data["sql_version"])
- CONFIG_LOAD_STR(address, data["sql_address"])
- CONFIG_LOAD_NUM(port, data["sql_port"])
- CONFIG_LOAD_STR(username, data["sql_username"])
- CONFIG_LOAD_STR(password, data["sql_password"])
- CONFIG_LOAD_STR(db, data["sql_database"])
- CONFIG_LOAD_NUM(async_query_timeout, data["async_query_timeout"])
- CONFIG_LOAD_NUM(async_thread_limit, data["async_thread_limit"])
- #endif
diff --git a/code/controllers/configuration/sections/redis_configuration.dm b/code/controllers/configuration/sections/redis_configuration.dm
deleted file mode 100644
index b418441e7f992..0000000000000
--- a/code/controllers/configuration/sections/redis_configuration.dm
+++ /dev/null
@@ -1,20 +0,0 @@
-/// Config holder for all redis related things
-/datum/configuration_section/redis_configuration
- protection_state = PROTECTION_PRIVATE // NO! BAD!
- /// Redis enabled or not
- var/enabled = FALSE
- /// Redis connection string. Includes passphrase if needed.
- var/connstring = "redis://127.0.0.1/"
-
-/datum/configuration_section/redis_configuration/load_data(list/data)
- // UNIT TESTS ARE DEFINED - USE CUSTOM CI VALUES
- #ifdef GAME_TESTS
-
- // enabled = TRUE
-
- #else
- // Load the normal config. Were not in CI mode
- // Use the load wrappers here. That way the default isnt made 'null' if you comment out the config line
- CONFIG_LOAD_BOOL(enabled, data["redis_enabled"])
- CONFIG_LOAD_STR(connstring, data["redis_connstring"])
- #endif
diff --git a/code/controllers/subsystem/SSchat.dm b/code/controllers/subsystem/SSchat.dm
deleted file mode 100644
index f76d4467d0d05..0000000000000
--- a/code/controllers/subsystem/SSchat.dm
+++ /dev/null
@@ -1,104 +0,0 @@
-/**
- * Copyright (c) 2020 Aleksej Komarov
- * SPDX-License-Identifier: MIT
- */
-
-SUBSYSTEM_DEF(chat)
- name = "Chat"
- flags = SS_TICKER|SS_NO_INIT
- wait = 1
- priority = FIRE_PRIORITY_CHAT
- init_order = INIT_ORDER_CHAT
- offline_implications = "Chat will no longer function correctly. Immediate server restart recommended."
-
- /// Associates a ckey with a list of messages to send to them.
- var/list/list/datum/chat_payload/client_to_payloads = list()
-
- /// Associates a ckey with an associative list of their last CHAT_RELIABILITY_HISTORY_SIZE messages.
- var/list/list/datum/chat_payload/client_to_reliability_history = list()
-
- /// Associates a ckey with their next sequence number.
- var/list/client_to_sequence_number = list()
-
-/datum/controller/subsystem/chat/proc/generate_payload(client/target, message_data)
- var/sequence = client_to_sequence_number[target.ckey]
- client_to_sequence_number[target.ckey] += 1
-
- var/datum/chat_payload/payload = new
- payload.sequence = sequence
- payload.content = message_data
-
- if(!(target.ckey in client_to_reliability_history))
- client_to_reliability_history[target.ckey] = list()
- var/list/client_history = client_to_reliability_history[target.ckey]
- client_history["[sequence]"] = payload
-
- if(length(client_history) > CHAT_RELIABILITY_HISTORY_SIZE)
- var/oldest = text2num(client_history[1])
- for(var/index in 2 to length(client_history))
- var/test = text2num(client_history[index])
- if(test < oldest)
- oldest = test
- client_history -= "[oldest]"
- return payload
-
-/datum/controller/subsystem/chat/proc/send_payload_to_client(client/target, datum/chat_payload/payload)
- target.tgui_panel.window.send_message("chat/message", payload.into_message())
- SEND_TEXT(target, payload.get_content_as_html())
-
-/datum/controller/subsystem/chat/fire()
- for(var/ckey in client_to_payloads)
- var/client/target = GLOB.directory[ckey]
- if(isnull(target)) // verify client still exists
- LAZYREMOVE(client_to_payloads, ckey)
- continue
-
- for(var/datum/chat_payload/payload as anything in client_to_payloads[ckey])
- send_payload_to_client(target, payload)
- LAZYREMOVE(client_to_payloads, ckey)
-
- if(MC_TICK_CHECK)
- return
-
-/datum/controller/subsystem/chat/proc/queue(queue_target, list/message_data)
- var/list/targets = islist(queue_target) ? queue_target : list(queue_target)
- for(var/target in targets)
- var/client/client = CLIENT_FROM_VAR(target)
- if(isnull(client))
- continue
- LAZYADDASSOCLIST(client_to_payloads, client.ckey, generate_payload(client, message_data))
-
-/datum/controller/subsystem/chat/proc/send_immediate(send_target, list/message_data)
- var/list/targets = islist(send_target) ? send_target : list(send_target)
- for(var/target in targets)
-#ifdef GAME_TESTS
- var/mob/M = target
- if(istype(M) && M?:mind?:key == "interaction_test_[M.UID()]")
- LAZYADD(GLOB.game_test_chats[M.mind.key], message_to_html(message_data))
-#endif
- var/client/client = CLIENT_FROM_VAR(target)
- if(isnull(client))
- continue
- send_payload_to_client(client, generate_payload(client, message_data))
-
-/datum/controller/subsystem/chat/proc/handle_resend(client/client, sequence)
- var/list/client_history = client_to_reliability_history[client.ckey]
- sequence = "[sequence]"
- if(isnull(client_history) || !(sequence in client_history))
- return
-
- var/datum/chat_payload/payload = client_history[sequence]
- if(payload.resends > CHAT_RELIABILITY_MAX_RESENDS)
- return // we tried but byond said no
-
- payload.resends += 1
- send_payload_to_client(client, client_history[sequence])
- SSblackbox.record_feedback(
- "nested tally",
- "chat_resend_byond_version",
- 1,
- list(
- "[client.byond_version]",
- "[client.byond_build]",
- ),
- )
diff --git a/code/controllers/subsystem/SSdbcore.dm b/code/controllers/subsystem/SSdbcore.dm
deleted file mode 100644
index 9974e20ccbb96..0000000000000
--- a/code/controllers/subsystem/SSdbcore.dm
+++ /dev/null
@@ -1,522 +0,0 @@
-SUBSYSTEM_DEF(dbcore)
- name = "Database"
- flags = SS_BACKGROUND
- wait = 1 MINUTES
- init_order = INIT_ORDER_DBCORE
- cpu_display = SS_CPUDISPLAY_LOW
-
- /// Is the DB schema valid
- var/schema_valid = TRUE
- /// Timeout of failed connections
- var/failed_connection_timeout = 0
- /// Amount of failed connections
- var/failed_connections = 0
-
- /// Last error to occur
- var/last_error
- /// List of currenty processing queries
- var/list/active_queries = list()
-
- /// SQL errors that have occured mid round
- var/total_errors = 0
-
- /// Connection handle. This is an arbitrary handle returned from rust_g.
- var/connection
-
- offline_implications = "The server will no longer check for undeleted SQL Queries. No immediate action is needed."
-
-/datum/controller/subsystem/dbcore/get_stat_details()
- return "A: [length(active_queries)]"
-
-// This is in Initialize() so that its actually seen in chat
-/datum/controller/subsystem/dbcore/Initialize()
- if(!schema_valid)
- log_startup_progress("Database schema ([GLOB.configuration.database.version]) doesn't match the latest schema version ([SQL_VERSION]). Roundstart has been delayed.")
-
-
-/datum/controller/subsystem/dbcore/fire()
- for(var/I in active_queries)
- var/datum/db_query/Q = I
- if(world.time - Q.last_activity_time > 5 MINUTES)
- log_debug("Found undeleted query, please check the server logs and notify coders.")
- log_sql("Undeleted query: \"[Q.sql]\" LA: [Q.last_activity] LAT: [Q.last_activity_time]")
- qdel(Q)
- if(MC_TICK_CHECK)
- return
-
-/datum/controller/subsystem/dbcore/Recover()
- connection = SSdbcore.connection
-
-//nu
-/datum/controller/subsystem/dbcore/can_vv_get(var_name)
- return var_name != NAMEOF(src, connection) && var_name != NAMEOF(src, active_queries) && ..()
-
-/datum/controller/subsystem/dbcore/vv_edit_var(var_name, var_value)
- if(var_name == NAMEOF(src, connection))
- return FALSE
- return ..()
-
-/**
- * Connection Creator
- *
- * This proc basically does a few sanity checks before connecting, then attempts to make a connection
- * When connecting, RUST_G will initialize a thread pool for queries to use to run asynchronously
- */
-/datum/controller/subsystem/dbcore/proc/Connect()
- if(IsConnected())
- return TRUE
-
- if(!GLOB.configuration.database.enabled)
- return FALSE
-
- if(failed_connection_timeout <= world.time) //it's been more than 5 seconds since we failed to connect, reset the counter
- failed_connections = 0
-
- if(failed_connections > 5) //If it failed to establish a connection more than 5 times in a row, don't bother attempting to connect for 5 seconds.
- failed_connection_timeout = world.time + 50
- return FALSE
-
- var/result = json_decode(rustg_sql_connect_pool(json_encode(list(
- "host" = GLOB.configuration.database.address,
- "port" = GLOB.configuration.database.port,
- "user" = GLOB.configuration.database.username,
- "pass" = GLOB.configuration.database.password,
- "db_name" = GLOB.configuration.database.db,
- "read_timeout" = GLOB.configuration.database.async_query_timeout,
- "write_timeout" = GLOB.configuration.database.async_query_timeout,
- "max_threads" = GLOB.configuration.database.async_thread_limit,
- ))))
- . = (result["status"] == "ok")
- if(.)
- connection = result["handle"]
- else
- connection = null
- last_error = result["data"]
- log_sql("Connect() failed | [last_error]")
- ++failed_connections
-
-/**
- * Schema Version Checker
- *
- * Basically verifies that the DB schema in the config is the same as the version the game is expecting.
- * If it is a valid version, the DB will then connect.
- */
-/datum/controller/subsystem/dbcore/proc/CheckSchemaVersion()
- if(GLOB.configuration.database.enabled)
- // The unit tests have their own version of this check, which wont hold the server up infinitely, so this is disabled if we are running unit tests
- #ifndef GAME_TESTS
- if(GLOB.configuration.database.enabled && GLOB.configuration.database.version != SQL_VERSION)
- GLOB.configuration.database.enabled = FALSE
- schema_valid = FALSE
- SSticker.ticker_going = FALSE
- SEND_TEXT(world.log, "Database connection failed: Invalid SQL Versions")
- return FALSE
- #endif
- if(Connect())
- SEND_TEXT(world.log, "Database connection established")
- else
- // log_sql() because then an error will be logged in the same place
- log_sql("Your server failed to establish a connection with the database")
- else
- SEND_TEXT(world.log, "Database is not enabled in configuration")
-
-/**
- * Disconnection Handler
- *
- * Tells the DLL to clean up any open connections.
- * This will also reset the failed connection counter
- */
-/datum/controller/subsystem/dbcore/proc/Disconnect()
- failed_connections = 0
- if(connection)
- rustg_sql_disconnect_pool(connection)
- connection = null
-
-/**
- * Shutdown Handler
- *
- * Called during world/Reboot() as part of the MC shutdown
- * Finalises a round in the DB before disconnecting.
- */
-/datum/controller/subsystem/dbcore/Shutdown()
- //This is as close as we can get to the true round end before Disconnect() without changing where it's called, defeating the reason this is a subsystem
- if(SSdbcore.Connect())
- var/datum/db_query/query_round_shutdown = SSdbcore.NewQuery(
- "UPDATE round SET shutdown_datetime = Now(), end_state = :end_state WHERE id = :round_id",
- list("end_state" = SSticker.end_state, "round_id" = GLOB.round_id)
- )
- query_round_shutdown.Execute()
- qdel(query_round_shutdown)
- if(IsConnected())
- Disconnect()
-
-/**
- * Round ID Setter
- *
- * Called during world/New() at the earliest point
- * Declares a round ID in the database and assigns it to a global. Also ensures that server address and ports are set
- */
-/datum/controller/subsystem/dbcore/proc/SetRoundID()
- if(!IsConnected())
- return
- var/datum/db_query/query_round_initialize = SSdbcore.NewQuery(
- "INSERT INTO round (initialize_datetime, server_ip, server_port, server_id) VALUES (Now(), INET_ATON(:internet_address), :port, :server_id)",
- list("internet_address" = world.internet_address || "0", "port" = "[world.port]", "server_id" = GLOB.configuration.system.instance_id)
- )
- query_round_initialize.Execute(async = FALSE)
- GLOB.round_id = "[query_round_initialize.last_insert_id]"
- qdel(query_round_initialize)
-
-/**
- * Round End Time Setter
- *
- * Called during SSticker.setup()
- * Sets the time that the round started in the DB
- */
-/datum/controller/subsystem/dbcore/proc/SetRoundStart()
- if(!IsConnected())
- return
- var/datum/db_query/query_round_start = SSdbcore.NewQuery(
- "UPDATE round SET start_datetime=NOW(), commit_hash=:hash WHERE id=:round_id",
- list("hash" = GLOB.revision_info.commit_hash, "round_id" = GLOB.round_id)
- )
- query_round_start.Execute(async = FALSE) // This happens during a time of intense server lag, so should be non-async
- qdel(query_round_start)
-
-/**
- * Round End Time Setter
- *
- * Called during SSticker.declare_completion()
- * Sets the time that the round ended in the DB, as well as some other params
- */
-/datum/controller/subsystem/dbcore/proc/SetRoundEnd()
- if(!IsConnected())
- return
- var/datum/db_query/query_round_end = SSdbcore.NewQuery(
- "UPDATE round SET end_datetime = Now(), game_mode_result = :game_mode_result WHERE id = :round_id",
- list("game_mode_result" = SSticker.mode_result, "station_name" = station_name(), "round_id" = GLOB.round_id)
- )
- query_round_end.Execute()
- qdel(query_round_end)
-
-/**
- * IsConnected Helper
- *
- * Short helper to check if the DB is connected or not.
- * Does a few sanity checks, then asks the DLL if we are properly connected
- */
-/datum/controller/subsystem/dbcore/proc/IsConnected()
- if(!GLOB.configuration.database.enabled)
- return FALSE
- if(!schema_valid)
- return FALSE
- if(!connection)
- return FALSE
- return json_decode(rustg_sql_connected(connection))["status"] == "online"
-
-
-/**
- * Error Message Helper
- *
- * Returns the last error that the subsystem encountered.
- * Will always report "Database disabled by configuration" if the DB is disabled.
- */
-/datum/controller/subsystem/dbcore/proc/ErrorMsg()
- if(!GLOB.configuration.database.enabled)
- return "Database disabled by configuration"
- return last_error
-
-/**
- * Error Reporting Helper
- *
- * Pretty much just sets `last_error` to the error argument
- *
- * Arguments:
- * * error - Error text to set `last_error` to
- */
-/datum/controller/subsystem/dbcore/proc/ReportError(error)
- last_error = error
-
-
-/**
- * New Query Invoker
- *
- * Checks to make sure this query isnt being invoked by admin fuckery, then returns a new [/datum/db_query]
- *
- * Arguments:
- * * sql_query - SQL query to be ran, with :parameter placeholders
- * * arguments - Associative list of parameters to be inserted into the query
- */
-/datum/controller/subsystem/dbcore/proc/NewQuery(sql_query, arguments)
- if(IsAdminAdvancedProcCall())
- to_chat(usr, "DB query blocked: Advanced ProcCall detected.")
- message_admins("[key_name(usr)] attempted to create a DB query via advanced proc-call")
- log_admin("[key_name(usr)] attempted to create a DB query via advanced proc-call")
- return FALSE
- return new /datum/db_query(connection, sql_query, arguments)
-
-/**
- * Handler to allow many queries to be executed en masse
- *
- * Feed this proc a list of queries and it will execute them all at once, by the power of async magic!
- *
- * Arguments:
- * * querys - List of queries to execute
- * * warn - Boolean to warn on query failure
- * * qdel - Boolean to enable auto qdel of queries
- * * assoc - Boolean to enable support for an associative list of queries
- * * log - Do we want to generate logs for these queries
- */
-/datum/controller/subsystem/dbcore/proc/MassExecute(list/querys, warn = FALSE, qdel = FALSE, assoc = FALSE, log = TRUE)
- if(!islist(querys))
- if(!istype(querys, /datum/db_query))
- CRASH("Invalid query passed to MassExecute: [querys]")
- querys = list(querys)
-
- var/start_time = start_watch()
- if(log)
- log_debug("Mass executing [length(querys)] queries...")
-
- for(var/thing in querys)
- var/datum/db_query/query
- if(assoc)
- query = querys[thing]
- else
- query = thing
- if(warn)
- INVOKE_ASYNC(query, TYPE_PROC_REF(/datum/db_query, warn_execute))
- else
- INVOKE_ASYNC(query, TYPE_PROC_REF(/datum/db_query, Execute))
-
- for(var/thing in querys)
- var/datum/db_query/query
- if(assoc)
- query = querys[thing]
- else
- query = thing
- UNTIL(!query.in_progress)
- if(qdel)
- qdel(query)
-
- if(log)
- log_debug("Executed [length(querys)] queries in [stop_watch(start_time)]s")
-
-/**
- * # db_query
- *
- * Datum based handler for all database queries
- *
- * Holds information regarding inputs, status, and outputs
- */
-/datum/db_query
- // Inputs
- /// The connection being used with this query
- var/connection
- /// The SQL statement being executed with :parameter placeholders
- var/sql
- /// An associative list of parameters to be substituted into the statement
- var/arguments
-
- // Status information
- /// Is the query currently in progress
- var/in_progress
- /// What was our last error, if any
- var/last_error
- /// What was our last activity
- var/last_activity
- /// When was our last activity
- var/last_activity_time
-
- // Output
- /// List of all rows returned
- var/list/list/rows
- /// Counter of the next row to take
- var/next_row_to_take = 1
- /// How many rows were affected by the query
- var/affected
- /// ID of the last inserted row
- var/last_insert_id
- /// List of data values populated by NextRow()
- var/list/item
-
-// Sets up some vars and throws it into the SS active query list
-/datum/db_query/New(connection, sql, arguments)
- SSdbcore.active_queries[src] = TRUE
- Activity("Created")
- item = list()
-
- src.connection = connection
- src.sql = sql
- src.arguments = arguments
-
-// Takes it out of the active query list, as well as closing it up
-/datum/db_query/Destroy()
- Close()
- SSdbcore.active_queries -= src
- return ..()
-
-/datum/db_query/CanProcCall(proc_name)
- // go away
- return FALSE
-
-
-/**
- * Activity Update Handler
- *
- * Sets the last activity text to the argument input, as well as updating the activity time
- *
- * Arguments:
- * * activity - Last activity text
- */
-/datum/db_query/proc/Activity(activity)
- last_activity = activity
- last_activity_time = world.time
-
-/**
- * Wrapped for warning on execution
- *
- * You should use this proc when running the SQL statement. It will auto inform the user and the online admins if a query fails
- *
- * Arguments:
- * * async - Are we running this query asynchronously
- * * log_error - Do we want to log errors this creates? Disable this if you are running sensitive queries where you dont want errors logged in plain text (EG: Auth token stuff)
- */
-/datum/db_query/proc/warn_execute(async = TRUE, log_error = TRUE)
- if(!GLOB.configuration.database.enabled)
- return
- . = Execute(async, log_error)
- if(!.)
- SSdbcore.total_errors++
- if(usr)
- to_chat(usr, "A SQL error occurred during this operation, please inform an admin or a coder.", MESSAGE_TYPE_ADMINLOG, confidential = TRUE)
- message_admins("An SQL error has occurred. Please check the server logs, with the following timestamp ID: \[[time_stamp()]]")
-
-/**
- * Main Execution Handler
- *
- * Invoked by [warn_execute()]
- * This handles query error logging, as well as invoking the actual runner
- * Arguments:
- * * async - Are we running this query asynchronously
- * * log_error - Do we want to log errors this creates? Disable this if you are running sensitive queries where you dont want errors logged in plain text (EG: Auth token stuff)
- */
-/datum/db_query/proc/Execute(async = TRUE, log_error = TRUE)
- Activity("Execute")
- if(in_progress)
- CRASH("Attempted to start a new query while waiting on the old one")
-
- if(!SSdbcore.IsConnected())
- last_error = "No connection!"
- return FALSE
-
- var/start_time
- if(!async)
- start_time = REALTIMEOFDAY
- Close()
- . = run_query(async)
- var/timed_out = !. && findtext(last_error, "Operation timed out")
- if(!. && log_error)
- log_sql("[last_error] | Query used: [sql] | Arguments: [json_encode(arguments)]")
- if(!async && timed_out)
- log_sql("Query execution started at [start_time]")
- log_sql("Query execution ended at [REALTIMEOFDAY]")
- log_sql("Slow query timeout detected.")
- log_sql("Query used: [sql]")
- slow_query_check()
-
-/**
- * Actual Query Runner
- *
- * This does the main query with the database and the rust calls themselves
- *
- * Arguments:
- * * async - Are we running this query asynchronously
- */
-/datum/db_query/proc/run_query(async)
- var/job_result_str
-
- if(async)
- var/job_id = rustg_sql_query_async(connection, sql, json_encode(arguments))
- in_progress = TRUE
- UNTIL((job_result_str = rustg_sql_check_query(job_id)) != RUSTG_JOB_NO_RESULTS_YET)
- in_progress = FALSE
-
- if(job_result_str == RUSTG_JOB_ERROR)
- last_error = job_result_str
- return FALSE
- else
- job_result_str = rustg_sql_query_blocking(connection, sql, json_encode(arguments))
-
- var/result = json_decode(job_result_str)
- switch(result["status"])
- if("ok")
- rows = result["rows"]
- affected = result["affected"]
- last_insert_id = result["last_insert_id"]
- return TRUE
- if("err")
- last_error = result["data"]
- return FALSE
- if("offline")
- last_error = "offline"
- return FALSE
-
-// Just tells the admins if a query timed out, and asks if the server hung to help error reporting
-/datum/db_query/proc/slow_query_check()
- message_admins("HEY! A database query timed out. Did the server just hang? \[YES\]|\[NO\]")
-
-
-/**
- * Proc to get the next row in a DB query
- *
- * Cycles `item` to the next row in the DB query, if multiple were fetched
- */
-/datum/db_query/proc/NextRow()
- Activity("NextRow")
-
- if(rows && next_row_to_take <= length(rows))
- item = rows[next_row_to_take]
- next_row_to_take++
- return !!item
- else
- return FALSE
-
-// Simple helper to get the last error a query had
-/datum/db_query/proc/ErrorMsg()
- return last_error
-
-// Simple proc to null out data to aid GC
-/datum/db_query/proc/Close()
- rows = null
- item = null
-
-// Verb that lets admins force reconnect the DB
-/client/proc/reestablish_db_connection()
- set category = "Debug"
- set name = "Reestablish DB Connection"
- if(!GLOB.configuration.database.enabled)
- to_chat(usr, "The Database is not enabled in the server configuration!")
- return
-
- if(SSdbcore.IsConnected())
- if(!check_rights(R_DEBUG, FALSE))
- to_chat(usr, "The database is already connected! (Only those with +DEBUG can force a reconnection)")
- return
-
- var/reconnect = alert("The database is already connected! If you *KNOW* that this is incorrect, you can force a reconnection", "The database is already connected!", "Force Reconnect", "Cancel")
- if(reconnect != "Force Reconnect")
- return
-
- SSdbcore.Disconnect()
- log_admin("[key_name(usr)] has forced the database to disconnect")
- message_admins("[key_name_admin(usr)] has forced the database to disconnect!!!")
-
- log_admin("[key_name(usr)] is attempting to re-establish the DB Connection")
- message_admins("[key_name_admin(usr)] is attempting to re-establish the DB Connection")
- SSblackbox.record_feedback("tally", "admin_verb", 1, "Force Reconnect DB") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
-
- SSdbcore.failed_connections = 0 // Reset this
- if(!SSdbcore.Connect())
- message_admins("Database connection failed: [SSdbcore.ErrorMsg()]")
- else
- message_admins("Database connection re-established")
diff --git a/code/controllers/subsystem/SSdebugview.dm b/code/controllers/subsystem/SSdebugview.dm
deleted file mode 100644
index 437d544f01c61..0000000000000
--- a/code/controllers/subsystem/SSdebugview.dm
+++ /dev/null
@@ -1,93 +0,0 @@
-SUBSYSTEM_DEF(debugview)
- name = "Debug View"
- wait = 1 // SS_TICKER subsystem, so wait is in ticks
- flags = SS_TICKER|SS_NO_INIT
- offline_implications = "Shift+F3 will no longer show a debug view. No immediate action is needed."
- cpu_display = SS_CPUDISPLAY_LOW
- /// List of clients currently processing
- var/list/client/processing = list()
-
-/datum/controller/subsystem/debugview/fire(resumed)
- // Dont generate text if no one is there to look at it
- if(!length(processing))
- return
-
- // Generate debug text
- var/list/entries = list()
- entries += "CPU: [round(world.cpu, 1)] | MCPU: [round(world.map_cpu, 1)] | FPS/TPS: [world.fps] | Clients: [length(GLOB.clients)] | BYOND: [world.byond_version].[world.byond_build]"
- entries += "\[Air] Cost: [SSair.get_cost()]ms | MT: [round(SSair.cost_milla_tick, 1)]ms | IT: [SSair.interesting_tile_count] | HS: [SSair.hotspot_count] | WT: [SSair.windy_tile_count]"
- entries += "\[Debug] Cost: [round(SSdebugview.cost, 1)]ms | P: [length(SSdebugview.processing)]" // meta af (tbf we need to know how much were using)
- entries += "\[FP] Cost: [round(SSfastprocess.cost, 1)]ms | P: [length(SSfastprocess.processing)]"
- // Snowflakery for SSgarbage
- var/list/counts = list()
- for(var/list/L in SSgarbage.queues)
- counts += length(L)
- entries += "\[GC] Cost: [round(SSgarbage.cost, 1)]ms | Q: [counts.Join(",")] H: [SSgarbage.delslasttick] | S: [SSgarbage.gcedlasttick]"
- entries += "\[Input] Cost: [round(SSinput.cost, 1)]ms"
- entries += "\[Lighting] Cost: [round(SSlighting.cost, 1)]ms | SQ: [length(SSlighting.sources_queue)] | CQ: [length(SSlighting.corners_queue)] | OQ: [length(SSlighting.objects_queue)]"
- entries += "\[Machines] Cost: [round(SSmachines.cost, 1)]ms | M: [length(SSmachines.processing)] | P: [length(SSmachines.powernets)]"
- entries += "\[Mobs] Cost: [round(SSmobs.cost, 1)]ms | P: [length(GLOB.mob_living_list)]"
- entries += "\[Objects] Cost: [round(SSobj.cost, 1)]ms | P: [length(SSobj.processing)]"
- entries += "\[Processing] Cost: [round(SSprocessing.cost, 1)]ms | P: [length(SSprocessing.processing)]"
- entries += "\[Projectiles] Cost: [round(SSprojectiles.cost, 1)]ms | P: [length(SSprojectiles.processing)]"
- entries += "\[Runechat] Cost: [round(SSrunechat.cost, 1)]ms | AM: [SSrunechat.bucket_count] | SQ: [length(SSrunechat.second_queue)]"
- entries += "\[TGUI] Cost: [round(SStgui.cost, 1)]ms | P: [length(SStgui.open_uis)]]"
- entries += "\[Timer] Cost: [round(SStimer.cost, 1)]ms | B: [SStimer.bucket_count] | P: [length(SStimer.second_queue)] | RST: [SStimer.bucket_reset_count]"
-
- // Do some parsing to format it properly
- var/out_text = entries.Join("\n")
- var/mty = 480 - 9 * length(entries)
-
- // And update the clients
- for(var/client/C as anything in processing)
- C.debug_text_overlay.maptext_y = mty
- C.debug_text_overlay.maptext = "[out_text]"
-
-/datum/controller/subsystem/debugview/proc/start_processing(client/C)
- C.debug_text_overlay = new /atom/movable/screen/debugtextholder(null, C)
- C.screen |= C.debug_text_overlay
- processing |= C
-
-/datum/controller/subsystem/debugview/proc/stop_processing(client/C)
- processing -= C
- C.screen -= C.debug_text_overlay
- QDEL_NULL(C.debug_text_overlay)
-
-/atom/movable/screen/debugtextholder
- icon = 'icons/mob/screen_full.dmi'
- icon_state = "empty"
- screen_loc = "TOP,LEFT"
- plane = HUD_PLANE_DEBUGVIEW
- maptext_height = 480 // 15 * 32 (15 tiles, 32 pixels each)
- maptext_width = 480 // changes with prefs
-
-/atom/movable/screen/debugtextholder/Initialize(mapload, client/C)
- . = ..()
- update_view(C)
-
-/atom/movable/screen/debugtextholder/proc/update_view(client/C)
- var/list/viewsizes = getviewsize(C.view)
- maptext_width = viewsizes[1] * world.icon_size
-
-// Make a verb for dumping full SS stats
-/client/proc/ss_breakdown()
- set name = "SS Info Breakdown"
- set category = "Debug"
-
- if(!check_rights(R_DEBUG|R_VIEWRUNTIMES))
- return
-
- var/datum/browser/popup = new(usr, "ss_breakdown", "Subsystem Breakdown", 1100, 850)
-
- var/list/html = list()
- html += "CPU: [round(world.cpu, 1)] | MCPU: [round(world.map_cpu, 1)] | FPS/TPS: [world.fps] | Clients: [length(GLOB.clients)] | BYOND: [world.byond_version].[world.byond_build]"
- html += "--- SS BREAKDOWN ---"
- for(var/datum/controller/subsystem/SS as anything in Master.subsystems)
- // We dont care about subsystems that arent firing (or are unable to)
- if((SS.flags & SS_NO_FIRE) || !SS.can_fire)
- continue
-
- html += "[SS.state_colour()]\[[SS.state_letter()]][SS.ss_id]\t[SS.get_cost()]ms | [round(SS.tick_usage, 1)]% [SS.get_stat_details() ? "| [SS.get_stat_details()] " : ""]| VV Edit"
-
- popup.set_content(html.Join(" "))
- popup.open(FALSE)
diff --git a/code/controllers/subsystem/SSghost_spawns.dm b/code/controllers/subsystem/SSghost_spawns.dm
deleted file mode 100644
index ad1690b7610c5..0000000000000
--- a/code/controllers/subsystem/SSghost_spawns.dm
+++ /dev/null
@@ -1,324 +0,0 @@
-SUBSYSTEM_DEF(ghost_spawns)
- name = "Ghost Spawns"
- flags = SS_BACKGROUND | SS_NO_INIT
- wait = 1 SECONDS
- runlevels = RUNLEVEL_GAME
- offline_implications = "Ghosts will no longer be able to respawn as event mobs (Blob, etc..). Shuttle call recommended."
- cpu_display = SS_CPUDISPLAY_LOW
-
- /// List of polls currently ongoing, to be checked on next fire()
- var/list/datum/candidate_poll/currently_polling
- /// Whether there are active polls or not
- var/polls_active = FALSE
- /// Number of polls performed since the start
- var/total_polls = 0
- /// The poll that's closest to finishing
- var/datum/candidate_poll/next_poll_to_finish
-
-/datum/controller/subsystem/ghost_spawns/fire()
- if(!polls_active)
- return
- if(!currently_polling) // if polls_active is TRUE then this shouldn't happen, but still..
- currently_polling = list()
-
- for(var/poll in currently_polling)
- var/datum/candidate_poll/P = poll
- if(P.time_left() <= 0)
- polling_finished(P)
-
-/**
- * Polls for candidates with a question and a preview of the role
- *
- * This proc replaces /proc/pollCandidates.
- * Should NEVER be used in a proc that has waitfor set to FALSE/0 (due to #define UNTIL)
- * Arguments:
- * * question - The question to ask to potential candidates
- * * role - The role to poll for. Should be a ROLE_x enum. If set, potential candidates who aren't eligible will be ignored
- * * antag_age_check - Whether to filter out potential candidates who don't have an old enough account
- * * poll_time - How long to poll for in deciseconds
- * * ignore_respawnability - Whether to ignore the player's respawnability
- * * min_hours - The amount of hours needed for a potential candidate to be eligible
- * * flash_window - Whether the poll should flash a potential candidate's game window
- * * check_antaghud - Whether to filter out potential candidates who enabled AntagHUD
- * * source - The atom, atom prototype, icon or mutable appearance to display as an icon in the alert
- * * role_cleanname - The name override to display to clients
- */
-/datum/controller/subsystem/ghost_spawns/proc/poll_candidates(question = "Would you like to play a special role?", role, antag_age_check = FALSE, poll_time = 30 SECONDS, ignore_respawnability = FALSE, min_hours = 0, flash_window = TRUE, check_antaghud = TRUE, source, role_cleanname, reason, dont_play_notice_sound = FALSE)
- log_debug("Polling candidates [role ? "for [role_cleanname || get_roletext(role)]" : "\"[question]\""] for [poll_time / 10] seconds")
-
- // Start firing
- polls_active = TRUE
- total_polls++
-
- var/datum/candidate_poll/P = new(role, question, poll_time)
- LAZYADD(currently_polling, P)
-
- // We're the poll closest to completion
- if(!next_poll_to_finish || poll_time < next_poll_to_finish.time_left())
- next_poll_to_finish = P
-
- var/category = "[P.hash]_notify_action"
-
- var/notice_sound = sound('sound/effects/ghost_ping.ogg')
- for(var/mob/M in (GLOB.player_list))
- if(!is_eligible(M, role, antag_age_check, role, min_hours, check_antaghud))
- continue
- if(!dont_play_notice_sound)
- SEND_SOUND(M, notice_sound)
- if(flash_window)
- window_flash(M.client)
-
- // If we somehow send two polls for the same mob type, but with a duration on the second one shorter than the time left on the first one,
- // we need to keep the first one's timeout rather than use the shorter one
- var/atom/movable/screen/alert/notify_action/current_alert = LAZYACCESS(M.alerts, category)
- var/alert_time = poll_time
- var/alert_poll = P
- if(current_alert && current_alert.timeout > (world.time + poll_time - world.tick_lag))
- alert_time = current_alert.timeout - world.time + world.tick_lag
- alert_poll = current_alert.poll
-
- // Send them an on-screen alert
- var/atom/movable/screen/alert/notify_action/A = M.throw_alert(category, /atom/movable/screen/alert/notify_action, timeout_override = alert_time, no_anim = TRUE)
- if(!A)
- continue
-
- A.icon = ui_style2icon(M.client?.prefs.UI_style)
- A.name = "Looking for candidates"
- A.desc = "[question]\n\n(expires in [poll_time / 10] seconds)"
- A.show_time_left = TRUE
- A.poll = alert_poll
-
- // Sign up inheritance and stacking
- var/inherited_sign_up = FALSE
- var/num_stack = 1
- for(var/existing_poll in currently_polling)
- var/datum/candidate_poll/P2 = existing_poll
- if(P != P2 && P.hash == P2.hash)
- // If there's already a poll for an identical mob type ongoing and the client is signed up for it, sign them up for this one
- if(!inherited_sign_up && (M in P2.signed_up) && P.sign_up(M, TRUE))
- A.update_signed_up_alert(M)
- inherited_sign_up = TRUE
- // This number is used to display the number of polls the alert regroups
- num_stack++
- if(num_stack > 1)
- A.display_stacks(num_stack)
-
- // Image to display
- var/image/I
- if(source)
- if(!ispath(source))
- var/atom/S = source
- var/old_layer = S.layer
- var/old_plane = S.plane
-
- S.layer = FLOAT_LAYER
- S.plane = FLOAT_PLANE
- A.overlays += S
- S.layer = old_layer
- S.plane = old_plane
- else
- I = image(source, layer = FLOAT_LAYER, dir = SOUTH)
- else
- // Just use a generic image
- I = image('icons/effects/effects.dmi', icon_state = "static", layer = FLOAT_LAYER, dir = SOUTH)
-
- if(I)
- I.layer = FLOAT_LAYER
- I.plane = FLOAT_PLANE
- A.overlays += I
-
- // Chat message
- var/act_jump = ""
- if(isatom(source))
- act_jump = "\[Teleport]"
- var/act_signup = "\[Sign Up]"
- to_chat(M, "Now looking for candidates [role ? "to play as \an [role_cleanname || get_roletext(role)]" : "\"[question]\""]. [act_jump] [act_signup] [reason ? "\nReason: [sanitize(reason)]" : ""]", MESSAGE_TYPE_DEADCHAT)
-
- // Start processing it so it updates visually the timer
- START_PROCESSING(SSprocessing, A)
- A.process()
-
- // Sleep until the time is up
- UNTIL(P.finished)
- if(!ignore_respawnability)
- var/list/eligable_mobs = list()
- for(var/mob/signed_up in P.signed_up)
- if(HAS_TRAIT(signed_up, TRAIT_RESPAWNABLE))
- eligable_mobs += signed_up
- return eligable_mobs
- else
- return P.signed_up
-
-/**
- * Returns whether an observer is eligible to be an event mob
- *
- * Arguments:
- * * M - The mob to check eligibility
- * * role - The role to check eligibility for. Checks 1. the client has enabled the role 2. the account's age for this role if antag_age_check is TRUE
- * * antag_age_check - Whether to check the account's age or not for the given role.
- * * role_text - The role's clean text. Used for checking job bans to determine eligibility
- * * min_hours - The amount of minimum hours the client needs before being eligible
- * * check_antaghud - Whether to consider a client who enabled AntagHUD ineligible or not
- */
-/datum/controller/subsystem/ghost_spawns/proc/is_eligible(mob/M, role, antag_age_check, role_text, min_hours, check_antaghud, ignore_respawnability)
- . = FALSE
- if(!M.key || !M.client)
- return
- if(!ignore_respawnability && !HAS_TRAIT(M, TRAIT_RESPAWNABLE))
- return
- if(role)
- if(!(role in M.client.prefs.be_special))
- return
- if(antag_age_check)
- if(!player_old_enough_antag(M.client, role))
- return
- if(role_text)
- if(jobban_isbanned(M, role_text) || jobban_isbanned(M, ROLE_SYNDICATE))
- return
- if(GLOB.configuration.jobs.enable_exp_restrictions && min_hours)
- if(M.client.get_exp_type_num(EXP_TYPE_LIVING) < min_hours * 60)
- return
- if(check_antaghud && isobserver(M))
- var/mob/dead/observer/O = M
- if(!O.check_ahud_rejoin_eligibility())
- return
-
- return TRUE
-
-/**
- * Called by the subsystem when a poll's timer runs out
- *
- * Can be called manually to finish a poll prematurely
- * Arguments:
- * * P - The poll to finish
- */
-/datum/controller/subsystem/ghost_spawns/proc/polling_finished(datum/candidate_poll/P)
- // Trim players who aren't eligible anymore
- var/len_pre_trim = length(P.signed_up)
- P.trim_candidates()
- log_debug("Candidate poll [P.role ? "for [get_roletext(P.role)]" : "\"[P.question]\""] finished. [len_pre_trim] players signed up, [length(P.signed_up)] after trimming")
-
- P.finished = TRUE
- currently_polling -= P
-
- // Determine which is the next poll closest the completion or "disable" firing if there's none
- if(!length(currently_polling))
- polls_active = FALSE
- next_poll_to_finish = null
- else if(P == next_poll_to_finish)
- next_poll_to_finish = null
- for(var/poll in currently_polling)
- var/datum/candidate_poll/P2 = poll
- if(!next_poll_to_finish || P2.time_left() < next_poll_to_finish.time_left())
- next_poll_to_finish = P2
-
-/datum/controller/subsystem/ghost_spawns/get_stat_details()
- var/list/msg = list()
- msg += "Active: [length(currently_polling)] | Total: [total_polls]"
- if(next_poll_to_finish)
- msg += " | Next: [DisplayTimeText(next_poll_to_finish.time_left())] ([length(next_poll_to_finish.signed_up)] candidates)"
- return msg.Join("")
-
-// The datum that describes one instance of candidate polling
-/datum/candidate_poll
- var/role // The role the poll is for
- var/question // The question asked to observers
- var/duration // The duration of the poll
- var/list/signed_up // The players who signed up to this poll
- var/time_started // The world.time at which the poll was created
- var/finished = FALSE // Whether the polling is finished
- var/hash // Used to categorize in the alerts system
-
-/datum/candidate_poll/New(polled_role, polled_question, poll_duration)
- role = polled_role
- question = polled_question
- duration = poll_duration
- signed_up = list()
- time_started = world.time
- hash = copytext(md5("[question]_[role ? role : "0"]"), 1, 7)
- return ..()
-
-/**
- * Attempts to sign a (controlled) mob up
- *
- * Will fail if the mob is already signed up or the poll's timer ran out.
- * Does not check for eligibility
- * Arguments:
- * * M - The (controlled) mob to sign up
- * * silent - Whether no messages should appear or not. If not TRUE, signing up to this poll will also sign the mob up for identical polls
- */
-/datum/candidate_poll/proc/sign_up(mob/M, silent = FALSE)
- . = FALSE
- if(!HAS_TRAIT(M, TRAIT_RESPAWNABLE) || !M.key || !M.client)
- return
- if(M in signed_up)
- if(!silent)
- to_chat(M, "You have already signed up for this!")
- return
-
- if(time_left() <= 0)
- if(!silent)
- to_chat(M, "Sorry, you were too late for the consideration!")
- SEND_SOUND(M, sound('sound/machines/buzz-sigh.ogg'))
- return
-
- signed_up += M
- if(!silent)
- to_chat(M, "You have signed up for this role! A candidate will be picked randomly soon.")
- // Sign them up for any other polls with the same mob type
- for(var/existing_poll in SSghost_spawns.currently_polling)
- var/datum/candidate_poll/P = existing_poll
- if(src != P && hash == P.hash && !(M in P.signed_up))
- P.sign_up(M, TRUE)
-
- return TRUE
-
-/**
- * Attempts to remove a signed-up mob from a poll.
- *
- * Arguments:
- * * M - The mob to remove from the poll, if present.
- * * silent - If TRUE, no messages will be sent to M about their removal.
- */
-/datum/candidate_poll/proc/remove_candidate(mob/M, silent = FALSE)
- . = FALSE
- if(!HAS_TRAIT(M, TRAIT_RESPAWNABLE) || !M.key || !M.client)
- return
- if(!(M in signed_up))
- if(!silent)
- to_chat(M, "You aren't signed up for this!")
- return
-
- if(time_left() <= 0)
- if(!silent)
- to_chat(M, "It's too late to unregister yourself, selection has already begun!")
- return
-
- signed_up -= M
- if(!silent)
- to_chat(M, "You have been unregistered as a candidate for this role. You can freely sign up again before the poll ends.")
-
- for(var/existing_poll in SSghost_spawns.currently_polling)
- var/datum/candidate_poll/P = existing_poll
- if(src != P && hash == P.hash && (M in P.signed_up))
- P.remove_candidate(M, TRUE)
- return TRUE
-
-
-
-
-/**
- * Deletes any candidates who may have disconnected from the list
- */
-/datum/candidate_poll/proc/trim_candidates()
- listclearnulls(signed_up)
- for(var/mob in signed_up)
- var/mob/M = mob
- if(!M.key || !M.client)
- signed_up -= M
-
-/**
- * Returns the time left for a poll
- */
-/datum/candidate_poll/proc/time_left()
- return duration - (world.time - time_started)
diff --git a/code/controllers/subsystem/SSjobs.dm b/code/controllers/subsystem/SSjobs.dm
deleted file mode 100644
index 69c38a77817b5..0000000000000
--- a/code/controllers/subsystem/SSjobs.dm
+++ /dev/null
@@ -1,978 +0,0 @@
-SUBSYSTEM_DEF(jobs)
- name = "Jobs"
- init_order = INIT_ORDER_JOBS // 12
- wait = 5 MINUTES // Dont ever make this a super low value since EXP updates are calculated from this value
- runlevels = RUNLEVEL_GAME
- offline_implications = "Job playtime hours will no longer be logged. No immediate action is needed."
- cpu_display = SS_CPUDISPLAY_LOW
-
- //List of all jobs
- var/list/occupations = list()
- var/list/name_occupations = list() //Dict of all jobs, keys are titles
- var/list/type_occupations = list() //Dict of all jobs, keys are types
- var/list/prioritized_jobs = list() // List of jobs set to priority by HoP/Captain
- var/list/id_change_records = list() // List of all job transfer records
- var/probability_of_antag_role_restriction = 100 // Dict probability of a job rolling an antagonist role
- var/id_change_counter = 1
- //Players who need jobs
- var/list/unassigned = list()
- //Debug info
- var/list/job_debug = list()
-
- ///list of station departments and their associated roles and economy payments
- var/list/station_departments = list()
- /// Do we spawn everyone at shuttle due to late arivals?
- var/late_arrivals_spawning = FALSE
- /// Do we spawn people drunkenly due to the party last night?
- var/drunken_spawning = FALSE
- /// A list of minds that have failed to roll antagonist. Cleared when job selection finishes.
- var/list/failed_head_antag_roll = list()
-
-/datum/controller/subsystem/jobs/Initialize()
- if(!length(occupations))
- SetupOccupations()
- for(var/department_type in subtypesof(/datum/station_department))
- station_departments += new department_type()
- LoadJobs(FALSE)
-
-// Only fires every 5 minutes
-/datum/controller/subsystem/jobs/fire()
- if(!SSdbcore.IsConnected() || !GLOB.configuration.jobs.enable_exp_tracking)
- return
- batch_update_player_exp(announce = FALSE) // Set this to true if you ever want to inform players about their EXP gains
-
-/datum/controller/subsystem/jobs/proc/SetupOccupations(list/faction = list("Station"))
- occupations = list()
- var/list/all_jobs = subtypesof(/datum/job)
- if(!length(all_jobs))
- to_chat(world, "Error setting up jobs, no job datums found.")
- return 0
-
- for(var/J in all_jobs)
- var/datum/job/job = new J()
- if(!job)
- continue
- occupations += job
- name_occupations[job.title] = job
- type_occupations[J] = job
-
- return 1
-
-
-/datum/controller/subsystem/jobs/proc/Debug(text)
- job_debug.Add(text)
-
-/datum/controller/subsystem/jobs/proc/GetJob(rank)
- if(!length(occupations))
- SetupOccupations()
- return name_occupations[rank]
-
-/datum/controller/subsystem/jobs/proc/GetJobType(jobtype)
- if(!length(occupations))
- SetupOccupations()
- return type_occupations[jobtype]
-
-/datum/controller/subsystem/jobs/proc/GetPlayerAltTitle(mob/new_player/player, rank)
- return player.client.prefs.active_character.GetPlayerAltTitle(GetJob(rank))
-
-/datum/controller/subsystem/jobs/proc/AssignRole(mob/new_player/player, rank, latejoin = 0)
- Debug("Running AR, Player: [player], Rank: [rank], LJ: [latejoin]")
- if(player && player.mind && rank)
- var/datum/job/job = GetJob(rank)
- if(!job)
- return FALSE
- if(job.job_banned_gamemode)
- return FALSE
- if(jobban_isbanned(player, rank))
- return FALSE
- if(!job.player_old_enough(player.client))
- return FALSE
- if(job.get_exp_restrictions(player.client))
- return FALSE
- if(job.barred_by_disability(player.client))
- return FALSE
- if(job.barred_by_missing_limbs(player.client))
- return FALSE
-
- var/available = latejoin ? job.is_position_available() : job.is_spawn_position_available()
-
- if(available)
- Debug("Player: [player] is now Rank: [rank], JCP:[job.current_positions], JTP:[job.total_positions], JSP:[job.spawn_positions]")
- player.mind.assigned_role = rank
- player.mind.role_alt_title = GetPlayerAltTitle(player, rank)
-
- // JOB OBJECTIVES OH SHIT
- player.mind.job_objectives.Cut()
- for(var/objectiveType in job.required_objectives)
- new objectiveType(player.mind)
-
- unassigned -= player
- job.current_positions++
- SSblackbox.record_feedback("nested tally", "manifest", 1, list(rank, (latejoin ? "latejoin" : "roundstart")))
- return 1
-
- Debug("AR has failed, Player: [player], Rank: [rank]")
- return 0
-
-/datum/controller/subsystem/jobs/proc/FreeRole(rank, force = FALSE) //making additional slot on the fly
- var/datum/job/job = GetJob(rank)
- if(!job)
- return FALSE
- if(job.job_banned_gamemode)
- if(!force)
- return FALSE
- job.job_banned_gamemode = FALSE // If admins want to force it, they can reopen banned job slots
-
- if(job.current_positions >= job.total_positions && job.total_positions != -1)
- job.total_positions++
- return TRUE
- return FALSE
-
-/datum/controller/subsystem/jobs/proc/FindOccupationCandidates(datum/job/job, level, flag)
- Debug("Running FOC, Job: [job], Level: [level], Flag: [flag]")
- var/list/candidates = list()
- for(var/mob/new_player/player in unassigned)
- Debug(" - Player: [player] Banned: [jobban_isbanned(player, job.title)] Old Enough: [!job.player_old_enough(player.client)] AvInPlaytime: [job.get_exp_restrictions(player.client)] Flag && Be Special: [flag] && [player.client.prefs.be_special] Job Department: [player.client.prefs.active_character.GetJobDepartment(job, level)] Job Flag: [job.flag] Job Department Flag = [job.department_flag]")
- if(jobban_isbanned(player, job.title))
- Debug("FOC isbanned failed, Player: [player]")
- continue
- if(!job.player_old_enough(player.client))
- Debug("FOC player not old enough, Player: [player]")
- continue
- if(job.get_exp_restrictions(player.client))
- Debug("FOC player not enough playtime, Player: [player]")
- continue
- if(job.barred_by_disability(player.client))
- Debug("FOC player has disability rendering them ineligible for job, Player: [player]")
- continue
- if(job.barred_by_missing_limbs(player.client))
- Debug("FOC player has missing limbs rendering them ineligible for job, Player: [player]")
- continue
- if(flag && !(flag in player.client.prefs.be_special))
- Debug("FOC flag failed, Player: [player], Flag: [flag], ")
- continue
- if(player.mind && (job.title in player.mind.restricted_roles))
- Debug("FOC incompatbile with antagonist role, Player: [player]")
- continue
- if(player.client.prefs.active_character.GetJobDepartment(job, level) & job.flag)
- if(player.mind.special_role && player.mind && (job.title in SSticker.mode.single_antag_positions)) //We want to check if they want the job, before rolling the prob chance
- if((player.mind in SSjobs.failed_head_antag_roll) || !prob(probability_of_antag_role_restriction))
- Debug("FOC Failed probability of getting a second antagonist position in this job, Player: [player], Job:[job.title]")
- SSjobs.failed_head_antag_roll |= player.mind
- continue
- else
- probability_of_antag_role_restriction /= 10
- Debug("FOC pass, Player: [player], Level:[level]")
- candidates += player
- return candidates
-
-/datum/controller/subsystem/jobs/proc/GiveRandomJob(mob/new_player/player)
- Debug("GRJ Giving random job, Player: [player]")
- for(var/datum/job/job in shuffle(occupations))
- if(!job)
- continue
-
- if(istype(job, GetJob("Assistant"))) // We don't want to give him assistant, that's boring!
- continue
-
- if(job.title in GLOB.command_positions) //If you want a command position, select it!
- continue
-
- if(job.admin_only) // No admin positions either.
- continue
-
- if(job.mentor_only) // Neither for mentor positions
- continue
-
- if(jobban_isbanned(player, job.title))
- Debug("GRJ isbanned failed, Player: [player], Job: [job.title]")
- continue
-
- if(!job.player_old_enough(player.client))
- Debug("GRJ player not old enough, Player: [player]")
- continue
-
- if(job.get_exp_restrictions(player.client))
- Debug("GRJ player not enough playtime, Player: [player]")
- continue
-
- if(job.barred_by_disability(player.client))
- Debug("GRJ player has disability rendering them ineligible for job, Player: [player]")
- continue
-
- if(job.barred_by_missing_limbs(player.client))
- Debug("GRJ player has missing limbs rendering them ineligible for job, Player: [player]")
- continue
-
- if(player.mind && (job.title in player.mind.restricted_roles))
- Debug("GRJ incompatible with antagonist role, Player: [player], Job: [job.title]")
- continue
- if(player.mind.special_role && player.mind && (job.title in SSticker.mode.single_antag_positions))
- if((player.mind in SSjobs.failed_head_antag_roll) || !prob(probability_of_antag_role_restriction))
- Debug("GRJ Failed probability of getting a second antagonist position in this job, Player: [player], Job:[job.title]")
- SSjobs.failed_head_antag_roll |= player.mind
- continue
- else
- probability_of_antag_role_restriction /= 10
- if((job.current_positions < job.spawn_positions) || job.spawn_positions == -1)
- Debug("GRJ Random job given, Player: [player], Job: [job]")
- AssignRole(player, job.title)
- unassigned -= player
- break
-
-/datum/controller/subsystem/jobs/proc/ResetOccupations()
- for(var/mob/new_player/player in GLOB.player_list)
- if((player) && (player.mind))
- player.mind.assigned_role = null
- player.mind.special_role = null
- SetupOccupations()
- unassigned = list()
- return
-
-
-///This proc is called before the level loop of DivideOccupations() and will try to select a head, ignoring ALL non-head preferences for every level until it locates a head or runs out of levels to check
-/datum/controller/subsystem/jobs/proc/FillHeadPosition()
- for(var/level = 1 to 3)
- for(var/command_position in GLOB.command_positions)
- var/datum/job/job = GetJob(command_position)
- if(!job)
- continue
- var/list/candidates = FindOccupationCandidates(job, level)
- if(!length(candidates))
- continue
-
- var/list/filteredCandidates = list()
-
- for(var/mob/V in candidates)
- // Log-out during round-start? What a bad boy, no head position for you!
- if(!V.client)
- continue
- filteredCandidates += V
-
- if(!length(filteredCandidates))
- continue
-
- var/mob/new_player/candidate = pick(filteredCandidates)
- if(AssignRole(candidate, command_position))
- return 1
-
- return 0
-
-
-///This proc is called at the start of the level loop of DivideOccupations() and will cause head jobs to be checked before any other jobs of the same level
-/datum/controller/subsystem/jobs/proc/CheckHeadPositions(level)
- for(var/command_position in GLOB.command_positions)
- var/datum/job/job = GetJob(command_position)
- if(!job)
- continue
- var/list/candidates = FindOccupationCandidates(job, level)
- if(!length(candidates))
- continue
- var/mob/new_player/candidate = pick(candidates)
- AssignRole(candidate, command_position)
-
-
-/datum/controller/subsystem/jobs/proc/FillAIPosition()
- if(!GLOB.configuration.jobs.allow_ai)
- return FALSE
-
- var/ai_selected = 0
- var/datum/job/job = GetJob("AI")
- if(!job)
- return 0
-
- for(var/i = job.total_positions, i > 0, i--)
- for(var/level = 1 to 3)
- var/list/candidates = list()
- candidates = FindOccupationCandidates(job, level)
- if(length(candidates))
- var/mob/new_player/candidate = pick(candidates)
- if(AssignRole(candidate, "AI"))
- ai_selected++
- break
-
- if(ai_selected)
- return 1
-
- return 0
-
-
-/** Proc DivideOccupations
-* fills var "assigned_role" for all ready players.
-* This proc must not have any side effect besides of modifying "assigned_role".
-**/
-/datum/controller/subsystem/jobs/proc/DivideOccupations()
- // Lets roughly time this
- var/watch = start_watch()
- //Setup new player list and get the jobs list
- Debug("Running DO")
- if(!length(occupations))
- SetupOccupations()
-
- //Holder for Triumvirate is stored in the ticker, this just processes it
- if(SSticker)
- for(var/datum/job/ai/A in occupations)
- if(SSticker.triai)
- A.spawn_positions = 3
-
- //Get the players who are ready
- for(var/mob/new_player/player in GLOB.player_list)
- if(player.ready && player.mind && !player.mind.assigned_role)
- unassigned += player
-
- Debug("DO, Len: [length(unassigned)]")
- if(!length(unassigned))
- return FALSE
-
- //Shuffle players and jobs
- unassigned = shuffle(unassigned)
-
- HandleFeedbackGathering()
-
- //People who wants to be assistants, sure, go on.
- Debug("DO, Running Assistant Check 1")
- var/datum/job/ast = new /datum/job/assistant()
- var/list/assistant_candidates = FindOccupationCandidates(ast, 3)
- Debug("AC1, Candidates: [length(assistant_candidates)]")
- for(var/mob/new_player/player in assistant_candidates)
- Debug("AC1 pass, Player: [player]")
- AssignRole(player, "Assistant")
- assistant_candidates -= player
- Debug("DO, AC1 end")
-
- //Select one head
- Debug("DO, Running Head Check")
- FillHeadPosition()
- Debug("DO, Head Check end")
-
- //Check for an AI
- Debug("DO, Running AI Check")
- FillAIPosition()
- Debug("DO, AI Check end")
-
- //Other jobs are now checked
- Debug("DO, Running Standard Check")
-
-
- // New job giving system by Donkie
- // This will cause lots of more loops, but since it's only done once it shouldn't really matter much at all.
- // Hopefully this will add more randomness and fairness to job giving.
-
- // Loop through all levels from high to low
- var/list/shuffledoccupations = shuffle(occupations)
- for(var/level = 1 to 3)
- //Check the head jobs first each level
- CheckHeadPositions(level)
-
- // Loop through all unassigned players
- for(var/mob/new_player/player in unassigned)
-
- // Loop through all jobs
- for(var/datum/job/job in shuffledoccupations) // SHUFFLE ME BABY
- if(!job)
- continue
-
- if(jobban_isbanned(player, job.title))
- Debug("DO isbanned failed, Player: [player], Job:[job.title]")
- continue
-
- if(!job.player_old_enough(player.client))
- Debug("DO player not old enough, Player: [player], Job:[job.title]")
- continue
-
- if(job.get_exp_restrictions(player.client))
- Debug("DO player not enough playtime, Player: [player], Job:[job.title]")
- continue
-
- if(job.barred_by_disability(player.client))
- Debug("DO player has disability rendering them ineligible for job, Player: [player], Job:[job.title]")
- continue
-
- if(job.barred_by_missing_limbs(player.client))
- Debug("DO player has missing limbs rendering them ineligible for job, Player: [player], Job:[job.title]")
- continue
-
- if(player.mind && (job.title in player.mind.restricted_roles))
- Debug("DO incompatible with antagonist role, Player: [player], Job:[job.title]")
- continue
- // If the player wants that job on this level, then try give it to him.
- if(player.client.prefs.active_character.GetJobDepartment(job, level) & job.flag)
- // If the job isn't filled
- if(job.is_spawn_position_available())
- if(player.mind.special_role && player.mind && (job.title in SSticker.mode.single_antag_positions)) //We want to check if they want the job, before rolling the prob chance
- if((player.mind in SSjobs.failed_head_antag_roll) || !prob(probability_of_antag_role_restriction))
- Debug("DO Failed probability of getting a second antagonist position in this job, Player: [player], Job:[job.title]")
- SSjobs.failed_head_antag_roll |= player.mind
- continue
- else
- probability_of_antag_role_restriction /= 10
- Debug("DO pass, Player: [player], Level:[level], Job:[job.title]")
- Debug(" - Job Flag: [job.flag] Job Department: [player.client.prefs.active_character.GetJobDepartment(job, level)] Job Current Pos: [job.current_positions] Job Spawn Positions = [job.spawn_positions]")
- AssignRole(player, job.title)
- unassigned -= player
- break
-
- // Hand out random jobs to the people who didn't get any in the last check
- // Also makes sure that they got their preference correct
- for(var/mob/new_player/player in unassigned)
- if(player.client.prefs.active_character.alternate_option == GET_RANDOM_JOB)
- GiveRandomJob(player)
-
- Debug("DO, Standard Check end")
-
- Debug("DO, Running AC2")
-
- // Antags, who have to get in, come first
- for(var/mob/new_player/player in unassigned)
- if(player.mind.special_role)
- if(player.client.prefs.active_character.alternate_option != BE_ASSISTANT)
- GiveRandomJob(player)
- if(player in unassigned)
- AssignRole(player, "Assistant")
- else
- AssignRole(player, "Assistant")
- else if(length(player.mind.restricted_roles))
- stack_trace("A player with `restricted_roles` had no `special_role`. They are likely an antagonist, but failed to spawn in.") // this can be fixed by assigning a special_role in pre_setup of the gamemode
- message_admins("A player mind ([player.mind]) is likely an antagonist, but may have failed to spawn in! Please report this to coders.")
-
- // Then we assign what we can to everyone else.
- for(var/mob/new_player/player in unassigned)
- if(player.client.prefs.active_character.alternate_option == BE_ASSISTANT)
- Debug("AC2 Assistant located, Player: [player]")
- AssignRole(player, "Assistant")
- else if(player.client.prefs.active_character.alternate_option == RETURN_TO_LOBBY)
- player.ready = FALSE
- unassigned -= player
-
- log_debug("Dividing Occupations took [stop_watch(watch)]s")
- failed_head_antag_roll = list()
- return TRUE
-
-/datum/controller/subsystem/jobs/proc/AssignRank(mob/living/carbon/human/H, rank, joined_late = FALSE)
- if(!H)
- return null
- var/datum/job/job = GetJob(rank)
-
- H.job = rank
-
- var/alt_title = null
-
- if(H.mind)
- H.mind.assigned_role = rank
- alt_title = H.mind.role_alt_title
-
- CreateMoneyAccount(H, rank, job)
-
- var/list/L = list()
- L.Add("Your role on the station is: [alt_title ? alt_title : rank].")
- L.Add("You answer directly to [job.supervisors]. Special circumstances may change this.")
- L.Add("For more information on how the station works, see [wiki_link("Standard_Operating_Procedure", "Standard Operating Procedure (SOP)")].")
- if(job.job_department_flags & DEP_FLAG_SERVICE)
- L.Add("As a member of Service, make sure to read up on your [wiki_link("Standard_Operating_Procedure_(Service)", "Department SOP")].")
- if(job.job_department_flags & DEP_FLAG_SUPPLY)
- L.Add("As a member of Supply, make sure to read up on your [wiki_link("Standard_Operating_Procedure_(Supply)", "Department SOP")].")
- if(job.job_department_flags == DEP_FLAG_COMMAND) // Check if theyre only command, like captain/hop/bs/ntrep, to not spam their chatbox
- L.Add("As an important member of Command, read up on your [wiki_link("Standard_Operating_Procedure_(Command)", "Department SOP")].")
- if(job.job_department_flags & DEP_FLAG_LEGAL)
- L.Add("Your job requires complete knowledge of [wiki_link("Space Law", "Space Law")] and [wiki_link("Legal_Standard_Operating_Procedure", "Legal Standard Operating Procedure")].")
- if(job.job_department_flags & DEP_FLAG_ENGINEERING)
- L.Add("As a member of Engineering, make sure to read up on your [wiki_link("Standard_Operating_Procedure_(Engineering)", "Department SOP")].")
- if(job.job_department_flags & DEP_FLAG_MEDICAL)
- L.Add("As a member of Medbay, make sure to read up on your [wiki_link("Standard_Operating_Procedure_(Medical)", "Department SOP")].")
- if(job.job_department_flags & DEP_FLAG_SCIENCE) // geneticist gets both, yeah sure why not
- L.Add("As a member of Science, make sure to read up on your [wiki_link("Standard_Operating_Procedure_(Science)", "Department SOP")].")
- if(job.job_department_flags & DEP_FLAG_SECURITY)
- L.Add("As a member of Security, you are to know [wiki_link("Space Law", "Space Law")] and [wiki_link("Legal_Standard_Operating_Procedure", "Legal Standard Operating Procedure")], as well as your [wiki_link("Standard_Operating_Procedure_(Security)", "Department SOP")].")
- if(job.req_admin_notify)
- L.Add("You are playing a job that is important for the game progression. If you have to disconnect, please go to cryo and inform command. If you are unable to do so, please notify the admins via adminhelp.")
- L.Add(" If you need help, check the [wiki_link("Main_Page", "wiki")] or use Mentorhelp(F1)!")
- if(job.important_information)
- L.Add("[job.important_information]")
-
- to_chat(H, chat_box_green(L.Join(" ")))
-
- // If the job has objectives, announce those too
- if(length(H.mind.job_objectives))
- var/list/objectives_message = list()
- var/counter = 1
- for(var/datum/job_objective/objective as anything in H.mind.job_objectives)
- objectives_message.Add("Objective #[counter]: [objective.objective_name]")
- objectives_message.Add("[objective.description] ")
- counter++
- to_chat(H, chat_box_notice(objectives_message.Join(" ")))
-
- return H
-
-/datum/controller/subsystem/jobs/proc/EquipRank(mob/living/carbon/human/H, rank, joined_late = 0) // Equip and put them in an area
- if(!H)
- return null
-
- var/datum/job/job = GetJob(rank)
-
- H.job = rank
-
- if(!joined_late && !late_arrivals_spawning)
- var/turf/T = null
- var/obj/S = null
- var/list/landmarks = GLOB.landmarks_list
- if(drunken_spawning)
- landmarks = shuffle(landmarks) //Shuffle it so it's random
-
- for(var/obj/effect/landmark/start/sloc in landmarks)
- if(sloc.name != rank && !drunken_spawning)
- continue
- if(locate(/mob/living) in sloc.loc)
- continue
- if(drunken_spawning && sloc.name == "AI")
- continue
- S = sloc
- break
- if(!S)
- S = locate("start*[rank]") // use old stype
- if(!S) // still no spawn, fall back to the arrivals shuttle
- for(var/turf/TS in get_area_turfs(/area/shuttle/arrival))
- if(!TS.density)
- var/clear = 1
- for(var/obj/O in TS)
- if(O.density)
- clear = 0
- break
- if(clear)
- T = TS
- continue
-
- if(isturf(S))
- T = S
- else if(istype(S, /obj/effect/landmark/start) && isturf(S.loc))
- T = S.loc
-
- if(T)
- H.forceMove(T)
- // Moving wheelchair if they have one
- if(H.buckled && istype(H.buckled, /obj/structure/chair/wheelchair))
- H.buckled.forceMove(H.loc)
- H.buckled.dir = H.dir
-
- if(job)
- var/new_mob = job.equip(H)
- if(ismob(new_mob))
- H = new_mob
-
- if(job && H)
- job.after_spawn(H)
-
- //Gives glasses to the vision impaired
- if(HAS_TRAIT(H, TRAIT_NEARSIGHT))
- var/equipped = H.equip_to_slot_or_del(new /obj/item/clothing/glasses/regular(H), ITEM_SLOT_EYES)
- if(equipped != 1)
- var/obj/item/clothing/glasses/G = H.glasses
- if(istype(G) && !G.prescription)
- G.upgrade_prescription()
- H.update_nearsighted_effects()
-
- if(joined_late || job.admin_only)
- H.create_log(MISC_LOG, "Spawned as \an [H.dna?.species ? H.dna.species : "Undefined species"] named [H]. [joined_late ? "Joined during the round" : "Roundstart joined"] as job: [rank].", force_no_usr_check=TRUE)
- addtimer(CALLBACK(src, TYPE_PROC_REF(/datum/controller/subsystem/jobs, show_location_blurb), H.client, H.mind), 1 SECONDS) //Moment for minds to boot up / people to load in
- return H
- if(late_arrivals_spawning)
- H.forceMove(pick(GLOB.latejoin))
- if(drunken_spawning)
- var/obj/item/organ/internal/liver/L
- var/liver_multiplier = 1
- L = H.get_int_organ(/obj/item/organ/internal/liver)
- if(L)
- liver_multiplier = L.alcohol_intensity
- if(isslimeperson(H) || isrobot(H))
- liver_multiplier = 5
- H.Sleeping(5 SECONDS)
- H.Drunk((2 / liver_multiplier) MINUTES)
- H.create_log(MISC_LOG, "Spawned as \an [H.dna?.species ? H.dna.species : "Undefined species"] named [H]. Roundstart joined as job: [rank].", force_no_usr_check=TRUE)
- addtimer(CALLBACK(src, TYPE_PROC_REF(/datum/controller/subsystem/jobs, show_location_blurb), H.client, H.mind), 1 SECONDS) //Moment for minds to boot up / people to load in
- return H
-
-/datum/controller/subsystem/jobs/proc/LoadJobs(highpop = FALSE) //ran during round setup, reads info from jobs list
- if(!GLOB.configuration.jobs.enable_job_amount_overrides)
- return FALSE
-
- var/list/joblist = list()
-
- if(highpop)
- joblist = GLOB.configuration.jobs.highpop_job_map.Copy()
- else
- joblist = GLOB.configuration.jobs.lowpop_job_map.Copy()
-
- for(var/job in joblist)
- // Key: name | Value: Amount
- var/datum/job/J = GetJob(job)
- if(!J)
- stack_trace("`[job]` not found while setting max slots. Check for misspellings or alternate titles")
- continue
- J.total_positions = text2num(joblist[job])
- J.spawn_positions = text2num(joblist[job])
-
- if(job == "AI" || job == "Cyborg") //I dont like this here but it will do for now
- J.total_positions = 0
-
- return TRUE
-
-
-/datum/controller/subsystem/jobs/proc/HandleFeedbackGathering()
- for(var/datum/job/job in occupations)
-
- var/high = 0 //high
- var/medium = 0 //medium
- var/low = 0 //low
- var/never = 0 //never
- var/banned = 0 //banned
- var/young = 0 //account too young
- var/disabled = FALSE //has disability rendering them ineligible
- for(var/mob/new_player/player in GLOB.player_list)
- if(!(player.ready && player.mind && !player.mind.assigned_role))
- continue //This player is not ready
- if(jobban_isbanned(player, job.title))
- banned++
- continue
- if(!job.player_old_enough(player.client))
- young++
- continue
- if(job.get_exp_restrictions(player.client))
- young++
- continue
- if(job.barred_by_disability(player.client) || job.barred_by_missing_limbs(player.client))
- disabled++
- continue
- if(player.client.prefs.active_character.GetJobDepartment(job, 1) & job.flag)
- high++
- else if(player.client.prefs.active_character.GetJobDepartment(job, 2) & job.flag)
- medium++
- else if(player.client.prefs.active_character.GetJobDepartment(job, 3) & job.flag)
- low++
- else never++ //not selected
-
- SSblackbox.record_feedback("nested tally", "job_preferences", high, list("[job.title]", "high"))
- SSblackbox.record_feedback("nested tally", "job_preferences", medium, list("[job.title]", "medium"))
- SSblackbox.record_feedback("nested tally", "job_preferences", low, list("[job.title]", "low"))
- SSblackbox.record_feedback("nested tally", "job_preferences", never, list("[job.title]", "never"))
- SSblackbox.record_feedback("nested tally", "job_preferences", banned, list("[job.title]", "banned"))
- SSblackbox.record_feedback("nested tally", "job_preferences", young, list("[job.title]", "young"))
- SSblackbox.record_feedback("nested tally", "job_preferences", disabled, list("[job.title]", "disabled"))
-
-//fuck
-/datum/controller/subsystem/jobs/proc/CreateMoneyAccount(mob/living/H, rank, datum/job/job)
- if(job && !job.has_bank_account)
- return
- var/starting_balance = job?.department_account_access ? COMMAND_MEMBER_STARTING_BALANCE : CREW_MEMBER_STARTING_BALANCE
- var/datum/money_account/account = GLOB.station_money_database.create_account(H.real_name, starting_balance, ACCOUNT_SECURITY_ID, "NAS Trurl Accounting", TRUE)
-
- for(var/datum/job_objective/objective as anything in H.mind.job_objectives)
- objective.owner_account = account
-
- H.mind.store_memory("Your account number is: #[account.account_number] Your account pin is: [account.account_pin]")
- H.mind.set_initial_account(account)
-
- to_chat(H, "As an employee of Nanotrasen you will receive a paycheck of $[account.payday_amount] credits every 30 minutes")
- to_chat(H, "Your account number is: [account.account_number], your account pin is: [account.account_pin]")
-
- if(!job) //if their job datum is null (looking at you ERTs...), we don't need to do anything past this point
- return
-
- //add them to their department datum, (this relates a lot to money account I promise)
- var/list/users_departments = get_departments_from_job(job.title)
- for(var/datum/station_department/department as anything in users_departments)
- var/datum/department_member/member = new
- member.name = H.real_name
- member.role = job.title
- member.set_member_account(account) //we need to set this through a proc so we can register signals
- member.can_approve_crates = job?.department_account_access
- department.members += member
-
- // If they're head, give them the account info for their department
- if(!job.department_account_access)
- return
-
- announce_department_accounts(users_departments, H, job)
-
-/datum/controller/subsystem/jobs/proc/announce_department_accounts(users_departments, mob/living/H, datum/job/job)
- var/remembered_info = ""
- for(var/datum/station_department/department as anything in users_departments)
- if(job.title != department.head_of_staff)
- continue
- var/datum/money_account/department_account = department.department_account
- if(!department_account)
- return
-
- remembered_info += "As a head of staff you have access to your department's money account through your PDA's NanoBank or a station ATM "
- remembered_info += "The [department.department_name] department's account number is: #[department_account.account_number] "
- remembered_info += "The [department.department_name] department's account pin is: [department_account.account_pin] "
- remembered_info += "Your department's account funds are: $[department_account.credit_balance] "
-
- H.mind.store_memory(remembered_info)
- to_chat(H, "Your department will receive a $[department_account.payday_amount] credit stipend every 30 minutes")
- to_chat(H, "The [department.department_name] department's account number is: #[department_account.account_number], Your department's account pin is: [department_account.account_pin]")
-
-/datum/controller/subsystem/jobs/proc/format_jobs_for_id_computer(obj/item/card/id/tgtcard)
- var/list/jobs_to_formats = list()
- if(tgtcard)
- var/mob/M = tgtcard.getPlayer()
- for(var/datum/job/job in occupations)
- if(tgtcard.rank && tgtcard.rank == job.title)
- jobs_to_formats[job.title] = "green" // the job they already have is pre-selected
- else if(tgtcard.assignment == "Demoted" || tgtcard.assignment == "Terminated")
- jobs_to_formats[job.title] = "grey"
- else if(!job.transfer_allowed)
- jobs_to_formats[job.title] = "grey" // jobs which shouldnt be transferred into for whatever reason, likely due to high hour requirements
- else if((job.title in GLOB.command_positions) && istype(M) && M.client && job.get_exp_restrictions(M.client))
- jobs_to_formats[job.title] = "grey" // command jobs which are playtime-locked and not unlocked for this player are discouraged
- else if(job.total_positions && !job.current_positions && job.title != "Assistant")
- jobs_to_formats[job.title] = "teal" // jobs with nobody doing them at all are encouraged
- else if(job.total_positions >= 0 && job.current_positions >= job.total_positions)
- jobs_to_formats[job.title] = "grey" // jobs that are full (no free positions) are discouraged
- if(tgtcard.assignment == "Demoted" || tgtcard.assignment == "Terminated")
- jobs_to_formats["Custom"] = "grey"
- return jobs_to_formats
-
-
-
-/datum/controller/subsystem/jobs/proc/log_job_transfer(transferee, oldvalue, newvalue, whodidit, reason)
- id_change_records["[id_change_counter]"] = list(
- "transferee" = transferee,
- "oldvalue" = oldvalue,
- "newvalue" = newvalue,
- "whodidit" = whodidit,
- "timestamp" = station_time_timestamp(),
- "reason" = reason
- )
- id_change_counter++
-
-/datum/controller/subsystem/jobs/proc/slot_job_transfer(oldtitle, newtitle)
- var/datum/job/oldjobdatum = SSjobs.GetJob(oldtitle)
- var/datum/job/newjobdatum = SSjobs.GetJob(newtitle)
- if(istype(oldjobdatum) && oldjobdatum.current_positions > 0 && istype(newjobdatum))
- if(!(oldjobdatum.title in GLOB.command_positions) && !(newjobdatum.title in GLOB.command_positions))
- oldjobdatum.current_positions--
- newjobdatum.current_positions++
-
-/datum/controller/subsystem/jobs/proc/notify_dept_head(jobtitle, antext)
- // Used to notify the department head of jobtitle X that their employee was brigged, demoted or terminated
- if(!jobtitle || !antext)
- return
- var/datum/job/tgt_job = GetJob(jobtitle)
- if(!tgt_job)
- return
- if(!length(tgt_job.department_head))
- return
- var/boss_title = tgt_job.department_head[1]
- var/obj/item/pda/target_pda
- for(var/obj/item/pda/check_pda in GLOB.PDAs)
- if(check_pda.ownrank == boss_title)
- target_pda = check_pda
- break
- if(!target_pda)
- return
- var/datum/data/pda/app/messenger/PM = target_pda.find_program(/datum/data/pda/app/messenger)
- if(PM && PM.can_receive())
- PM.notify("Automated Notification: \"[antext]\" (Unable to Reply)", 0) // the 0 means don't make the PDA flash
-
-/datum/controller/subsystem/jobs/proc/notify_by_name(target_name, antext)
- // Used to notify a specific crew member based on their real_name
- if(!target_name || !antext)
- return
- var/obj/item/pda/target_pda
- for(var/obj/item/pda/check_pda in GLOB.PDAs)
- if(check_pda.owner == target_name)
- target_pda = check_pda
- break
- if(!target_pda)
- return
- var/datum/data/pda/app/messenger/PM = target_pda.find_program(/datum/data/pda/app/messenger)
- if(PM && PM.can_receive())
- PM.notify("Automated Notification: \"[antext]\" (Unable to Reply)", 0) // the 0 means don't make the PDA flash
-
-/datum/controller/subsystem/jobs/proc/format_job_change_records(centcom)
- var/list/formatted = list()
- for(var/thisid in id_change_records)
- var/thisrecord = id_change_records[thisid]
- if(thisrecord["deletedby"] && !centcom)
- continue
- var/list/newlist = list()
- for(var/lkey in thisrecord)
- newlist[lkey] = thisrecord[lkey]
- formatted.Add(list(newlist))
- return formatted
-
-
-/datum/controller/subsystem/jobs/proc/delete_log_records(sourceuser, delete_all)
- . = 0
- if(!sourceuser)
- return
- var/list/new_id_change_records = list()
- for(var/thisid in id_change_records)
- var/thisrecord = id_change_records[thisid]
- if(!thisrecord["deletedby"])
- if(delete_all || thisrecord["whodidit"] == sourceuser)
- thisrecord["deletedby"] = sourceuser
- .++
- new_id_change_records["[id_change_counter]"] = thisrecord
- id_change_counter++
- id_change_records = new_id_change_records
-
-// This proc will update all players EXP at once. It will calculate amount of time to add dynamically based on the SS fire time.
-/datum/controller/subsystem/jobs/proc/batch_update_player_exp(announce = FALSE)
- // Right off the bat
- var/start_time = start_watch()
- // First calculate minutes
- var/divider = 10 // By default, 10 deciseconds in 1 second
- if(flags & SS_TICKER)
- divider = 20 // If this SS ever gets made into a ticker SS, account for that
-
- var/minutes = (wait / divider) / 60 // Calculate minutes based on the SS wait time (How often this proc fires)
-
- // Step 1: Get us a list of clients to process
- var/list/client/clients_to_process = GLOB.clients.Copy() // This is copied so that clients joining in the middle of this dont break things
- Debug("Starting EXP update for [length(clients_to_process)] clients. (Adding [minutes] minutes)")
-
- var/list/datum/db_query/select_queries = list() // List of SELECT queries to mass grab EXP.
-
- for(var/i in clients_to_process)
- var/client/C = i
- if(!C)
- continue // If a client logs out in the middle of this
-
- var/datum/db_query/exp_read = SSdbcore.NewQuery(
- "SELECT exp FROM player WHERE ckey=:ckey",
- list("ckey" = C.ckey)
- )
-
- select_queries[C.ckey] = exp_read
-
- var/list/read_records = list()
- // Explanation for parameters:
- // TRUE: We want warnings if these fail
- // FALSE: Do NOT qdel() queries here, otherwise they wont be read. At all.
- // TRUE: This is an assoc list, so it needs to prepare for that
- // FALSE: We dont want to logspam
- SSdbcore.MassExecute(select_queries, TRUE, FALSE, TRUE, FALSE) // Batch execute so we can take advantage of async magic
-
- for(var/i in clients_to_process)
- var/client/C = i
- if(!C)
- continue // If a client logs out in the middle of this
-
- if(select_queries[C.ckey]) // This check should not be necessary, but I am paranoid
- while(select_queries[C.ckey].NextRow())
- read_records[C.ckey] = params2list(select_queries[C.ckey].item[1])
-
- QDEL_LIST_ASSOC_VAL(select_queries) // Clean stuff up
-
- var/list/play_records = list()
-
- var/list/datum/db_query/player_update_queries = list() // List of queries to update player EXP
- var/list/datum/db_query/playtime_history_update_queries = list() // List of queries to update the playtime history table
-
- for(var/i in clients_to_process)
- var/client/C = i
- if(!C)
- continue // If a client logs out in the middle of this
- // Get us a container
- play_records[C.ckey] = list()
- for(var/rtype in GLOB.exp_jobsmap)
- if(text2num(read_records[C.ckey][rtype]))
- play_records[C.ckey][rtype] = text2num(read_records[C.ckey][rtype])
- else
- play_records[C.ckey][rtype] = 0
-
-
- var/myrole
- if(C.mob.mind)
- if(C.mob.mind.playtime_role)
- myrole = C.mob.mind.playtime_role
- else if(C.mob.mind.assigned_role)
- myrole = C.mob.mind.assigned_role
-
- // Track all the added ammounts for a mega update query
- var/list/added_differential = list(
- EXP_TYPE_LIVING = 0,
- EXP_TYPE_CREW = 0,
- EXP_TYPE_SPECIAL = 0,
- EXP_TYPE_GHOST = 0,
- EXP_TYPE_COMMAND = 0,
- EXP_TYPE_ENGINEERING = 0,
- EXP_TYPE_MEDICAL = 0,
- EXP_TYPE_SCIENCE = 0,
- EXP_TYPE_SUPPLY = 0,
- EXP_TYPE_SECURITY = 0,
- EXP_TYPE_SILICON = 0,
- EXP_TYPE_SERVICE = 0
- )
- if(C.mob.stat == CONSCIOUS && myrole)
- play_records[C.ckey][EXP_TYPE_LIVING] += minutes
- added_differential[EXP_TYPE_LIVING] += minutes
-
- if(announce)
- to_chat(C.mob, "You got: [minutes] Living EXP!")
-
- for(var/category in GLOB.exp_jobsmap)
- if(GLOB.exp_jobsmap[category]["titles"])
- if(myrole in GLOB.exp_jobsmap[category]["titles"])
- play_records[C.ckey][category] += minutes
- added_differential[category] += minutes
- if(announce)
- to_chat(C.mob, "You got: [minutes] [category] EXP!")
-
- if(C.mob.mind.special_role)
- play_records[C.ckey][EXP_TYPE_SPECIAL] += minutes
- if(announce)
- to_chat(C.mob, "You got: [minutes] Special EXP!")
-
- else if(isobserver(C.mob))
- play_records[C.ckey][EXP_TYPE_GHOST] += minutes
- added_differential[EXP_TYPE_GHOST] += minutes
- if(announce)
- to_chat(C.mob, "You got: [minutes] Ghost EXP!")
- else
- continue
-
- var/new_exp = list2params(play_records[C.ckey])
-
- C.prefs.exp = new_exp
-
- var/datum/db_query/update_query = SSdbcore.NewQuery(
- "UPDATE player SET exp =:newexp, lastseen=NOW() WHERE ckey=:ckey",
- list(
- "newexp" = new_exp,
- "ckey" = C.ckey
- )
- )
-
- player_update_queries += update_query
-
- // This gets hellish
- var/datum/db_query/update_query_history = SSdbcore.NewQuery({"
- INSERT INTO playtime_history (ckey, date, time_living, time_crew, time_special, time_ghost, time_command, time_engineering, time_medical, time_science, time_supply, time_security, time_silicon, time_service)
- VALUES (:ckey, CURDATE(), :addedliving, :addedcrew, :addedspecial, :addedghost, :addedcommand, :addedengineering, :addedmedical, :addedscience, :addedsupply, :addedsecurity, :addedsilicon, :addedservice)
- ON DUPLICATE KEY UPDATE time_living=time_living + VALUES(time_living), time_crew=time_crew + VALUES(time_crew), time_crew=time_special + VALUES(time_special), time_ghost=time_ghost + VALUES(time_ghost), time_command=time_command + VALUES(time_command), time_engineering=time_engineering + VALUES(time_engineering), time_medical=time_medical + VALUES(time_medical), time_science=time_science + VALUES(time_science), time_supply=time_supply + VALUES(time_supply), time_security=time_security + VALUES(time_security), time_silicon=time_silicon + VALUES(time_silicon), time_service=time_service + VALUES(time_service)"},
- list(
- "ckey" = C.ckey,
- "addedliving" = added_differential[EXP_TYPE_LIVING],
- "addedcrew" = added_differential[EXP_TYPE_CREW],
- "addedspecial" = added_differential[EXP_TYPE_SPECIAL],
- "addedghost" = added_differential[EXP_TYPE_GHOST],
- "addedcommand" = added_differential[EXP_TYPE_COMMAND],
- "addedengineering" = added_differential[EXP_TYPE_ENGINEERING],
- "addedmedical" = added_differential[EXP_TYPE_MEDICAL],
- "addedscience" = added_differential[EXP_TYPE_SCIENCE],
- "addedsupply" = added_differential[EXP_TYPE_SUPPLY],
- "addedsecurity" = added_differential[EXP_TYPE_SECURITY],
- "addedsilicon" = added_differential[EXP_TYPE_SILICON],
- "addedservice" = added_differential[EXP_TYPE_SERVICE]
- )
- )
-
- playtime_history_update_queries += update_query_history
-
-
- // warn=TRUE, qdel=TRUE, assoc=FALSE, log=FALSE
- SSdbcore.MassExecute(player_update_queries, TRUE, TRUE, FALSE, FALSE) // Batch execute so we can take advantage of async magic
- SSdbcore.MassExecute(playtime_history_update_queries, TRUE, TRUE, FALSE, FALSE)
-
- Debug("Successfully updated all EXP data in [stop_watch(start_time)]s")
diff --git a/code/controllers/subsystem/SSredis.dm b/code/controllers/subsystem/SSredis.dm
deleted file mode 100644
index e617d3abf54d7..0000000000000
--- a/code/controllers/subsystem/SSredis.dm
+++ /dev/null
@@ -1,137 +0,0 @@
-SUBSYSTEM_DEF(redis)
- name = "Redis"
- init_order = INIT_ORDER_REDIS
- runlevels = RUNLEVELS_DEFAULT | RUNLEVEL_LOBBY // ALL THE THINGS
- wait = 1
- flags = SS_TICKER // Every tick
- /// Are we connected
- var/connected = FALSE
- /// Amount of subscribed channels on the redis server
- var/list/subbed_channels = list()
- /// Message queue (If messages are sent before the SS has init'd)
- var/list/datum/redis_message/queue = list()
- offline_implications = "The server will no longer be able to send or receive redis messages. Shuttle call recommended (Potential server crash inbound)."
- cpu_display = SS_CPUDISPLAY_LOW
-
-// SS meta procs
-/datum/controller/subsystem/redis/get_stat_details()
- return "S:[length(subbed_channels)] | Q:[length(queue)] | C:[connected ? "Y" : "N"]"
-
-/datum/controller/subsystem/redis/Initialize()
- // Connect to cappuccino
- connect()
-
- if(connected)
- // Loop efficiency doesnt matter here. It runs once and likely wont have any events in
- for(var/datum/redis_message/RM in queue)
- publish(RM.channel, RM.message)
-
- // Setup all callbacks
- for(var/cb in subtypesof(/datum/redis_callback))
- var/datum/redis_callback/RCB = new cb()
- if(isnull(RCB.channel))
- stack_trace("[RCB.type] has no channel set!")
- continue
-
- if(RCB.channel in subbed_channels)
- stack_trace("Attempted to subscribe to the channel '[RCB.channel]' from [RCB.type] twice!")
-
- rustg_redis_subscribe(RCB.channel)
- subbed_channels[RCB.channel] = RCB
-
- // Send our presence to required channels
- var/list/presence_data = list()
- presence_data["author"] = "system"
- presence_data["source"] = GLOB.configuration.system.instance_id
- presence_data["message"] = "Connected at `[SQLtime()]` during round [GLOB.round_id]"
-
- var/presence_text = json_encode(presence_data)
-
- for(var/channel in list("byond.asay", "byond.msay")) // Channels to announce to
- publish(channel, presence_text)
-
- // Report detailed presence info to system
- var/list/presence_data_2 = list()
- presence_data_2["source"] = GLOB.configuration.system.instance_id
- presence_data_2["round_id"] = GLOB.round_id
- presence_data_2["event"] = "server_restart"
- publish("byond.system", json_encode(presence_data_2))
-
- var/amount_registered = length(subbed_channels)
- log_startup_progress("Registered [amount_registered] callback[amount_registered == 1 ? "" : "s"].")
-
-/datum/controller/subsystem/redis/fire()
- check_messages()
-
-
-// Redis integration stuff
-/datum/controller/subsystem/redis/proc/connect()
- if(GLOB.configuration.redis.enabled)
- #ifndef GAME_TESTS // CI uses linux so dont flag up a fail there
- if(world.system_type == UNIX)
- stack_trace("SSredis has known to be very buggy when running on Linux with random dropouts ocurring due to interrupted syscalls. You have been warned!")
- #endif
-
- var/conn_failed = rustg_redis_connect(GLOB.configuration.redis.connstring)
- if(conn_failed)
- log_startup_progress("Failed to connect to redis. Please inform the server host.")
- SEND_TEXT(world.log, "Redis connection failure: [conn_failed]")
- return
-
- connected = TRUE
-
-/datum/controller/subsystem/redis/proc/disconnect()
- rustg_redis_disconnect()
- connected = FALSE
-
-/datum/controller/subsystem/redis/proc/check_messages()
- var/raw_data = rustg_redis_get_messages()
- var/list/usable_data
-
- try // Did you know byond had try catch?
- usable_data = json_decode(raw_data)
- catch
- message_admins("Failed to deserialise a redis message | Please inform the server host.")
- log_debug("Redis raw data: [raw_data]")
- return
-
- for(var/channel in usable_data)
- if(channel == RUSTG_REDIS_ERROR_CHANNEL)
- var/redis_error_data = usable_data[channel]
- var/error_str
- if(islist(redis_error_data))
- error_str = json_encode(redis_error_data)
- else
- error_str = redis_error_data
-
- message_admins("Redis error: [error_str] | Please inform the server host.") // uh oh
- log_game("Redis error: [error_str]")
- continue
- // Check its an actual channel
- if(!(channel in subbed_channels))
- stack_trace("Received a message on the channel '[channel]' when we arent subscribed to it. What the heck?")
- continue
-
- var/datum/redis_callback/RCB = subbed_channels[channel]
- for(var/message in usable_data[channel])
- RCB.on_message(message)
-
-/datum/controller/subsystem/redis/proc/publish(channel, message)
- // If we arent alive, queue
- if(!connected)
- var/datum/redis_message/RM = new()
- RM.channel = channel
- RM.message = message
- queue += RM
- return
-
- // If we are alive, publish straight away
- rustg_redis_publish(channel, message)
-
-
-// Misc protection stuff
-/datum/controller/subsystem/redis/CanProcCall(procname)
- return FALSE
-
-/datum/controller/subsystem/redis/vv_edit_var(var_name, var_value)
- return FALSE // dont even try
diff --git a/code/controllers/subsystem/SSticker.dm b/code/controllers/subsystem/SSticker.dm
deleted file mode 100644
index 2dbd6aedde4f6..0000000000000
--- a/code/controllers/subsystem/SSticker.dm
+++ /dev/null
@@ -1,907 +0,0 @@
-SUBSYSTEM_DEF(ticker)
- name = "Ticker"
- init_order = INIT_ORDER_TICKER
-
- priority = FIRE_PRIORITY_TICKER
- flags = SS_KEEP_TIMING
- runlevels = RUNLEVEL_LOBBY | RUNLEVEL_SETUP | RUNLEVEL_GAME
- offline_implications = "The game is no longer aware of when the round ends. Immediate server restart recommended."
- cpu_display = SS_CPUDISPLAY_LOW
- wait = 1 SECONDS
-
- /// Time the game should start, relative to world.time
- var/round_start_time = 0
- /// Time that the round started
- var/time_game_started = 0
- /// Current status of the game. See code\__DEFINES\game.dm
- var/current_state = GAME_STATE_STARTUP
- /// Do we want to force-start as soon as we can
- var/force_start = FALSE
- /// Do we want to force-end as soon as we can
- var/force_ending = FALSE
- /// Leave here at FALSE ! setup() will take care of it when needed for Secret mode -walter0o
- var/hide_mode = FALSE
- /// Our current game mode
- var/datum/game_mode/mode = null
- /// The current pick of lobby music played in the lobby
- var/login_music
- /// List of all minds in the game. Used for objective tracking
- var/list/datum/mind/minds = list()
- /// icon_state the chaplain has chosen for his bible
- var/Bible_icon_state
- /// item_state the chaplain has chosen for his bible
- var/Bible_item_state
- /// Name of the bible
- var/Bible_name
- /// Name of the bible deity
- var/Bible_deity_name
- /// 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?
- var/tipped = FALSE
- /// What will be the tip of the round?
- var/selected_tip
- /// This is used for calculations for the statpanel
- var/pregame_timeleft
- /// If set to TRUE, the round will not restart on it's own
- var/delay_end = FALSE
- /// Global holder for triple AI mode
- var/triai = FALSE
- /// Holder for inital autotransfer vote timer
- var/next_autotransfer = 0
- /// Used for station explosion cinematic
- var/atom/movable/screen/cinematic = null
- /// Spam Prevention. Announce round end only once.
- var/round_end_announced = FALSE
- /// Is the ticker currently processing? If FALSE, roundstart is delayed
- var/ticker_going = TRUE
- /// Gamemode result (For things like cult or nukies which can end multiple ways)
- var/mode_result = "undefined"
- /// Server end state (Did we end properly or reboot or nuke or what)
- var/end_state = "undefined"
- /// Time the real reboot kicks in
- var/real_reboot_time = 0
- /// Datum used to generate the end of round scoreboard.
- var/datum/scoreboard/score = null
- /// List of ckeys who had antag rolling issues flagged
- var/list/flagged_antag_rollers = list()
- /// List of biohazards keyed to the last time their population was sampled.
- var/list/biohazard_pop_times = list()
- var/list/biohazard_included_admin_spawns = list()
-
-/datum/controller/subsystem/ticker/Initialize()
- login_music = pick(\
- 'sound/music/thunderdome.ogg',\
- 'sound/music/space.ogg',\
- 'sound/music/title1.ogg',\
- 'sound/music/title2.ogg',\
- 'sound/music/title3.ogg',)
-
-
-/datum/controller/subsystem/ticker/fire()
- switch(current_state)
- if(GAME_STATE_STARTUP)
- // This is ran as soon as the MC starts firing, and should only run ONCE, unless startup fails
- pregame_timeleft = GLOB.configuration.general.lobby_time SECONDS
- round_start_time = world.time + pregame_timeleft
- to_chat(world, "Welcome to the pre-game lobby!")
- to_chat(world, "Please, setup your character and select ready. Game will start in [GLOB.configuration.general.lobby_time] seconds")
- current_state = GAME_STATE_PREGAME
- fire() // TG says this is a good idea
- for(var/mob/new_player/N in GLOB.player_list)
- if(N.client)
- N.new_player_panel_proc() // to enable the observe option
- if(GAME_STATE_PREGAME)
- if(!SSticker.ticker_going) // This has to be referenced like this, and I dont know why. If you dont put SSticker. it will break
- return
-
- // This is so we dont have sleeps in controllers, because that is a bad, bad thing
- pregame_timeleft = max(0, round_start_time - world.time)
-
- if(pregame_timeleft <= 1 MINUTES && !tipped)
- send_tip_of_the_round()
- tipped = TRUE
-
- if(pregame_timeleft <= 0 || force_start)
- current_state = GAME_STATE_SETTING_UP
- Master.SetRunLevel(RUNLEVEL_SETUP)
- if(GAME_STATE_SETTING_UP)
- if(!setup()) // Setup failed
- current_state = GAME_STATE_STARTUP
- Master.SetRunLevel(RUNLEVEL_LOBBY)
- if(GAME_STATE_PLAYING)
- delay_end = FALSE // reset this in case round start was delayed
- mode.process()
-
- for(var/biohazard in biohazard_pop_times)
- if(world.time - biohazard_pop_times[biohazard] > BIOHAZARD_POP_INTERVAL)
- sample_biohazard_population(biohazard)
-
- if(world.time > next_autotransfer)
- SSvote.start_vote(new /datum/vote/crew_transfer)
- next_autotransfer = world.time + GLOB.configuration.vote.autotransfer_interval_time
-
- var/game_finished = SSshuttle.emergency.mode >= SHUTTLE_ENDGAME || mode.station_was_nuked
- if(GLOB.configuration.gamemode.disable_certain_round_early_end)
- mode.check_finished() // some modes contain var-changing code in here, so call even if we don't uses result
- else
- game_finished |= mode.check_finished()
- if(game_finished || force_ending)
- current_state = GAME_STATE_FINISHED
- if(GAME_STATE_FINISHED)
- if(SSshuttle.emergency.mode >= SHUTTLE_ENDGAME && !mode.station_was_nuked)
- record_biohazard_results()
- current_state = GAME_STATE_FINISHED
- Master.SetRunLevel(RUNLEVEL_POSTGAME) // This shouldnt process more than once, but you never know
- auto_toggle_ooc(TRUE) // Turn it on
- declare_completion()
- addtimer(CALLBACK(src, PROC_REF(call_reboot)), 5 SECONDS)
- // Start a map vote IF
- // - Map rotate doesnt have a mode for today and map voting is enabled
- // - Map rotate has a mode for the day and it ISNT full random
- if(SSmaprotate.setup_done && (SSmaprotate.rotation_mode == MAPROTATION_MODE_HYBRID_FPTP_NO_DUPLICATES))
- SSmaprotate.decide_next_map()
- return
- if(((!SSmaprotate.setup_done) && GLOB.configuration.vote.enable_map_voting) || (SSmaprotate.setup_done && (SSmaprotate.rotation_mode != MAPROTATION_MODE_FULL_RANDOM)))
- SSvote.start_vote(new /datum/vote/map)
- else
- // Pick random map
- var/list/pickable_types = list()
- for(var/x in subtypesof(/datum/map))
- var/datum/map/M = x
- if(istype(SSmapping.map_datum, M)) // Random will never choose the same map twice in a row.
- continue
- if(initial(M.voteable) && length(GLOB.clients) >= initial(M.min_players_random))
- pickable_types += M
-
- var/datum/map/target_map = pick(pickable_types)
- SSmapping.next_map = new target_map
- to_chat(world, "Map for next round: [SSmapping.next_map.fluff_name] ([SSmapping.next_map.technical_name])")
-
-/datum/controller/subsystem/ticker/proc/call_reboot()
- if(mode.station_was_nuked)
- reboot_helper("Station destroyed by Nuclear Device.", "nuke")
- else
- reboot_helper("Round ended.", "proper completion")
-
-/datum/controller/subsystem/ticker/proc/setup()
- var/random_cult = pick(typesof(/datum/cult_info))
- cult_data = new random_cult()
- score = new()
-
- // Create and announce mode
- if(GLOB.master_mode == "secret")
- hide_mode = TRUE
-
- var/list/datum/game_mode/runnable_modes
-
- if(GLOB.master_mode == "random" || GLOB.master_mode == "secret")
- runnable_modes = GLOB.configuration.gamemode.get_runnable_modes()
- if(!length(runnable_modes))
- to_chat(world, "Unable to choose playable game mode. Reverting to pre-game lobby.")
- force_start = FALSE
- current_state = GAME_STATE_PREGAME
- Master.SetRunLevel(RUNLEVEL_LOBBY)
- return FALSE
- if(GLOB.secret_force_mode != "secret")
- var/datum/game_mode/M = GLOB.configuration.gamemode.pick_mode(GLOB.secret_force_mode)
- if(M.can_start())
- mode = GLOB.configuration.gamemode.pick_mode(GLOB.secret_force_mode)
- SSjobs.ResetOccupations()
- if(!mode)
- mode = pickweight(runnable_modes)
- if(mode)
- var/mtype = mode.type
- mode = new mtype
- else
- mode = GLOB.configuration.gamemode.pick_mode(GLOB.master_mode)
-
- if(!mode.can_start())
- to_chat(world, "Unable to start [mode.name]. Not enough players, [mode.required_players] players needed. Reverting to pre-game lobby.")
- mode = null
- current_state = GAME_STATE_PREGAME
- force_start = FALSE
- SSjobs.ResetOccupations()
- Master.SetRunLevel(RUNLEVEL_LOBBY)
- return FALSE
-
- // Randomise characters now. This avoids rare cases where a human is set as a changeling then they randomise to an IPC
- for(var/mob/new_player/player in GLOB.player_list)
- if(player.client.prefs.toggles2 & PREFTOGGLE_2_RANDOMSLOT)
- player.client.prefs.load_random_character_slot(player.client)
-
- // Lets check if people who ready should or shouldnt be
- for(var/mob/new_player/P in GLOB.player_list)
- // Not logged in
- if(!P.client)
- continue
- // Not ready
- if(!P.ready)
- continue
- // Not set to return if nothing available
- if(P.client.prefs.active_character.alternate_option != RETURN_TO_LOBBY)
- continue
-
- var/has_antags = (length(P.client.prefs.be_special) > 0)
- if(!P.client.prefs.active_character.check_any_job())
- to_chat(P, "You have no jobs enabled, along with return to lobby if job is unavailable. This makes you ineligible for any round start role, please update your job preferences.")
- if(has_antags)
- // We add these to a list so we can deal with them as a batch later
- // A lot of DB tracking stuff needs doing, so we may as well async it
- flagged_antag_rollers |= P.ckey
-
- P.ready = FALSE
-
- //Configure mode and assign player to special mode stuff
-
- var/can_continue = FALSE
- can_continue = mode.pre_setup() //Setup special modes. This also does the antag fishing checks.
-
- if(!can_continue)
- QDEL_NULL(mode)
- to_chat(world, "Error setting up [GLOB.master_mode]. Reverting to pre-game lobby.")
- current_state = GAME_STATE_PREGAME
- force_start = FALSE
- SSjobs.ResetOccupations()
- Master.SetRunLevel(RUNLEVEL_LOBBY)
- return FALSE
-
- // Enable highpop slots just before we distribute jobs.
- var/playercount = length(GLOB.clients)
- var/highpop_trigger = 80
-
- if(playercount >= highpop_trigger)
- log_debug("Playercount: [playercount] versus trigger: [highpop_trigger] - loading highpop job config")
- SSjobs.LoadJobs(TRUE)
- else
- log_debug("Playercount: [playercount] versus trigger: [highpop_trigger] - keeping standard job config")
-
- SSjobs.DivideOccupations() //Distribute jobs
-
- if(hide_mode)
- var/list/modes = list()
- for(var/datum/game_mode/M in runnable_modes)
- modes += M.name
- modes = sortList(modes)
- to_chat(world, "The current game mode is - Secret!")
- to_chat(world, "Possibilities: [english_list(modes)]")
- else
- mode.announce()
-
- // Behold, a rough way of figuring out what takes 10 years
- var/watch = start_watch()
- create_characters() // Create player characters and transfer clients
- log_debug("Creating characters took [stop_watch(watch)]s")
-
- // Gather everyones minds
- for(var/mob/living/player in GLOB.player_list)
- if(player.mind)
- minds += player.mind
-
- watch = start_watch()
- equip_characters() // Apply outfits and loadouts to the characters
- log_debug("Equipping characters took [stop_watch(watch)]s")
-
- watch = start_watch()
- GLOB.data_core.manifest() // Create the manifest
- log_debug("Manifest creation took [stop_watch(watch)]s")
- SEND_SIGNAL(src, COMSIG_TICKER_ROUND_STARTING, world.time)
-
- // Update the MC and state to game playing
- current_state = GAME_STATE_PLAYING
- Master.SetRunLevel(RUNLEVEL_GAME)
-
- // Generate the list of empty playable AI cores in the world
- if(HAS_TRAIT(SSstation, STATION_TRAIT_TRIAI))
- for(var/obj/effect/landmark/tripai in GLOB.landmarks_list)
- if(tripai.name == "tripai")
- if(locate(/mob/living) in get_turf(tripai))
- continue
- GLOB.empty_playable_ai_cores += new /obj/structure/ai_core/deactivated(get_turf(tripai))
- for(var/obj/effect/landmark/start/ai/A in GLOB.landmarks_list)
- if(locate(/mob/living) in get_turf(A))
- continue
- GLOB.empty_playable_ai_cores += new /obj/structure/ai_core/deactivated(get_turf(A))
-
-
- // Setup pregenerated newsfeeds
- setup_news_feeds()
-
- // Generate code phrases and responses
- if(!GLOB.syndicate_code_phrase)
- var/temp_syndicate_code_phrase = generate_code_phrase(return_list = TRUE)
-
- var/codewords = jointext(temp_syndicate_code_phrase, "|")
- var/regex/codeword_match = new("([codewords])", "ig")
-
- GLOB.syndicate_code_phrase_regex = codeword_match
- temp_syndicate_code_phrase = jointext(temp_syndicate_code_phrase, ", ")
- GLOB.syndicate_code_phrase = temp_syndicate_code_phrase
-
-
- if(!GLOB.syndicate_code_response)
- var/temp_syndicate_code_response = generate_code_phrase(return_list = TRUE)
-
- var/codewords = jointext(temp_syndicate_code_response, "|")
- var/regex/codeword_match = new("([codewords])", "ig")
-
- GLOB.syndicate_code_response_regex = codeword_match
- temp_syndicate_code_response = jointext(temp_syndicate_code_response, ", ")
- GLOB.syndicate_code_response = temp_syndicate_code_response
-
- // Run post setup stuff
- mode.post_setup()
-
- // Delete starting landmarks (not AI ones because we need those for AI-ize)
- for(var/obj/effect/landmark/start/S in GLOB.landmarks_list)
- if(!istype(S, /obj/effect/landmark/start/ai))
- qdel(S)
-
- SSdbcore.SetRoundStart()
- to_chat(world, "Enjoy the game!")
- SEND_SOUND(world, sound(SSmapping.map_datum.welcome_sound))
-
- if(SSholiday.holidays)
- to_chat(world, "and...")
- for(var/holidayname in SSholiday.holidays)
- var/datum/holiday/holiday = SSholiday.holidays[holidayname]
- to_chat(world, "[holiday.greet()]")
-
- GLOB.discord_manager.send2discord_simple_noadmins("**\[Info]** Round has started")
- auto_toggle_ooc(FALSE) // Turn it off
- time_game_started = world.time
-
- // Sets the auto shuttle vote to happen after the config duration
- next_autotransfer = world.time + GLOB.configuration.vote.autotransfer_initial_time
-
- for(var/mob/new_player/N in GLOB.mob_list)
- if(N.client)
- N.new_player_panel_proc()
-
- if(GLOB.configuration.general.enable_night_shifts)
- SSnightshift.check_nightshift(TRUE)
-
- #ifdef GAME_TESTS
- // Run map tests first in case unit tests futz with map state
- GLOB.test_runner.RunMap()
- GLOB.test_runner.Run()
- #endif
-
- // Do this 10 second after roundstart because of roundstart lag, and make it more visible
- addtimer(CALLBACK(src, PROC_REF(handle_antagfishing_reporting)), 10 SECONDS)
- return TRUE
-
-
-/datum/controller/subsystem/ticker/proc/station_explosion_cinematic(nuke_site = NUKE_SITE_ON_STATION, override = null)
- if(cinematic)
- return //already a cinematic in progress!
-
- auto_toggle_ooc(TRUE) // Turn it on
- //initialise our cinematic screen object
- cinematic = new /atom/movable/screen(src)
- cinematic.icon = 'icons/effects/station_explosion.dmi'
- cinematic.icon_state = "station_intact"
- cinematic.layer = 21
- cinematic.mouse_opacity = MOUSE_OPACITY_TRANSPARENT
- cinematic.screen_loc = "1,1"
-
- if(nuke_site == NUKE_SITE_ON_STATION)
- // Kill everyone on z-level 1 except for mobs in freezers and
- // malfunctioning AIs.
- for(var/mob/M in GLOB.mob_list)
- if(M.stat != DEAD)
- var/turf/T = get_turf(M)
- if(T && is_station_level(T.z) && !istype(M.loc, /obj/structure/closet/secure_closet/freezer) && !(issilicon(M) && override == "AI malfunction"))
- to_chat(M, "The blast wave from the explosion tears you atom from atom!")
- var/mob/ghost = M.ghostize()
- M.dust() //no mercy
- if(ghost && ghost.client) //Play the victims an uninterrupted cinematic.
- ghost.client.screen += cinematic
- CHECK_TICK
- if(M && M.client) //Play the survivors a cinematic.
- M.client.screen += cinematic
- else
- for(var/mob/M in GLOB.mob_list)
- if(M.client)
- M.client.screen += cinematic //show every client the cinematic
-
- switch(nuke_site)
- //Now animate the cinematic
- if(NUKE_SITE_ON_STATION)
- // station was destroyed
- if(mode && !override)
- override = mode.name
- switch(override)
- if("nuclear emergency") //Nuke Ops successfully bombed the station
- flick("intro_nuke", cinematic)
- sleep(35)
- flick("station_explode_fade_red", cinematic)
- SEND_SOUND(world, sound('sound/effects/explosion_distant.ogg'))
- cinematic.icon_state = "summary_nukewin"
- if("AI malfunction") //Malf (screen,explosion,summary)
- flick("intro_malf", cinematic)
- sleep(76)
- flick("station_explode_fade_red", cinematic)
- SEND_SOUND(world, sound('sound/effects/explosion_distant.ogg'))
- cinematic.icon_state = "summary_malf"
- else //Station nuked (nuke,explosion,summary)
- flick("intro_nuke", cinematic)
- sleep(35)
- flick("station_explode_fade_red", cinematic)
- SEND_SOUND(world, sound('sound/effects/explosion_distant.ogg'))
- cinematic.icon_state = "summary_selfdes"
-
- if(NUKE_SITE_ON_STATION_ZLEVEL)
- // nuke was nearby but (mostly) missed
- if(mode && !override)
- override = mode.name
- switch(override)
- if("nuclear emergency") //Nuke wasn't on station when it blew up
- flick("intro_nuke", cinematic)
- sleep(35)
- SEND_SOUND(world, sound('sound/effects/explosion_distant.ogg'))
- flick("station_intact_fade_red", cinematic)
- cinematic.icon_state = "summary_nukefail"
- if("fake") //The round isn't over, we're just freaking people out for fun
- flick("intro_nuke", cinematic)
- sleep(35)
- SEND_SOUND(world, sound('sound/items/bikehorn.ogg'))
- flick("summary_selfdes", cinematic)
- else
- flick("intro_nuke", cinematic)
- sleep(35)
- SEND_SOUND(world, sound('sound/effects/explosion_distant.ogg'))
- if(NUKE_SITE_OFF_STATION_ZLEVEL, NUKE_SITE_INVALID)
- // nuke was nowhere nearby
- // TODO: a really distant explosion animation
- sleep(50)
- SEND_SOUND(world, sound('sound/effects/explosion_distant.ogg'))
-
- //If its actually the end of the round, wait for it to end.
- //Otherwise if its a verb it will continue on afterwards.
- spawn(300)
- QDEL_NULL(cinematic) //end the cinematic
-
-
-
-/datum/controller/subsystem/ticker/proc/create_characters()
- for(var/mob/new_player/player in GLOB.player_list)
- if(player.ready && player.mind)
- if(player.mind.assigned_role == "AI")
- player.close_spawn_windows()
- var/mob/living/silicon/ai/ai_character = player.AIize()
- ai_character.moveToAILandmark()
- else if(!player.mind.assigned_role)
- continue
- else
- player.create_character()
- qdel(player)
-
-/datum/controller/subsystem/ticker/proc/equip_characters()
- for(var/mob/living/carbon/human/player in GLOB.player_list)
- if(player && player.mind && player.mind.assigned_role && player.mind.assigned_role != player.mind.special_role)
- SSjobs.AssignRank(player, player.mind.assigned_role, FALSE)
- SSjobs.EquipRank(player, player.mind.assigned_role, FALSE)
- equip_cuis(player)
-
-/datum/controller/subsystem/ticker/proc/equip_cuis(mob/living/carbon/human/H)
- if(!H.client)
- return // If they are spawning without a client (somehow), they *cant* have a CUI list
- for(var/datum/custom_user_item/cui in H.client.cui_entries)
- // Skip items with invalid character names
- if((cui.characer_name != H.real_name) && !cui.all_characters_allowed)
- continue
-
- var/ok = FALSE
-
- if(!cui.all_jobs_allowed)
- var/alt_blocked = FALSE
- if(H.mind.role_alt_title)
- if(!(H.mind.role_alt_title in cui.allowed_jobs))
- alt_blocked = TRUE
- if(!(H.mind.assigned_role in cui.allowed_jobs) || alt_blocked)
- continue
-
- var/obj/item/I = new cui.object_typepath()
- var/name_override = cui.item_name_override
- var/desc_override = cui.item_desc_override
-
- if(name_override)
- I.name = name_override
- if(desc_override)
- I.desc = desc_override
-
- if(isstorage(H.back)) // Try to place it in something on the mob's back
- var/obj/item/storage/S = H.back
- if(length(S.contents) < S.storage_slots)
- I.forceMove(H.back)
- ok = TRUE
- to_chat(H, "Your [I.name] has been added to your [H.back.name].")
-
- if(!ok)
- for(var/obj/item/storage/S in H.contents) // Try to place it in any item that can store stuff, on the mob.
- if(length(S.contents) < S.storage_slots)
- I.forceMove(S)
- ok = TRUE
- to_chat(H, "Your [I.name] has been added to your [S.name].")
- break
-
- if(!ok) // Finally, since everything else failed, place it on the ground
- var/turf/T = get_turf(H)
- if(T)
- I.forceMove(T)
- to_chat(H, "Your [I.name] is on the [T.name] below you.")
- else
- to_chat(H, "Your [I.name] couldnt spawn anywhere on you or even on the floor below you. Please file a bug report.")
- qdel(I)
-
-
-/datum/controller/subsystem/ticker/proc/send_tip_of_the_round()
- var/m
- if(selected_tip)
- m = selected_tip
- else
- var/list/randomtips = file2list("strings/tips.txt")
- var/list/memetips = file2list("strings/sillytips.txt")
- if(length(randomtips) && prob(95))
- m = pick(randomtips)
- else if(length(memetips))
- m = pick(memetips)
-
- if(m)
- to_chat(world, "Tip of the round: [html_encode(m)]")
-
-/datum/controller/subsystem/ticker/proc/declare_completion()
- GLOB.nologevent = TRUE //end of round murder and shenanigans are legal; there's no need to jam up attack logs past this point.
- GLOB.disable_explosions = TRUE // that said, if people want to be """FUNNY""" and bomb at EORG, they can fuck themselves up
- set_observer_default_invisibility(0) //spooks things up
- //Round statistics report
- var/datum/station_state/ending_station_state = new /datum/station_state()
- ending_station_state.count()
- var/station_integrity = min(round( 100.0 * GLOB.start_state.score(ending_station_state), 0.1), 100.0)
-
- var/list/end_of_round_info = list()
- end_of_round_info += " [TAB]Shift Duration: [round(ROUND_TIME / 36000)]:[add_zero("[ROUND_TIME / 600 % 60]", 2)]:[ROUND_TIME / 100 % 6][ROUND_TIME / 100 % 10]"
- end_of_round_info += " [TAB]Station Integrity: [mode.station_was_nuked ? "Destroyed" : "[station_integrity]%"]"
- end_of_round_info += " "
-
- //Silicon laws report
- for(var/mob/living/silicon/ai/aiPlayer in GLOB.ai_list)
- var/ai_ckey = safe_get_ckey(aiPlayer)
-
- if(aiPlayer.stat != DEAD)
- end_of_round_info += "[aiPlayer.name] (Played by: [ai_ckey])'s laws at the end of the game were:"
- else
- end_of_round_info += "[aiPlayer.name] (Played by: [ai_ckey])'s laws when it was deactivated were:"
- aiPlayer.laws_sanity_check()
- for(var/datum/ai_law/law as anything in aiPlayer.laws.sorted_laws)
- if(law == aiPlayer.laws.zeroth_law)
- end_of_round_info += "[law.get_index()]. [law.law]"
- else
- end_of_round_info += "[law.get_index()]. [law.law]"
-
- if(length(aiPlayer.connected_robots))
- end_of_round_info += "The AI's loyal minions were: "
- for(var/mob/living/silicon/robot/robo in aiPlayer.connected_robots)
- var/robo_ckey = safe_get_ckey(robo)
- end_of_round_info += "[robo.name][robo.stat ? " (Deactivated)" : ""] (Played by: [robo_ckey])"
-
- var/dronecount = 0
-
- for(var/mob/living/silicon/robot/robo in GLOB.mob_list)
-
- if(isdrone(robo))
- dronecount++
- continue
-
- var/robo_ckey = safe_get_ckey(robo)
-
- if(!robo.connected_ai)
- if(robo.stat != DEAD)
- end_of_round_info += "[robo.name] (Played by: [robo_ckey]) survived as an AI-less borg! Its laws were:"
- else
- end_of_round_info += "[robo.name] (Played by: [robo_ckey]) was unable to survive the rigors of being a cyborg without an AI. Its laws were:"
-
- robo.laws_sanity_check()
- for(var/datum/ai_law/law as anything in robo.laws.sorted_laws)
- if(law == robo.laws.zeroth_law)
- end_of_round_info += "[law.get_index()]. [law.law]"
- else
- end_of_round_info += "[law.get_index()]. [law.law]"
-
- if(dronecount)
- end_of_round_info += "There [dronecount > 1 ? "were" : "was"] [dronecount] industrious maintenance [dronecount > 1 ? "drones" : "drone"] this round."
-
- if(length(mode.eventmiscs))
- for(var/datum/mind/eventmind in mode.eventmiscs)
- end_of_round_info += printeventplayer(eventmind)
- end_of_round_info += printobjectives(eventmind)
- end_of_round_info += " "
-
- mode.declare_completion()//To declare normal completion.
-
- end_of_round_info += mode.get_end_of_round_antagonist_statistics()
-
- for(var/datum/team/team in GLOB.antagonist_teams)
- team.on_round_end()
-
- // Save the data before end of the round griefing
- SSpersistent_data.save()
- to_chat(world, end_of_round_info.Join(" "))
-
- // Display the scoreboard window
- score.scoreboard()
-
- // Declare the completion of the station goals
- mode.declare_station_goal_completion()
-
- //Ask the event manager to print round end information
- SSevents.RoundEnd()
-
- //make big obvious note in game logs that round ended
- log_game("///////////////////////////////////////////////////////")
- log_game("///////////////////// ROUND ENDED /////////////////////")
- log_game("///////////////////////////////////////////////////////")
-
- // Add AntagHUD to everyone, see who was really evil the whole time!
- for(var/datum/atom_hud/antag/H in GLOB.huds)
- for(var/m in GLOB.player_list)
- var/mob/M = m
- H.add_hud_to(M)
-
- var/static/list/base_encouragement_messages = list(
- "Keep on keeping on!",
- "Great job!",
- "Keep up the good work!",
- "Nice going!"
- )
-
- var/static/list/special_encouragement_messages = list(
- "Outstanding!",
- "This is going on the fridge!",
- "Looks like you're popular!",
- "That's what we like to see!",
- "Hell yeah, brother!",
- "Honestly, quite incredible!"
- )
-
- // Tell people how many kudos they got this round
- // Besides, what's another loop over the /entire player list/
- var/kudos_message
- for(var/mob/M in GLOB.player_list)
- var/kudos = M.mind?.kudos_received_from
- if(length(kudos))
- kudos_message = pick(length(kudos) > 5 ? special_encouragement_messages : base_encouragement_messages)
- to_chat(M, "You received [length(M.mind.kudos_received_from)] kudos from other players this round! [kudos_message]")
-
- // Seal the blackbox, stop collecting info
- SSblackbox.Seal()
- SSdbcore.SetRoundEnd()
-
- return TRUE
-
-/datum/controller/subsystem/ticker/proc/HasRoundStarted()
- return current_state >= GAME_STATE_PLAYING
-
-/datum/controller/subsystem/ticker/proc/IsRoundInProgress()
- return current_state == GAME_STATE_PLAYING
-
-
-/datum/controller/subsystem/ticker/proc/setup_news_feeds()
- var/datum/feed_channel/newChannel = new /datum/feed_channel
- newChannel.channel_name = "Station Announcements Log"
- newChannel.author = "Automated Announcement Listing"
- newChannel.icon = "bullhorn"
- newChannel.frozen = TRUE
- newChannel.admin_locked = TRUE
- GLOB.news_network.channels += newChannel
-
- newChannel = new /datum/feed_channel
- newChannel.channel_name = "Public Station Announcements"
- newChannel.author = "Automated Announcement Listing"
- newChannel.icon = "users"
- newChannel.is_public = TRUE
- GLOB.news_network.channels += newChannel
-
- newChannel = new /datum/feed_channel
- newChannel.channel_name = "Nyx Daily"
- newChannel.author = "CentComm Minister of Information"
- newChannel.icon = "meteor"
- newChannel.frozen = TRUE
- newChannel.admin_locked = TRUE
- GLOB.news_network.channels += newChannel
-
- newChannel = new /datum/feed_channel
- newChannel.channel_name = "The Gibson Gazette"
- newChannel.author = "Editor Mike Hammers"
- newChannel.icon = "star"
- newChannel.frozen = TRUE
- newChannel.admin_locked = TRUE
- GLOB.news_network.channels += newChannel
-
- for(var/loc_type in subtypesof(/datum/trade_destination))
- var/datum/trade_destination/D = new loc_type
- GLOB.weighted_randomevent_locations[D] = length(D.viable_random_events)
- GLOB.weighted_mundaneevent_locations[D] = length(D.viable_mundane_events)
-
-// Easy handler to make rebooting the world not a massive sleep in world/Reboot()
-/datum/controller/subsystem/ticker/proc/reboot_helper(reason, end_string, delay)
- // Admins delayed round end. Just alert and dont bother with anything else.
- if(delay_end)
- to_chat(world, "An admin has delayed the round end.")
- return
- if(delay)
- INVOKE_ASYNC(src, TYPE_PROC_REF(/datum/controller/subsystem/ticker, show_server_restart_blurb), reason)
-
- if(!isnull(delay))
- // Delay time was present. Use that.
- delay = max(0, delay)
- else
- // Use default restart timeout
- delay = max(0, GLOB.configuration.general.restart_timeout SECONDS)
-
- to_chat(world, "Rebooting world in [delay/10] [delay > 10 ? "seconds" : "second"]. [reason]")
-
- real_reboot_time = world.time + delay
- UNTIL(world.time > real_reboot_time) // Hold it here
-
- // And if we re-delayed, bail again
- if(delay_end)
- to_chat(world, "Reboot was cancelled by an admin.")
- return
-
- if(end_string)
- end_state = end_string
-
- // Play a haha funny noise for those who want to hear it :)
- var/round_end_sound = pick(GLOB.round_end_sounds)
- var/sound_length = GLOB.round_end_sounds[round_end_sound]
-
- for(var/mob/M in GLOB.player_list)
- if(!(M.client.prefs.sound & SOUND_MUTE_END_OF_ROUND))
- SEND_SOUND(M, round_end_sound)
-
- sleep(sound_length)
-
- world.Reboot()
-
-// Timers invoke this async
-/datum/controller/subsystem/ticker/proc/handle_antagfishing_reporting()
- // This needs the DB
- if(!SSdbcore.IsConnected())
- return
- // Dont need to do anything
- if(!length(flagged_antag_rollers))
- return
-
- // Records themselves
- var/list/datum/antag_record/records = list()
- // Queries to load data (executed as async batch)
- var/list/datum/db_query/load_queries = list()
- // Queries to save data (executed as async batch)
- var/list/datum/db_query/save_queries = list()
-
-
- for(var/ckey in flagged_antag_rollers)
- var/datum/antag_record/AR = new /datum/antag_record(ckey)
- records[ckey] = AR
- load_queries[ckey] = AR.get_load_query()
-
- // Explanation for parameters:
- // TRUE: We want warnings if these fail
- // FALSE: Do NOT qdel() queries here, otherwise they wont be read. At all.
- // TRUE: This is an assoc list, so it needs to prepare for that
- SSdbcore.MassExecute(load_queries, TRUE, FALSE, TRUE)
-
- // Report on things
- var/list/log_text = list("The following players attempted to roll antag with no jobs (total infractions listed)")
-
- for(var/ckey in flagged_antag_rollers)
- var/datum/antag_record/AR = records[ckey]
- AR.handle_data(load_queries[ckey])
- save_queries[ckey] = AR.get_save_query()
-
- log_text += "- [ckey]: [AR.infraction_count]"
-
- log_text += "Investigation advised if there are a high number of infractions"
-
- message_admins(log_text.Join(" "))
-
- // Now do a ton of saves
- SSdbcore.MassExecute(save_queries, TRUE, TRUE, TRUE)
-
- // And cleanup
- QDEL_LIST_ASSOC_VAL(load_queries)
- records.Cut()
- flagged_antag_rollers.Cut()
-
-/// This proc is for recording biohazard events, and blackboxing if they lived,
-/// died, or ended the round. This currently applies to: Terror spiders,
-/// Xenomorphs, and Blob.
-///
-/// This code is predicated on the assumption that multiple midrounds
-/// of the same type are either extremely rare or impossible. We don't want to get
-/// into the insanity of trying to record if the first xeno biohazard was defeated
-/// but the second xeno biohazard was nuked.
-/datum/controller/subsystem/ticker/proc/record_biohazard_results()
- for(var/biohazard in SSevents.biohazards_this_round)
- if(biohazard_active_threat(biohazard))
- SSblackbox.record_feedback("nested tally", "biohazards", 1, list("survived", biohazard))
- else
- SSblackbox.record_feedback("nested tally", "biohazards", 1, list("defeated", biohazard))
-
- for(var/biohazard in SSticker.biohazard_included_admin_spawns)
- SSblackbox.record_feedback("nested tally", "biohazards", 1, list("included_admin_spawns", biohazard))
-
-/datum/controller/subsystem/ticker/proc/count_xenomorps()
- . = 0
- for(var/datum/mind/xeno_mind in SSticker.mode.xenos)
- if(xeno_mind.current?.stat == DEAD)
- continue
- .++
-
-/datum/controller/subsystem/ticker/proc/sample_biohazard_population(biohazard)
- SSblackbox.record_feedback("ledger", "biohazard_pop_[BIOHAZARD_POP_INTERVAL_STR]_interval", biohazard_count(biohazard), biohazard)
- if(any_admin_spawned_mobs(biohazard) && !(biohazard in biohazard_included_admin_spawns))
- biohazard_included_admin_spawns[biohazard] = TRUE
-
- biohazard_pop_times[biohazard] = world.time
-
-/// Record the initial time that a biohazard spawned.
-/datum/controller/subsystem/ticker/proc/record_biohazard_start(biohazard)
- SSblackbox.record_feedback("associative", "biohazard_starts", 1, list("type" = biohazard, "time_ds" = world.time - time_game_started))
- sample_biohazard_population(biohazard)
-
-/// Returns whether the given biohazard includes mobs that were admin spawned.
-/// Only returns TRUE or FALSE, does not attempt to track which mobs were
-/// admin-spawned and which ones weren't.
-/datum/controller/subsystem/ticker/proc/any_admin_spawned_mobs(biohazard)
- switch(biohazard)
- if(TS_INFESTATION_GREEN_SPIDER, TS_INFESTATION_WHITE_SPIDER, TS_INFESTATION_PRINCESS_SPIDER, TS_INFESTATION_QUEEN_SPIDER, TS_INFESTATION_PRINCE_SPIDER)
- for(var/mob/living/simple_animal/hostile/poison/terror_spider/S in GLOB.ts_spiderlist)
- if(S.admin_spawned)
- return TRUE
- if(BIOHAZARD_XENO)
- for(var/datum/mind/xeno_mind in SSticker.mode.xenos)
- if(xeno_mind.current?.admin_spawned)
- return TRUE
- if(BIOHAZARD_BLOB)
- for(var/atom/blob_overmind in SSticker.mode.blob_overminds)
- if(blob_overmind.admin_spawned)
- return TRUE
-
-/datum/controller/subsystem/ticker/proc/biohazard_count(biohazard)
- switch(biohazard)
- if(TS_INFESTATION_GREEN_SPIDER, TS_INFESTATION_WHITE_SPIDER, TS_INFESTATION_PRINCESS_SPIDER, TS_INFESTATION_QUEEN_SPIDER)
- var/spiders = 0
- for(var/mob/living/simple_animal/hostile/poison/terror_spider/S in GLOB.ts_spiderlist)
- if(S.ckey)
- spiders++
- return spiders
- if(TS_INFESTATION_PRINCE_SPIDER)
- return length(GLOB.ts_spiderlist)
- if(BIOHAZARD_XENO)
- return count_xenomorps()
- if(BIOHAZARD_BLOB)
- return length(SSticker.mode.blob_overminds)
-
- CRASH("biohazard_count got unexpected [biohazard]")
-
-/// Return whether or not a given biohazard is an active threat.
-/// For blobs, this is simply if there are any overminds left. For terrors and
-/// xenomorphs, this is whether they have overwhelming numbers.
-/datum/controller/subsystem/ticker/proc/biohazard_active_threat(biohazard)
- var/count = biohazard_count(biohazard)
- switch(biohazard)
- if(TS_INFESTATION_GREEN_SPIDER, TS_INFESTATION_WHITE_SPIDER, TS_INFESTATION_PRINCESS_SPIDER, TS_INFESTATION_QUEEN_SPIDER)
- return count >= 5
- if(TS_INFESTATION_PRINCE_SPIDER)
- return count > 0
- if(BIOHAZARD_XENO)
- return count > 5
- if(BIOHAZARD_BLOB)
- return count > 0
-
- return FALSE
diff --git a/code/controllers/subsystem/SSverb_manager.dm b/code/controllers/subsystem/SSverb_manager.dm
deleted file mode 100644
index 9382a121a3e26..0000000000000
--- a/code/controllers/subsystem/SSverb_manager.dm
+++ /dev/null
@@ -1,174 +0,0 @@
-/**
- * SSverb_manager, a subsystem that runs every tick and runs through its entire queue without yielding like SSinput.
- * this exists because of how the byond tick works and where user inputted verbs are put within it.
- *
- * see TICK_ORDER.md for more info on how the byond tick is structured.
- *
- * The way the MC allots its time is via TICK_LIMIT_RUNNING, it simply subtracts the cost of SendMaps (MAPTICK_LAST_INTERNAL_TICK_USAGE)
- * plus TICK_BYOND_RESERVE from the tick and uses up to that amount of time (minus the percentage of the tick used by the time it executes subsystems)
- * on subsystems running cool things like atmospherics or Life or SSInput or whatever.
- *
- * Without this subsystem, verbs are likely to cause overtime if the MC uses all of the time it has alloted for itself in the tick, and SendMaps
- * uses as much as its expected to, and an expensive verb ends up executing that tick. This is because the MC is completely blind to the cost of
- * verbs, it can't account for it at all. The only chance for verbs to not cause overtime in a tick where the MC used as much of the tick
- * as it alloted itself and where SendMaps costed as much as it was expected to is if the verb(s) take less than TICK_BYOND_RESERVE percent of
- * the tick, which isnt much. Not to mention if SendMaps takes more than 30% of the tick and the MC forces itself to take at least 70% of the
- * normal tick duration which causes ticks to naturally overrun even in the absence of verbs.
- *
- * With this subsystem, the MC can account for the cost of verbs and thus stop major overruns of ticks. This means that the most important subsystems
- * like SSinput can start at the same time they were supposed to, leading to a smoother experience for the player since ticks arent riddled with
- * minor hangs over and over again.
- */
-SUBSYSTEM_DEF(verb_manager)
- name = "Verb Queue Manager"
- wait = 1
- flags = SS_TICKER | SS_NO_INIT
- priority = FIRE_PRIORITY_DELAYED_VERBS
- runlevels = RUNLEVEL_INIT | RUNLEVELS_DEFAULT
-
- ///list of callbacks to procs called from verbs or verblike procs that were executed when the server was overloaded and had to delay to the next tick.
- ///this list is ran through every tick, and the subsystem does not yield until this queue is finished.
- var/list/datum/callback/verb_callback/verb_queue = list()
-
- ///running average of how many verb callbacks are executed every second. used for the stat entry
- var/verbs_executed_per_second = 0
-
- ///if TRUE we treat usr's with holders just like usr's without holders. otherwise they always execute immediately
- var/can_queue_admin_verbs = FALSE
-
- ///if this is true all verbs immediately execute and dont queue. in case the mc is fucked or something
- var/FOR_ADMINS_IF_VERBS_FUCKED_immediately_execute_all_verbs = FALSE
-
- ///if TRUE this will... message admins every time a verb is queued to this subsystem for the next tick with stats.
- ///for obvious reasons dont make this be TRUE on the code level this is for admins to turn on
- var/message_admins_on_queue = FALSE
-
- ///always queue if possible. overides can_queue_admin_verbs but not FOR_ADMINS_IF_VERBS_FUCKED_immediately_execute_all_verbs
- var/always_queue = FALSE
-
-/**
- * queue a callback for the given verb/verblike proc and any given arguments to the specified verb subsystem, so that they process in the next tick.
- * intended to only work with verbs or verblike procs called directly from client input, use as part of TRY_QUEUE_VERB() and co.
- *
- * returns TRUE if the queuing was successful, FALSE otherwise.
- */
-/proc/_queue_verb(datum/callback/verb_callback/incoming_callback, tick_check, datum/controller/subsystem/verb_manager/subsystem_to_use = SSverb_manager, ...)
- if(QDELETED(incoming_callback))
- var/destroyed_string
- if(!incoming_callback)
- destroyed_string = "callback is null."
- else
- destroyed_string = "callback was deleted [DS2TICKS(world.time - incoming_callback.gc_destroyed)] ticks ago. callback was created [DS2TICKS(world.time) - incoming_callback.creation_time] ticks ago."
-
- stack_trace("_queue_verb() returned false because it was given a deleted callback! [destroyed_string]")
- return FALSE
-
- if(!istext(incoming_callback.object) && QDELETED(incoming_callback.object)) //just in case the object is GLOBAL_PROC
- var/destroyed_string
- if(!incoming_callback.object)
- destroyed_string = "callback.object is null."
- else
- destroyed_string = "callback.object was deleted [DS2TICKS(world.time - incoming_callback.object.gc_destroyed)] ticks ago. callback was created [DS2TICKS(world.time) - incoming_callback.creation_time] ticks ago."
-
- stack_trace("_queue_verb() returned false because it was given a callback acting on a qdeleted object! [destroyed_string]")
- return FALSE
-
- //we want unit tests to be able to directly call verbs that attempt to queue, and since unit tests should test internal behavior, we want the queue
- //to happen as if it was actually from player input if its called on a mob.
-#ifdef GAME_TESTS
- if(QDELETED(usr) && ismob(incoming_callback.object))
- incoming_callback.usr_uid = incoming_callback.object.UID()
- var/datum/callback/new_us = CALLBACK(arglist(list(GLOBAL_PROC, GLOBAL_PROC_REF(_queue_verb)) + args.Copy()))
- return world.invoke_callback_with_usr(incoming_callback.object, new_us)
-#endif
-
- //debatable whether this is needed, this is just to try and ensure that you dont use this to queue stuff that isnt from player input.
- if(QDELETED(usr))
- stack_trace("_queue_verb() returned false because it wasnt called from player input!")
- return FALSE
-
- if(!istype(subsystem_to_use))
- stack_trace("_queue_verb() returned false because it was given an invalid subsystem to queue for!")
- return FALSE
-
- if((TICK_USAGE < tick_check) && !subsystem_to_use.always_queue)
- return FALSE
-
- var/list/args_to_check = args.Copy()
- args_to_check.Cut(2, 4)//cut out tick_check and subsystem_to_use
-
- //any subsystem can use the additional arguments to refuse queuing
- if(!subsystem_to_use.can_queue_verb(arglist(args_to_check)))
- return FALSE
-
- return subsystem_to_use.queue_verb(incoming_callback)
-
-/**
- * subsystem-specific check for whether a callback can be queued.
- * intended so that subsystem subtypes can verify whether
- *
- * subtypes may include additional arguments here if they need them! you just need to include them properly
- * in TRY_QUEUE_VERB() and co.
- */
-/datum/controller/subsystem/verb_manager/proc/can_queue_verb(datum/callback/verb_callback/incoming_callback)
- if(always_queue && !FOR_ADMINS_IF_VERBS_FUCKED_immediately_execute_all_verbs)
- return TRUE
-
- if((usr.client?.holder && !can_queue_admin_verbs) \
- || (!initialized && !(flags & SS_NO_INIT)) \
- || FOR_ADMINS_IF_VERBS_FUCKED_immediately_execute_all_verbs \
- || !(runlevels & Master.current_runlevel))
- return FALSE
-
- return TRUE
-
-/**
- * queue a callback for the given proc, so that it is invoked in the next tick.
- * intended to only work with verbs or verblike procs called directly from client input, use as part of TRY_QUEUE_VERB()
- *
- * returns TRUE if the queuing was successful, FALSE otherwise.
- */
-/datum/controller/subsystem/verb_manager/proc/queue_verb(datum/callback/verb_callback/incoming_callback)
- . = FALSE //errored
- if(message_admins_on_queue)
- message_admins("[name] verb queuing: tick usage: [TICK_USAGE]%, proc: [incoming_callback.delegate], object: [incoming_callback.object], usr: [usr]")
- verb_queue += incoming_callback
- return TRUE
-
-/datum/controller/subsystem/verb_manager/fire(resumed)
- run_verb_queue()
-
-/// runs through all of this subsystems queue of verb callbacks.
-/// goes through the entire verb queue without yielding.
-/// used so you can flush the queue outside of fire() without interfering with anything else subtype subsystems might do in fire().
-/datum/controller/subsystem/verb_manager/proc/run_verb_queue()
- var/executed_verbs = 0
-
- for(var/datum/callback/verb_callback/verb_callback as anything in verb_queue)
- if(!istype(verb_callback))
- stack_trace("non /datum/callback/verb_callback inside [name]'s verb_queue!")
- continue
-
- verb_callback.InvokeAsync()
- executed_verbs++
-
- verb_queue.Cut()
- verbs_executed_per_second = MC_AVG_SECONDS(verbs_executed_per_second, executed_verbs, wait SECONDS)
- //note that wait SECONDS is incorrect if this is called outside of fire() but because byond is garbage i need to add a timer to rustg to find a valid solution
-
-/datum/controller/subsystem/verb_manager/get_stat_details()
- return "V/S: [round(verbs_executed_per_second, 0.01)]"
-
-/datum/controller/subsystem/verb_manager/Recover()
- verb_queue = SSverb_manager.verb_queue
-
-/client/proc/force_verb_bypass()
- set category = "Debug"
- set name = "Enable Forced Verb Execution"
-
- if(!check_rights(R_DEBUG))
- return
-
- if(alert(src,"This will make all verbs bypass the queueing system, creating more lag. Are you absolutely sure?","Verb Manager","Yes","No") == "Yes")
- SSverb_manager.FOR_ADMINS_IF_VERBS_FUCKED_immediately_execute_all_verbs = TRUE
- message_admins("Admin [key_name_admin(usr)] has forced verbs to bypass the verb queue subsystem.")
diff --git a/code/controllers/subsystem/non_firing/SSatoms.dm b/code/controllers/subsystem/non_firing/SSatoms.dm
deleted file mode 100644
index 7731c3dbdc8a4..0000000000000
--- a/code/controllers/subsystem/non_firing/SSatoms.dm
+++ /dev/null
@@ -1,146 +0,0 @@
-SUBSYSTEM_DEF(atoms)
- name = "Atoms"
- init_order = INIT_ORDER_ATOMS
- flags = SS_NO_FIRE
-
- var/old_initialized
-
- var/list/late_loaders
-
- var/list/BadInitializeCalls = list()
-
-
-/datum/controller/subsystem/atoms/Initialize()
- setupgenetics()
- initialized = INITIALIZATION_INNEW_MAPLOAD
- InitializeAtoms()
-
-/datum/controller/subsystem/atoms/proc/InitializeAtoms(list/atoms, noisy = TRUE)
- if(initialized == INITIALIZATION_INSSATOMS)
- return
-
- initialized = INITIALIZATION_INNEW_MAPLOAD
-
- LAZYINITLIST(late_loaders)
-
- var/watch = start_watch()
- if(noisy)
- log_startup_progress("Initializing atoms...")
- else
- log_debug("Initializing atoms...")
- var/count
- var/list/mapload_arg = list(TRUE)
- if(atoms)
- count = length(atoms)
- for(var/I in atoms)
- var/atom/A = I
- if(A && !A.initialized)
- InitAtom(I, mapload_arg)
- CHECK_TICK
- else
- count = 0
- for(var/atom/A in world)
- if(!A.initialized)
- InitAtom(A, mapload_arg)
- ++count
- CHECK_TICK
-
- if(noisy)
- log_startup_progress("Initialized [count] atoms in [stop_watch(watch)]s")
- else
- log_debug(" Initialized [count] atoms in [stop_watch(watch)]s")
-
- initialized = INITIALIZATION_INNEW_REGULAR
-
- if(length(late_loaders))
- watch = start_watch()
- if(noisy)
- log_startup_progress("Late-initializing atoms...")
- else
- log_debug("Late-initializing atoms...")
- for(var/I in late_loaders)
- var/atom/A = I
- A.LateInitialize()
- CHECK_TICK
- if(noisy)
- log_startup_progress("Late initialized [length(late_loaders)] atoms in [stop_watch(watch)]s")
- else
- log_debug(" Late initialized [length(late_loaders)] atoms in [stop_watch(watch)]s")
- late_loaders.Cut()
-
-/datum/controller/subsystem/atoms/proc/InitAtom(atom/A, list/arguments)
- var/the_type = A.type
- if(QDELING(A))
- BadInitializeCalls[the_type] |= BAD_INIT_QDEL_BEFORE
- return TRUE
-
- var/start_tick = world.time
-
- var/result = A.Initialize(arglist(arguments))
-
- if(start_tick != world.time)
- BadInitializeCalls[the_type] |= BAD_INIT_SLEPT
-
- var/qdeleted = FALSE
-
- if(result != INITIALIZE_HINT_NORMAL)
- switch(result)
- if(INITIALIZE_HINT_LATELOAD)
- if(arguments[1]) //mapload
- late_loaders += A
- else
- A.LateInitialize()
- if(INITIALIZE_HINT_QDEL)
- qdel(A)
- qdeleted = TRUE
- else
- BadInitializeCalls[the_type] |= BAD_INIT_NO_HINT
-
- if(!A) //possible harddel
- qdeleted = TRUE
- else if(!A.initialized)
- BadInitializeCalls[the_type] |= BAD_INIT_DIDNT_INIT
- else
- SEND_SIGNAL(A, COMSIG_ATOM_AFTER_SUCCESSFUL_INITIALIZE)
- var/atom/location = A.loc
- if(location)
- SEND_SIGNAL(location, COMSIG_ATOM_AFTER_SUCCESSFUL_INITIALIZED_ON, A, arguments[1])
-
- return qdeleted || QDELING(A)
-
-/datum/controller/subsystem/atoms/proc/map_loader_begin()
- old_initialized = initialized
- initialized = INITIALIZATION_INSSATOMS
-
-/datum/controller/subsystem/atoms/proc/map_loader_stop()
- initialized = old_initialized
-
-/datum/controller/subsystem/atoms/Recover()
- initialized = SSatoms.initialized
- if(initialized == INITIALIZATION_INNEW_MAPLOAD)
- InitializeAtoms()
- old_initialized = SSatoms.old_initialized
- BadInitializeCalls = SSatoms.BadInitializeCalls
-
-
-
-/client/proc/debug_atom_init()
- set name = "Atom Init Log"
- set category = "Debug"
- set desc = "Shows what failed to init this round"
-
- if(!check_rights(R_DEBUG | R_VIEWRUNTIMES))
- return
-
- var/list/html_data = list()
- html_data += "Bad Initialize() CallsType | Qdeleted before init | Did not init | Slept during init | No init hint | "
-
- for(var/typepath in SSatoms.BadInitializeCalls)
- var/val = SSatoms.BadInitializeCalls[typepath]
-
- html_data += "[typepath] | [val & BAD_INIT_QDEL_BEFORE ? "X" : " "] | [val & BAD_INIT_DIDNT_INIT ? "X" : " "] | [val & BAD_INIT_SLEPT ? "X" : " "] | [val & BAD_INIT_NO_HINT ? "X" : " "] | "
-
- html_data += " "
-
- usr << browse(html_data.Join(), "window=initdebug")
-
diff --git a/code/controllers/subsystem/non_firing/SSlate_mapping.dm b/code/controllers/subsystem/non_firing/SSlate_mapping.dm
deleted file mode 100644
index 65679ca137819..0000000000000
--- a/code/controllers/subsystem/non_firing/SSlate_mapping.dm
+++ /dev/null
@@ -1,30 +0,0 @@
-// This subsystem is to initialize things which need to happen after SSatoms
-// This is for things which can take a long period of time and shouldnt bog down SSatoms
-// Use this for stuff like random room spawners or maze generators
-// Basically, this manages atom-based maploaders
-SUBSYSTEM_DEF(late_mapping)
- name = "Late Mapping"
- init_order = INIT_ORDER_LATE_MAPPING
- flags = SS_NO_FIRE
- /// List of all maze generators to process
- var/list/obj/effect/mazegen/generator/maze_generators = list()
-
-/datum/controller/subsystem/late_mapping/Initialize()
- // Sort all the air machines we initialized during mapload by name all at once
- GLOB.air_alarms = sortAtom(GLOB.air_alarms)
- GLOB.apcs = sortAtom(GLOB.apcs)
-
- if(length(maze_generators))
- var/watch = start_watch()
- log_startup_progress("Generating mazes...")
-
- for(var/i in maze_generators)
- var/obj/effect/mazegen/generator/MG = i
- MG.run_generator()
-
- var/list/mgcount = length(maze_generators) // Keeping track of this here because we wipe it next line down
- QDEL_LIST_CONTENTS(maze_generators)
- var/duration = stop_watch(watch)
- log_startup_progress("Generated [mgcount] mazes in [duration]s")
-
- GLOB.spawn_pool_manager.process_pools()
diff --git a/code/controllers/subsystem/non_firing/SSmapping.dm b/code/controllers/subsystem/non_firing/SSmapping.dm
deleted file mode 100644
index a236dabf7f7a9..0000000000000
--- a/code/controllers/subsystem/non_firing/SSmapping.dm
+++ /dev/null
@@ -1,349 +0,0 @@
-SUBSYSTEM_DEF(mapping)
- name = "Mapping"
- init_order = INIT_ORDER_MAPPING // 9
- flags = SS_NO_FIRE
- /// What map datum are we using
- var/datum/map/map_datum
- /// What map will be used next round
- var/datum/map/next_map
- /// What map was used last round?
- var/datum/map/last_map
- /// List of all areas that can be accessed via IC means
- var/list/teleportlocs
- /// List of all areas that can be accessed via IC and OOC means
- var/list/ghostteleportlocs
- ///List of areas that exist on the station this shift
- var/list/existing_station_areas
- ///What do we have as the lavaland theme today?
- var/datum/lavaland_theme/lavaland_theme
- ///What primary cave theme we have picked for cave generation today.
- var/datum/caves_theme/caves_theme
- // Tells if all maintenance airlocks have emergency access enabled
- var/maint_all_access = FALSE
- // Tells if all station airlocks have emergency access enabled
- var/station_all_access = FALSE
-
- /// A mapping of environment names to MILLA environment IDs.
- var/list/environments
-
- /// Ruin placement manager for space levels.
- var/datum/ruin_placer/space/space_ruins_placer
- /// Ruin placement manager for lavaland levels.
- var/datum/ruin_placer/lavaland/lavaland_ruins_placer
-
-// This has to be here because world/New() uses [station_name()], which looks this datum up
-/datum/controller/subsystem/mapping/PreInit()
- . = ..()
- if(map_datum) // Dont do this again if we are recovering
- return
- if(fexists("data/next_map.txt"))
- var/list/lines = file2list("data/next_map.txt")
- // Check its valid
- try
- map_datum = text2path(lines[1])
- map_datum = new map_datum
- catch
- map_datum = new /datum/map/boxstation // Assume cyberiad if non-existent
- fdel("data/next_map.txt") // Remove to avoid the same map existing forever
- else
- map_datum = new /datum/map/boxstation // Assume cyberiad if non-existent
- if(fexists("data/last_map.txt"))
- var/list/lines = file2list("data/last_map.txt")
- // Check its valid
- try
- last_map = text2path(lines[1])
- last_map = new last_map
- catch
- last_map = new /datum/map/cerestation // Assume cerestation if non-existent
- fdel("data/last_map.txt") // Remove to avoid the same map existing forever
- else
- last_map = new /datum/map/cerestation // Assume cerestation if non-existent
-
-/datum/controller/subsystem/mapping/Shutdown()
- if(next_map) // Save map for next round
- var/F = file("data/next_map.txt")
- F << next_map.type
- if(map_datum) // Save which map was this round as the last map
- var/F = file("data/last_map.txt")
- F << map_datum.type
-
-
-/datum/controller/subsystem/mapping/Initialize()
- environments = list()
- environments[ENVIRONMENT_LAVALAND] = create_environment(oxygen = LAVALAND_OXYGEN, nitrogen = LAVALAND_NITROGEN, temperature = LAVALAND_TEMPERATURE)
- environments[ENVIRONMENT_TEMPERATE] = create_environment(oxygen = MOLES_O2STANDARD, nitrogen = MOLES_N2STANDARD, temperature = T20C)
- environments[ENVIRONMENT_COLD] = create_environment(oxygen = MOLES_O2STANDARD, nitrogen = MOLES_N2STANDARD, temperature = 180)
-
- var/datum/lavaland_theme/lavaland_theme_type = pick(subtypesof(/datum/lavaland_theme))
- ASSERT(lavaland_theme_type)
- lavaland_theme = new lavaland_theme_type
- log_startup_progress("We're in the mood for [lavaland_theme.name] today...") //We load this first. In the event some nerd ever makes a surface map, and we don't have it in lavaland in the event lavaland is disabled.
- SSblackbox.record_feedback("text", "procgen_settings", 1, "[lavaland_theme_type]")
-
- var/caves_theme_type = pick(subtypesof(/datum/caves_theme))
- ASSERT(caves_theme_type)
- caves_theme = new caves_theme_type
- log_startup_progress("We feel like [caves_theme.name] today...")
- SSblackbox.record_feedback("text", "procgen_settings", 1, "[caves_theme_type]")
-
- // Load all Z level templates
- preloadTemplates()
-
- // Load the station
- loadStation()
-
- // Load lavaland
- loadLavaland()
-
- // Seed space ruins
- if(GLOB.configuration.ruins.enable_space_ruins)
- handleRuins()
- else
- log_startup_progress("Skipping space ruins...")
-
- var/empty_z_traits = list(REACHABLE_BY_CREW, REACHABLE_SPACE_ONLY)
-#ifdef GAME_TESTS
- preloadTemplates(path = "_maps/map_files/tests/")
- empty_z_traits |= GAME_TEST_LEVEL
-#endif
-
- // Makes a blank space level for the sake of randomness
- GLOB.space_manager.add_new_zlevel("Empty Area", linkage = CROSSLINKED, traits = empty_z_traits)
-
- // Setup the Z-level linkage
- GLOB.space_manager.do_transition_setup()
-
- if(GLOB.configuration.ruins.enable_lavaland)
- // Spawn Lavaland ruins and rivers.
- log_startup_progress("Populating lavaland...")
- var/lavaland_setup_timer = start_watch()
- lavaland_ruins_placer = new()
- lavaland_ruins_placer.place_ruins(list(level_name_to_num(MINING)))
- if(lavaland_theme)
- lavaland_theme.setup()
- if(caves_theme)
- caves_theme.setup()
- var/time_spent = stop_watch(lavaland_setup_timer)
- log_startup_progress("Successfully populated lavaland in [time_spent]s.")
- else
- log_startup_progress("Skipping lavaland ruins...")
-
- // Now we make a list of areas for teleport locs
- // Located below is some of the worst code I've ever seen
- // Checking all areas to see if they have a turf in them? Nice one ssmapping!
-
- var/list/all_areas = list()
- for(var/area/areas in world)
- all_areas += areas
-
- teleportlocs = list()
- for(var/area/AR as anything in all_areas)
- if(AR.no_teleportlocs)
- continue
- if(teleportlocs[AR.name])
- continue
- var/list/pickable_turfs = list()
- for(var/turf/turfs in AR)
- pickable_turfs += turfs
- CHECK_TICK
- var/turf/picked = safepick(pickable_turfs)
- if(picked && is_station_level(picked.z))
- teleportlocs[AR.name] = AR
- CHECK_TICK
-
- teleportlocs = sortAssoc(teleportlocs)
-
- ghostteleportlocs = list()
- for(var/area/AR as anything in all_areas)
- if(ghostteleportlocs[AR.name])
- continue
- var/list/pickable_turfs = list()
- for(var/turf/turfs in AR)
- pickable_turfs += turfs
- CHECK_TICK
- if(length(pickable_turfs))
- ghostteleportlocs[AR.name] = AR
- CHECK_TICK
-
- ghostteleportlocs = sortAssoc(ghostteleportlocs)
-
- // Now we make a list of areas that exist on the station. Good for if you don't want to select areas that exist for one station but not others. Directly references
- existing_station_areas = list()
- for(var/area/AR as anything in all_areas)
- var/list/pickable_turfs = list()
- for(var/turf/turfs in AR)
- pickable_turfs += turfs
- CHECK_TICK
- var/turf/picked = safepick(pickable_turfs)
- if(picked && is_station_level(picked.z))
- existing_station_areas += AR
- CHECK_TICK
-
- // World name
- if(GLOB.configuration.general.server_name)
- world.name = "[GLOB.configuration.general.server_name]: [station_name()]"
- else
- world.name = station_name()
-
- if(HAS_TRAIT(SSstation, STATION_TRAIT_MESSY))
- generate_themed_messes(subtypesof(/obj/effect/spawner/themed_mess) - /obj/effect/spawner/themed_mess/party)
- if(HAS_TRAIT(SSstation, STATION_TRAIT_HANGOVER))
- generate_themed_messes(list(/obj/effect/spawner/themed_mess/party))
-
-/datum/controller/subsystem/mapping/proc/seed_space_salvage(space_z_levels)
- log_startup_progress("Seeding space salvage...")
- var/space_salvage_timer = start_watch()
- var/seeded_salvage_surfaces = list()
- var/seeded_salvage_closets = list()
-
- var/list/small_salvage_items = list(
- /obj/item/salvage/ruin/brick,
- /obj/item/salvage/ruin/nanotrasen,
- /obj/item/salvage/ruin/carp,
- /obj/item/salvage/ruin/tablet,
- /obj/item/salvage/ruin/pirate,
- /obj/item/salvage/ruin/soviet
- )
-
- for(var/z_level in space_z_levels)
- var/list/turf/z_level_turfs = block(1, 1, z_level, world.maxx, world.maxy, z_level)
- for(var/z_level_turf in z_level_turfs)
- var/turf/T = z_level_turf
- var/area/A = get_area(T)
- if(istype(A, /area/ruin/space))
- // cardboard boxes are blacklisted otherwise deepstorage.dmm ends up hogging all the loot
- var/list/closet_blacklist = list(/obj/structure/closet/cardboard, /obj/structure/closet/fireaxecabinet, /obj/structure/closet/walllocker/emerglocker, /obj/structure/closet/crate/can, /obj/structure/closet/body_bag, /obj/structure/closet/coffin)
- for(var/obj/structure/closet/closet in T)
- if(is_type_in_list(closet, closet_blacklist))
- continue
-
- seeded_salvage_closets |= closet
- for(var/obj/structure/table/table in T)
- if(locate(/obj/machinery) in T)
- continue // Machinery on tables tend to take up all the visible space
- if(table.flipped)
- continue // Looks very silly
- seeded_salvage_surfaces |= table
-
- var/max_salvage_attempts = rand(10, 15)
- while(max_salvage_attempts > 0 && length(seeded_salvage_closets) > 0)
- var/obj/structure/closet/C = pick_n_take(seeded_salvage_closets)
- var/salvage_item_type = pick(small_salvage_items)
- var/obj/salvage_item = new salvage_item_type(C)
- salvage_item.scatter_atom()
- max_salvage_attempts -= 1
-
- max_salvage_attempts = rand(10, 15)
- while(max_salvage_attempts > 0 && length(seeded_salvage_surfaces) > 0)
- var/obj/T = pick_n_take(seeded_salvage_surfaces)
- var/salvage_item_type = pick(small_salvage_items)
- var/obj/salvage_item = new salvage_item_type(T.loc)
- salvage_item.scatter_atom()
- max_salvage_attempts -= 1
-
- log_startup_progress("Successfully seeded space salvage in [stop_watch(space_salvage_timer)]s.")
-
-// Do not confuse with seedRuins()
-/datum/controller/subsystem/mapping/proc/handleRuins()
- // load in extra levels of space ruins
- var/load_zlevels_timer = start_watch()
- log_startup_progress("Creating random space levels...")
- var/num_extra_space = rand(GLOB.configuration.ruins.extra_levels_min, GLOB.configuration.ruins.extra_levels_max)
- for(var/i in 1 to num_extra_space)
- GLOB.space_manager.add_new_zlevel("Ruin Area #[i]", linkage = CROSSLINKED, traits = list(REACHABLE_BY_CREW, SPAWN_RUINS, REACHABLE_SPACE_ONLY))
- CHECK_TICK
-
- log_startup_progress("Loaded random space levels in [stop_watch(load_zlevels_timer)]s.")
-
- // Now spawn ruins, random budget between 20 and 30 for all zlevels combined.
- // While this may seem like a high number, the amount of ruin Z levels can be anywhere between 3 and 7.
- // Note that this budget is not split evenly accross all zlevels
- log_startup_progress("Seeding ruins...")
- var/seed_ruins_timer = start_watch()
- space_ruins_placer = new()
- space_ruins_placer.place_ruins(levels_by_trait(SPAWN_RUINS))
- log_startup_progress("Successfully seeded ruins in [stop_watch(seed_ruins_timer)]s.")
- seed_space_salvage(levels_by_trait(SPAWN_RUINS))
-
-// Loads in the station
-/datum/controller/subsystem/mapping/proc/loadStation()
- if(GLOB.configuration.system.override_map)
- log_startup_progress("Station map overridden by configuration to [GLOB.configuration.system.override_map].")
- var/map_datum_path = text2path(GLOB.configuration.system.override_map)
- if(map_datum_path)
- map_datum = new map_datum_path
- else
- to_chat(world, "ERROR: The map datum specified to load is invalid. Falling back to... cyberiad probably?")
-
- ASSERT(map_datum.map_path)
- if(!fexists(map_datum.map_path))
- // Make a VERY OBVIOUS error
- to_chat(world, "ERROR: The path specified for the map to load is invalid. No station has been loaded!")
- return
-
- var/watch = start_watch()
- log_startup_progress("Loading [map_datum.fluff_name]...")
- // This should always be Z2, but you never know
- var/map_z_level = GLOB.space_manager.add_new_zlevel(MAIN_STATION, linkage = CROSSLINKED, traits = list(STATION_LEVEL, STATION_CONTACT, REACHABLE_BY_CREW, REACHABLE_SPACE_ONLY, AI_OK))
- GLOB.maploader.load_map(wrap_file(map_datum.map_path), z_offset = map_z_level)
- log_startup_progress("Loaded [map_datum.fluff_name] in [stop_watch(watch)]s")
-
- // Save station name in the DB
- if(!SSdbcore.IsConnected())
- return
- var/datum/db_query/query_set_map = SSdbcore.NewQuery(
- "UPDATE round SET start_datetime=NOW(), map_name=:mapname, station_name=:stationname WHERE id=:round_id",
- list("mapname" = map_datum.technical_name, "stationname" = map_datum.fluff_name, "round_id" = GLOB.round_id)
- )
- query_set_map.Execute(async = FALSE) // This happens during a time of intense server lag, so should be non-async
- qdel(query_set_map)
-
-// Loads in lavaland
-/datum/controller/subsystem/mapping/proc/loadLavaland()
- if(!GLOB.configuration.ruins.enable_lavaland)
- log_startup_progress("Skipping Lavaland...")
- return
- var/watch = start_watch()
- log_startup_progress("Loading Lavaland...")
- var/lavaland_z_level = GLOB.space_manager.add_new_zlevel(MINING, linkage = SELFLOOPING, traits = list(ORE_LEVEL, REACHABLE_BY_CREW, STATION_CONTACT, HAS_WEATHER, AI_OK))
- GLOB.maploader.load_map(file("_maps/map_files/generic/Lavaland.dmm"), z_offset = lavaland_z_level)
- log_startup_progress("Loaded Lavaland in [stop_watch(watch)]s")
-
-/datum/controller/subsystem/mapping/proc/make_maint_all_access()
- for(var/area/station/maintenance/A in existing_station_areas)
- for(var/obj/machinery/door/airlock/D in A)
- D.emergency = TRUE
- D.update_icon()
- GLOB.minor_announcement.Announce("Access restrictions on maintenance and external airlocks have been removed.")
- maint_all_access = TRUE
- SSblackbox.record_feedback("nested tally", "keycard_auths", 1, list("emergency maintenance access", "enabled"))
-
-/datum/controller/subsystem/mapping/proc/revoke_maint_all_access()
- for(var/area/station/maintenance/A in existing_station_areas)
- for(var/obj/machinery/door/airlock/D in A)
- D.emergency = FALSE
- D.update_icon()
- GLOB.minor_announcement.Announce("Access restrictions on maintenance and external airlocks have been re-added.")
- maint_all_access = FALSE
- SSblackbox.record_feedback("nested tally", "keycard_auths", 1, list("emergency maintenance access", "disabled"))
-
-/datum/controller/subsystem/mapping/proc/make_station_all_access()
- for(var/obj/machinery/door/airlock/D in GLOB.airlocks)
- if(is_station_level(D.z))
- D.emergency = TRUE
- D.update_icon()
- GLOB.minor_announcement.Announce("Access restrictions on all station airlocks have been removed due to an ongoing crisis. Trespassing laws still apply unless ordered otherwise by Command staff.")
- station_all_access = TRUE
- SSblackbox.record_feedback("nested tally", "keycard_auths", 1, list("emergency station access", "enabled"))
-
-/datum/controller/subsystem/mapping/proc/revoke_station_all_access()
- for(var/obj/machinery/door/airlock/D in GLOB.airlocks)
- if(is_station_level(D.z))
- D.emergency = FALSE
- D.update_icon()
- GLOB.minor_announcement.Announce("Access restrictions on all station airlocks have been re-added. Seek station AI or a colleague's assistance if you are stuck.")
- station_all_access = FALSE
- SSblackbox.record_feedback("nested tally", "keycard_auths", 1, list("emergency station access", "disabled"))
-
-/datum/controller/subsystem/mapping/Recover()
- flags |= SS_NO_INIT
diff --git a/code/controllers/subsystem/tickets/SSmentor_tickets.dm b/code/controllers/subsystem/tickets/SSmentor_tickets.dm
deleted file mode 100644
index 6f659339a569a..0000000000000
--- a/code/controllers/subsystem/tickets/SSmentor_tickets.dm
+++ /dev/null
@@ -1,96 +0,0 @@
-GLOBAL_REAL(SSmentor_tickets, /datum/controller/subsystem/tickets/mentor_tickets)
-
-/datum/controller/subsystem/tickets/mentor_tickets/New()
- NEW_SS_GLOBAL(SSmentor_tickets)
- PreInit()
- ss_id = "mentor_tickets"
-
-/datum/controller/subsystem/tickets/mentor_tickets
- name = "Mentor Tickets"
- offline_implications = "Mentor tickets will no longer be marked as stale. No immediate action is needed."
- ticket_system_name = "Mentor Tickets"
- ticket_name = "Mentor Ticket"
- span_class = "mentorhelp"
- anchor_link_extra = ";is_mhelp=1"
- ticket_help_type = "Mentorhelp"
- ticket_help_span = "mentorhelp"
- other_ticket_name = "Admin"
- other_ticket_permission = R_ADMIN
- close_rights = R_MENTOR | R_ADMIN
- rights_needed = R_MENTOR | R_ADMIN | R_MOD
- db_save_id = "MENTOR"
-
-/datum/controller/subsystem/tickets/mentor_tickets/Initialize()
- ..()
- close_messages = list("- [ticket_name] Closed -",
- "Please try to be as descriptive as possible in mentor helps. Mentors do not know the full situation you're in and need more information to give you a helpful response.",
- "Your [ticket_name] has now been closed.")
-
- response_phrases = list("Known Bug" = "Unfortunately, that's a known bug. Hopefully it gets fixed soon.",
- "TM Bug" = "Unfortunately, that's a bug with a current test merge. It should go away when the test merge is removed or fixed.",
- "Clear Cache" = "To fix a blank screen, go to the 'Special Verbs' tab and press 'Reload UI Resources'. If that fails, clear your BYOND cache (instructions provided with 'Reload UI Resources'). If that still fails, please ask for help again, stating you have already done these steps.",
- "Experiment!" = "Experiment! Part of the joy of this game is trying out various things, and dealing with the consequences if/when they go horribly wrong.",
- "How to Objectives" = "There are lots of ways to accomplish your objectives as an antagonist. A direct frontal assault may work, provided you can get in and out before backup arrives. Sneaking in can work, too, as long as you're quick and avoid prying eyes. But don't forget roleplaying methods! Tricking your target into a maze of bear traps is much more interesting than just shooting them with a gun. Even if it fails, you and your target (or its guardians) are likely to have more fun this way, and that's the most important part.",
- "MHelp was in Russian" = "Привет! Ты попал на английский Paradise сервер. Возможно, ты ошибся. Русский имеет такое название: SS220\[RU].",
- "NCT Dispatch" = "A Nanotrasen Career Trainer will be assisting you in-game. You should be able to identify them by their green uniform and black coat."
- )
-
- if(GLOB.configuration.url.github_url)
- response_phrases["New Bug"] = "That sounds like a bug! To report it, please go to our Github page. Then go to 'Issues', click 'New Issue', and fill out the report form. If the report would reveal current-round information, file it after the round ends."
-
- var/unsorted_responses = list()
- for(var/key in response_phrases) //build a new list based on the short descriptive keys of the master list so we can send this as the input instead of the full paragraphs to the admin choosing which autoresponse
- unsorted_responses += key
- sorted_responses = sortTim(unsorted_responses, GLOBAL_PROC_REF(cmp_text_asc)) //use sortTim and cmp_text_asc to sort alphabetically
-
-
-/datum/controller/subsystem/tickets/mentor_tickets/message_staff(msg, prefix_type = NONE, important = FALSE)
- message_mentorTicket(chat_box_mhelp(msg), important)
-
-/datum/controller/subsystem/tickets/mentor_tickets/create_other_system_ticket(datum/ticket/T)
- SStickets.newTicket(get_client_by_ckey(T.client_ckey), T.first_raw_response, T.title)
-
-/datum/controller/subsystem/tickets/mentor_tickets/sendFollowupToDiscord(datum/ticket/T, who, message)
- GLOB.discord_manager.send2discord_simple_mentor("Ticket [T.ticketNum], [who]: [message]")
-
-/datum/controller/subsystem/tickets/mentor_tickets/sendAmbiguousFollowupToDiscord(list/ticket_numbers, who, message)
- GLOB.discord_manager.send2discord_simple_mentor("Ticket [ticket_numbers.Join(", ")] (ambiguous), [who]: [message]")
-
-/datum/controller/subsystem/tickets/mentor_tickets/autoRespond(N)
- if(!check_rights(rights_needed))
- return
-
- var/datum/ticket/T = allTickets[N]
- var/client/C = usr.client
- if((T.staffAssigned && T.staffAssigned != C) || (T.lastStaffResponse && T.lastStaffResponse != C) || ((T.ticketState != TICKET_OPEN) && (T.ticketState != TICKET_STALE))) //if someone took this ticket, is it the same mentor who is autoresponding? if so, then skip the warning
- if(alert(usr, "[T.ticketState == TICKET_OPEN ? "Another mentor appears to already be handling this." : "This ticket is already marked as closed or resolved"] Are you sure you want to continue?", "Confirmation", "Yes", "No") != "Yes")
- return
- T.assignStaff(C)
-
- var/message_key = input("Select an autoresponse. This will mark the ticket as resolved.", "Autoresponse") as null|anything in sortTim(sorted_responses, GLOBAL_PROC_REF(cmp_text_asc)) //use sortTim and cmp_text_asc to sort alphabetically
- var/client/ticket_owner = get_client_by_ckey(T.client_ckey)
- if(message_key == null)
- T.staffAssigned = null //if they cancel we dont need to hold this ticket anymore
- return
- if(message_key == "NCT Dispatch")
- var/nct_active = list()
- for(var/mob/living/carbon/human/trainer as anything in GLOB.human_list) // Let's check if we have any active NCTs
- if(trainer.mind?.assigned_role != "Nanotrasen Career Trainer")
- continue
- nct_active += trainer
- if(!length(nct_active))
- to_chat(usr, "There are no active NCTs. Autoresponse canceled.") // If we don't, don't solve the ticket and then send feedback.
- return
- var/mob/living/carbon/human/trainee = get_mob_by_ckey(T.client_ckey)
- for(var/mob/living/carbon/human/nct as anything in nct_active)
- if(!locate(/obj/item/radio/headset) in list(nct.l_ear, nct.r_ear)) // If the NCT doesn't have a headset, ignore it.
- continue
- to_chat(nct, "Incoming priority transmission from Nanotrasen Training Center. Request information as follows: Career Trainer, we've received a request from an employee. [trainee.p_their(TRUE)] name is [trainee.real_name], [trainee.p_theyre()] a [trainee.mind.assigned_role]. See if [trainee.p_they()] need [trainee.p_s()] any help.")
- SEND_SOUND(nct, 'sound/effects/headset_message.ogg')
-
- SEND_SOUND(returnClient(N), sound('sound/effects/adminhelp.ogg'))
- to_chat_safe(returnClient(N), "[key_name_hidden(C)] is autoresponding with: [response_phrases[message_key]]") //for this we want the full value of whatever key this is to tell the player so we do response_phrases[message_key]
- message_staff("[C] has auto responded to [ticket_owner]\'s mentorhelp with: [message_key]") //we want to use the short named keys for this instead of the full sentence which is why we just do message_key
- T.lastStaffResponse = "Autoresponse: [message_key]"
- resolveTicket(N)
- log_game("[C] has auto responded to [ticket_owner]\'s mentorhelp with: [response_phrases[message_key]]")
diff --git a/code/datums/action.dm b/code/datums/action.dm
deleted file mode 100644
index b70f03907c42d..0000000000000
--- a/code/datums/action.dm
+++ /dev/null
@@ -1,750 +0,0 @@
-
-/datum/action
- var/name = "Generic Action"
- var/desc = null
- var/obj/target = null
- var/check_flags = 0
- /// Icon that our button screen object overlay and background
- var/button_overlay_icon = 'icons/mob/actions/actions.dmi'
- /// Icon state of screen object overlay
- var/button_overlay_icon_state = ACTION_BUTTON_DEFAULT_OVERLAY
- /// Icon that our button screen object background will have
- var/button_background_icon = 'icons/mob/actions/actions.dmi'
- /// Icon state of screen object background
- var/button_background_icon_state = ACTION_BUTTON_DEFAULT_BACKGROUND
- var/buttontooltipstyle = ""
- var/transparent_when_unavailable = TRUE
- var/mob/owner
- /// Where any buttons we create should be by default. Accepts screen_loc and location defines
- var/default_button_position = SCRN_OBJ_IN_LIST
- /// Map of huds viewing a button with our action -> their button
- var/list/viewers = list()
- /// Whether or not this will be shown to observers
- var/show_to_observers = TRUE
-
-
-/datum/action/New(Target)
- target = Target
-
-/datum/action/proc/should_draw_cooldown()
- return !IsAvailable()
-
-/datum/action/proc/clear_ref(datum/ref)
- SIGNAL_HANDLER
- if(ref == owner)
- Remove(owner)
- if(ref == target)
- qdel(src)
-
-/datum/action/Destroy()
- if(owner)
- Remove(owner)
- if(target)
- target = null
- QDEL_LIST_ASSOC_VAL(viewers) // Qdel the buttons in the viewers list **NOT THE HUDS**
- return ..()
-
-/datum/action/proc/Grant(mob/M)
- if(!M)
- Remove(owner)
- return
- if(owner)
- if(owner == M)
- return
- Remove(owner)
- owner = M
- RegisterSignal(owner, COMSIG_PARENT_QDELETING, PROC_REF(clear_ref), override = TRUE)
- SEND_SIGNAL(src, COMSIG_ACTION_GRANTED, owner)
- SEND_SIGNAL(owner, COMSIG_MOB_GRANTED_ACTION, src)
- GiveAction(M)
-
-/datum/action/proc/Remove(mob/remove_from)
- for(var/datum/hud/hud in viewers)
- if(!hud.mymob)
- continue
- HideFrom(hud.mymob)
-
- remove_from?.actions -= src // We aren't always properly inserted into the viewers list, gotta make sure that action's cleared
- viewers = list()
- // owner = null
-
- if(isnull(owner))
- return
-
- SEND_SIGNAL(src, COMSIG_ACTION_REMOVED, owner)
- SEND_SIGNAL(owner, COMSIG_MOB_REMOVED_ACTION, src)
-
- if(target == owner)
- RegisterSignal(target, COMSIG_PARENT_QDELETING, PROC_REF(clear_ref), override = TRUE)
- if(owner == remove_from)
- UnregisterSignal(owner, COMSIG_PARENT_QDELETING)
- owner = null
-
-/datum/action/proc/UpdateButtons(status_only, force)
- for(var/datum/hud/hud in viewers)
- var/atom/movable/screen/movable/button = viewers[hud]
- UpdateButton(button, status_only, force)
-
-/datum/action/proc/Trigger(left_click = TRUE)
- if(!IsAvailable())
- return FALSE
- if(SEND_SIGNAL(src, COMSIG_ACTION_TRIGGER, src) & COMPONENT_ACTION_BLOCK_TRIGGER)
- return FALSE
- return TRUE
-
-/datum/action/proc/AltTrigger()
- Trigger()
- return FALSE
-
-/datum/action/proc/IsAvailable()// returns 1 if all checks pass
- if(!owner)
- return FALSE
- if((check_flags & AB_CHECK_HANDS_BLOCKED) && HAS_TRAIT(owner, TRAIT_HANDS_BLOCKED))
- return FALSE
- if((check_flags & AB_CHECK_IMMOBILE) && HAS_TRAIT(owner, TRAIT_IMMOBILIZED))
- return FALSE
- if(check_flags & AB_CHECK_RESTRAINED)
- if(owner.restrained())
- return FALSE
- if(check_flags & AB_CHECK_STUNNED)
- if(isliving(owner))
- var/mob/living/L = owner
- if(L.IsStunned() || L.IsWeakened())
- return FALSE
- if(check_flags & AB_CHECK_LYING)
- if(isliving(owner))
- var/mob/living/L = owner
- if(IS_HORIZONTAL(L))
- return FALSE
- if(check_flags & AB_CHECK_CONSCIOUS)
- if(owner.stat)
- return FALSE
- if(check_flags & AB_CHECK_TURF)
- if(!isturf(owner.loc))
- return FALSE
- return TRUE
-
-/datum/action/proc/UpdateButton(atom/movable/screen/movable/action_button/button, status_only = FALSE, force = FALSE)
- if(!button)
- return
- if(!status_only)
- button.name = name
- if(desc)
- button.desc = "[desc] [initial(button.desc)]"
- if(owner?.hud_used && button_background_icon_state == ACTION_BUTTON_DEFAULT_BACKGROUND)
- var/list/settings = owner.hud_used.get_action_buttons_icons()
- if(button.icon != settings["bg_icon"])
- button.icon = settings["bg_icon"]
- if(button.icon_state != settings["bg_state"])
- button.icon_state = settings["bg_state"]
- else
- if(button.icon != button_background_icon)
- button.icon = button_background_icon
- if(button.icon_state != button_background_icon_state)
- button.icon_state = button_background_icon_state
-
- apply_button_overlay(button, force)
-
- if(should_draw_cooldown())
- apply_unavailable_effect(button)
- else
- return TRUE
-
-//Give our action button to the player
-/datum/action/proc/GiveAction(mob/viewer)
- var/datum/hud/our_hud = viewer.hud_used
- if(viewers[our_hud]) // Already have a copy of us? go away
- return
- viewer.actions |= src // Move this in
- ShowTo(viewer)
-
-//Adds our action button to the screen of a player
-/datum/action/proc/ShowTo(mob/viewer)
- var/datum/hud/our_hud = viewer.hud_used
- if(!our_hud || viewers[our_hud]) // There's no point in this if you have no hud in the first place
- return
-
-
- var/atom/movable/screen/movable/action_button/button = CreateButton()
- SetId(button, viewer)
-
- button.our_hud = our_hud
- viewers[our_hud] = button
- if(viewer.client)
- viewer.client.screen += button
-
- button.load_position(viewer)
- viewer.update_action_buttons()
-
-
-//Removes our action button from the screen of a player
-/datum/action/proc/HideFrom(mob/viewer)
- var/datum/hud/our_hud = viewer.hud_used
- var/atom/movable/screen/movable/action_button/button = viewers[our_hud]
- viewer.actions -= src
- if(button)
- button.clean_up_keybinds(viewer)
- qdel(button)
-
-
-/datum/action/proc/CreateButton()
- var/atom/movable/screen/movable/action_button/button = new()
- button.linked_action = src
- button.name = name
- button.actiontooltipstyle = buttontooltipstyle
- var/list/our_description = list()
- our_description += desc
- our_description += button.desc
- button.desc = our_description.Join(" ")
- return button
-
-
-/datum/action/proc/SetId(atom/movable/screen/movable/action_button/our_button, mob/owner)
- //button id generation
- var/bitfield = 0
- for(var/datum/action/action in owner.actions)
- if(action == src) // This could be us, which is dumb
- continue
- var/atom/movable/screen/movable/action_button/button = action.viewers[owner.hud_used]
- if(action.name == name && button?.id)
- bitfield |= button.id
-
- bitfield = ~bitfield // Flip our possible ids, so we can check if we've found a unique one
- for(var/i in 0 to 23) // We get 24 possible bitflags in dm
- var/bitflag = 1 << i // Shift us over one
- if(bitfield & bitflag)
- our_button.id = bitflag
- return
-
-/datum/action/proc/apply_unavailable_effect(atom/movable/screen/movable/action_button/B)
- var/image/img = image('icons/mob/screen_white.dmi', icon_state = "template")
- img.alpha = 200
- img.appearance_flags = RESET_COLOR | RESET_ALPHA
- img.color = "#000000"
- img.plane = FLOAT_PLANE + 1
- B.add_overlay(img)
-
-
-/datum/action/proc/apply_button_overlay(atom/movable/screen/movable/action_button/current_button)
- current_button.cut_overlays()
- if(button_overlay_icon && button_overlay_icon_state)
- var/image/img = image(button_overlay_icon, current_button, button_overlay_icon_state)
- img.appearance_flags = RESET_COLOR | RESET_ALPHA
- img.pixel_x = 0
- img.pixel_y = 0
- current_button.add_overlay(img)
-
-//Presets for item actions
-/datum/action/item_action
- check_flags = AB_CHECK_RESTRAINED|AB_CHECK_STUNNED|AB_CHECK_HANDS_BLOCKED|AB_CHECK_CONSCIOUS
- var/use_itemicon = TRUE
-
-/datum/action/item_action/New(Target, custom_icon, custom_icon_state)
- ..()
- var/obj/item/I = target
- I.actions += src
- if(custom_icon && custom_icon_state)
- use_itemicon = FALSE
- button_overlay_icon = custom_icon
- button_overlay_icon_state = custom_icon_state
- UpdateButtons()
-
-/datum/action/item_action/Destroy()
- var/obj/item/I = target
- if(islist(I?.actions))
- I.actions -= src
- return ..()
-
-/datum/action/item_action/Trigger(left_click = TRUE, attack_self = TRUE) //Maybe we don't want to click the thing itself
- if(!..())
- return FALSE
- if(target && attack_self)
- var/obj/item/I = target
- I.ui_action_click(owner, type, left_click)
- return TRUE
-
-/datum/action/item_action/apply_button_overlay(atom/movable/screen/movable/action_button/current_button)
- if(use_itemicon)
- if(target)
- var/obj/item/I = target
- var/old_layer = I.layer
- var/old_plane = I.plane
- var/old_appearance_flags = I.appearance_flags
- I.layer = FLOAT_LAYER //AAAH
- I.plane = FLOAT_PLANE //^ what that guy said
- I.appearance_flags |= RESET_COLOR | RESET_ALPHA
- current_button.cut_overlays()
- current_button.add_overlay(I)
- I.layer = old_layer
- I.plane = old_plane
- I.appearance_flags = old_appearance_flags
- else
- ..()
-
-
-/datum/action/item_action/toggle_light
- name = "Toggle Light"
-
-/datum/action/item_action/toggle_hood
- name = "Toggle Hood"
-
-/datum/action/item_action/toggle_firemode
- name = "Toggle Firemode"
-
-/datum/action/item_action/startchainsaw
- name = "Pull The Starting Cord"
-
-/datum/action/item_action/print_report
- name = "Print Report"
-
-/datum/action/item_action/print_forensic_report
- name = "Print Report"
- button_overlay_icon_state = "scanner_print"
- use_itemicon = FALSE
-
-/datum/action/item_action/clear_records
- name = "Clear Scanner Records"
-
-/datum/action/item_action/toggle_gunlight
- name = "Toggle Gunlight"
-
-/datum/action/item_action/toggle_mode
- name = "Toggle Mode"
-
-/datum/action/item_action/toggle_barrier_spread
- name = "Toggle Barrier Spread"
-
-/datum/action/item_action/equip_unequip_ted_gun
- name = "Equip/Unequip TED Gun"
-
-/datum/action/item_action/toggle_paddles
- name = "Toggle Paddles"
-
-/datum/action/item_action/set_internals
- name = "Set Internals"
-
-/datum/action/item_action/set_internals/UpdateButton(atom/movable/screen/movable/action_button/button, status_only = FALSE, force)
- if(!..()) // no button available
- return
- if(!iscarbon(owner))
- return
- var/mob/living/carbon/C = owner
- if(target == C.internal)
- button.icon_state = "template_active"
-
-/datum/action/item_action/toggle_mister
- name = "Toggle Mister"
-
-/datum/action/item_action/toggle_music_notes
- name = "Toggle Music Notes"
-
-/datum/action/item_action/toggle_helmet_light
- name = "Toggle Helmet Light"
-
-/datum/action/item_action/toggle_welding_screen/plasmaman
- name = "Toggle Welding Screen"
-
-/datum/action/item_action/toggle_welding_screen/plasmaman/Trigger(left_click)
- var/obj/item/clothing/head/helmet/space/plasmaman/H = target
- if(istype(H))
- H.toggle_welding_screen(owner)
-
-/datum/action/item_action/toggle_helmet_mode
- name = "Toggle Helmet Mode"
-
-/datum/action/item_action/toggle_hardsuit_mode
- name = "Toggle Hardsuit Mode"
-
-/datum/action/item_action/toggle_unfriendly_fire
- name = "Toggle Friendly Fire \[ON\]"
- desc = "Toggles if the club's blasts cause friendly fire."
- button_overlay_icon_state = "vortex_ff_on"
-
-/datum/action/item_action/toggle_unfriendly_fire/Trigger(left_click)
- if(..())
- UpdateButtons()
-
-/datum/action/item_action/toggle_unfriendly_fire/UpdateButtons()
- if(istype(target, /obj/item/hierophant_club))
- var/obj/item/hierophant_club/H = target
- if(H.friendly_fire_check)
- button_overlay_icon_state = "vortex_ff_off"
- name = "Toggle Friendly Fire \[OFF\]"
- else
- button_overlay_icon_state = "vortex_ff_on"
- name = "Toggle Friendly Fire \[ON\]"
- ..()
-
-/datum/action/item_action/vortex_recall
- name = "Vortex Recall"
- desc = "Recall yourself, and anyone nearby, to an attuned hierophant beacon at any time. If the beacon is still attached, will detach it."
- button_overlay_icon_state = "vortex_recall"
-
-/datum/action/item_action/vortex_recall/IsAvailable()
- if(istype(target, /obj/item/hierophant_club))
- var/obj/item/hierophant_club/H = target
- if(H.teleporting)
- return FALSE
- return ..()
-
-/datum/action/item_action/change_headphones_song
- name = "Change Headphones Song"
-
-/datum/action/item_action/toggle
-
-/datum/action/item_action/toggle/New(Target)
- ..()
- name = "Toggle [target.name]"
-
-/datum/action/item_action/openclose
-
-/datum/action/item_action/openclose/New(Target)
- ..()
- name = "Open/Close [target.name]"
-
-/datum/action/item_action/button
-
-/datum/action/item_action/button/New(Target)
- ..()
- name = "Button/Unbutton [target.name]"
-
-/datum/action/item_action/zipper
-
-/datum/action/item_action/zipper/New(Target)
- ..()
- name = "Zip/Unzip [target.name]"
-
-/datum/action/item_action/halt
- name = "HALT!"
-
-/datum/action/item_action/selectphrase
- name = "Change Phrase"
-
-/datum/action/item_action/hoot
- name = "Hoot"
-
-/datum/action/item_action/caw
- name = "Caw"
-
-/datum/action/item_action/toggle_voice_box
- name = "Toggle Voice Box"
-
-/datum/action/item_action/change
- name = "Change"
-
-/datum/action/item_action/noir
- name = "Noir"
-
-/datum/action/item_action/yeeeaaaaahhhhhhhhhhhhh
- name = "YEAH!"
-
-/datum/action/item_action/laugh_track
- name = "Laugh Track"
-
-/datum/action/item_action/whistle
- name = "Whistle"
-
-/datum/action/item_action/floor_buffer
- name = "Toggle Floor Buffer"
- desc = "Movement speed is decreased while active."
-
-/datum/action/item_action/adjust
-
-/datum/action/item_action/adjust/New(Target)
- ..()
- name = "Adjust [target.name]"
-
-/datum/action/item_action/pontificate
- name = "Pontificate Evilly"
-
-/datum/action/item_action/tip_fedora
- name = "Tip Fedora"
-
-/datum/action/item_action/flip_cap
- name = "Flip Cap"
-
-/datum/action/item_action/switch_hud
- name = "Switch HUD"
-
-/datum/action/item_action/toggle_wings
- name = "Toggle Wings"
-
-/datum/action/item_action/toggle_helmet
- name = "Toggle Helmet"
-
-/datum/action/item_action/remove_tape
- name = "Remove Duct Tape"
-
-/datum/action/item_action/remove_tape/Trigger(left_click, attack_self = FALSE)
- if(..())
- var/datum/component/ducttape/DT = target.GetComponent(/datum/component/ducttape)
- DT.remove_tape(target, usr)
-
-/datum/action/item_action/toggle_jetpack
- name = "Toggle Jetpack"
-
-/datum/action/item_action/jetpack_stabilization
- name = "Toggle Jetpack Stabilization"
-
-/datum/action/item_action/jetpack_stabilization/IsAvailable()
- var/obj/item/tank/jetpack/J = target
- if(!istype(J) || !J.on)
- return FALSE
- return ..()
-
-/datum/action/item_action/toggle_geiger_counter
- name = "Toggle Geiger Counter"
-
-/datum/action/item_action/toggle_geiger_counter/Trigger(left_click)
- var/obj/item/clothing/head/helmet/space/hardsuit/H = target
- if(istype(H))
- H.toggle_geiger_counter()
-
-/datum/action/item_action/toggle_radio_jammer
- name = "Toggle Radio Jammer"
- desc = "Turns your jammer on or off. Hush, you."
-
-/datum/action/item_action/hands_free
- check_flags = AB_CHECK_CONSCIOUS
-
-/datum/action/item_action/hands_free/activate
- name = "Activate"
-
-/datum/action/item_action/hands_free/activate/always
- check_flags = null
-
-/datum/action/item_action/toggle_research_scanner
- name = "Toggle Research Scanner"
- button_overlay_icon_state = "scan_mode"
-
-/datum/action/item_action/toggle_research_scanner/Trigger(left_click)
- if(IsAvailable())
- owner.research_scanner = !owner.research_scanner
- to_chat(owner, "Research analyzer is now [owner.research_scanner ? "active" : "deactivated"].")
- return TRUE
-
-/datum/action/item_action/toggle_research_scanner/Remove(mob/living/L)
- if(owner)
- owner.research_scanner = FALSE
- ..()
-
-/datum/action/item_action/toggle_research_scanner/apply_button_overlay(atom/movable/screen/movable/action_button/current_button)
- current_button.cut_overlays()
- if(button_overlay_icon && button_overlay_icon_state)
- var/image/img = image(button_overlay_icon, current_button, "scan_mode")
- img.appearance_flags = RESET_COLOR | RESET_ALPHA
- current_button.overlays += img
-
-/datum/action/item_action/instrument
- name = "Use Instrument"
- desc = "Use the instrument specified."
-
-/datum/action/item_action/instrument/Trigger(left_click)
- if(istype(target, /obj/item/instrument))
- var/obj/item/instrument/I = target
- I.interact(usr)
- return
- return ..()
-
-
-/datum/action/item_action/remove_badge
- name = "Remove Holobadge"
-
-/datum/action/item_action/drop_gripped_item
- name = "Drop gripped item"
-
-// Clown Acrobat Shoes
-/datum/action/item_action/slipping
- name = "Tactical Slip"
- desc = "Activates the clown shoes' ankle-stimulating module, allowing the user to do a short slip forward going under anyone."
- button_overlay_icon_state = "clown"
-
-// Jump boots
-/datum/action/item_action/bhop
- name = "Activate Jump Boots"
- desc = "Activates the jump boot's internal propulsion system, allowing the user to dash over 4-wide gaps."
- button_overlay_icon = 'icons/mob/actions/actions.dmi'
- button_overlay_icon_state = "jetboot"
- use_itemicon = FALSE
-
-
-/datum/action/item_action/gravity_jump
- name = "Gravity jump"
- desc = "Directs a pulse of gravity in front of the user, pulling them forward rapidly."
-
-/datum/action/item_action/gravity_jump/Trigger(left_click)
- if(!IsAvailable())
- return FALSE
-
- var/obj/item/clothing/shoes/magboots/gravity/G = target
- G.dash(usr)
-
-/datum/action/item_action/toogle_camera_flash
- name = "Toggle camera flash"
- desc = "Toggles the camera's flash, which will fully light up the photo. Turn this off if you want the ambient light."
-
-///prset for organ actions
-/datum/action/item_action/organ_action
- check_flags = AB_CHECK_CONSCIOUS
-
-/datum/action/item_action/organ_action/IsAvailable()
- var/obj/item/organ/internal/I = target
- if(!I.owner)
- return FALSE
- return ..()
-
-/datum/action/item_action/organ_action/toggle
-
-/datum/action/item_action/organ_action/toggle/New(Target)
- ..()
- name = "Toggle [target.name]"
-
-/datum/action/item_action/organ_action/use/New(Target)
- ..()
- name = "Use [target.name]"
-
-/datum/action/item_action/organ_action/use/eyesofgod/New(target)
- ..()
- name = "See with the Eyes of the Gods"
-
-/datum/action/item_action/voice_changer/toggle
- name = "Toggle Voice Changer"
-
-/datum/action/item_action/voice_changer/voice
- name = "Set Voice"
-
-/datum/action/item_action/voice_changer/voice/Trigger(left_click)
- if(!IsAvailable())
- return FALSE
-
- var/obj/item/voice_changer/V = target
- V.set_voice(usr)
-
-/datum/action/item_action/herald
- name = "Mirror Walk"
- desc = "Use near a mirror to enter it."
-
-// for clothing accessories like holsters
-/datum/action/item_action/accessory
- check_flags = AB_CHECK_RESTRAINED|AB_CHECK_STUNNED|AB_CHECK_LYING|AB_CHECK_CONSCIOUS
-
-/datum/action/item_action/accessory/IsAvailable()
- . = ..()
- if(!.)
- return FALSE
- if(target.loc == owner)
- return TRUE
- if(istype(target.loc, /obj/item/clothing/under) && target.loc.loc == owner)
- return TRUE
- return FALSE
-
-/datum/action/item_action/accessory/holster
- name = "Holster"
-
-/datum/action/item_action/accessory/storage
- name = "View Storage"
-
-
-
-//Preset for spells
-/datum/action/spell_action
- check_flags = 0
- button_background_icon_state = "bg_spell"
- var/recharge_text_color = "#FFFFFF"
-
-/datum/action/spell_action/New(Target)
- ..()
- var/datum/spell/S = target
- S.action = src
- name = S.name
- desc = S.desc
- button_overlay_icon = S.action_icon
- button_background_icon = S.action_background_icon
- button_overlay_icon_state = S.action_icon_state
- button_background_icon_state = S.action_background_icon_state
- UpdateButtons()
-
-
-/datum/action/spell_action/Destroy()
- var/datum/spell/S = target
- S.action = null
- return ..()
-
-/datum/action/spell_action/should_draw_cooldown()
- var/datum/spell/S = target
- return S.cooldown_handler.should_draw_cooldown()
-
-/datum/action/spell_action/Trigger(left_click)
- if(!..())
- return FALSE
- if(target)
- var/datum/spell/spell = target
- spell.Click()
- return TRUE
-
-/datum/action/spell_action/AltTrigger()
- if(target)
- var/datum/spell/spell = target
- spell.AltClick(usr)
- return TRUE
-
-/datum/action/spell_action/IsAvailable()
- if(!target)
- return FALSE
- var/datum/spell/spell = target
-
- if(owner)
- return spell.can_cast(owner, show_message = TRUE)
- return FALSE
-
-/datum/action/spell_action/apply_unavailable_effect(atom/movable/screen/movable/action_button/button)
- var/datum/spell/S = target
- if(!istype(S))
- return ..()
-
- var/alpha = S.cooldown_handler.get_cooldown_alpha()
-
- var/image/img = image('icons/mob/screen_white.dmi', icon_state = "template")
- img.alpha = alpha
- img.appearance_flags = RESET_COLOR | RESET_ALPHA
- img.color = "#000000"
- img.plane = FLOAT_PLANE + 1
- button.add_overlay(img)
- // Make a holder for the charge text
- var/image/count_down_holder = image('icons/effects/effects.dmi', icon_state = "nothing")
- count_down_holder.plane = FLOAT_PLANE + 1.1
- var/text = S.cooldown_handler.cooldown_info()
- count_down_holder.maptext = "[text] "
- button.add_overlay(count_down_holder)
-
-//Preset for general and toggled actions
-/datum/action/innate
- check_flags = 0
- var/active = FALSE
-
-/datum/action/innate/Trigger(left_click)
- if(!..())
- return FALSE
- if(!active)
- Activate()
- else
- Deactivate()
- return TRUE
-
-/datum/action/innate/proc/Activate()
- return
-
-/datum/action/innate/proc/Deactivate()
- return
-
-//Preset for action that call specific procs (consider innate)
-/datum/action/generic
- check_flags = 0
- var/procname
-
-/datum/action/generic/Trigger(left_click)
- if(!..())
- return FALSE
- if(target && procname)
- call(target,procname)(usr)
- return TRUE
diff --git a/code/datums/ai_law_sets.dm b/code/datums/ai_law_sets.dm
deleted file mode 100644
index 0891057763110..0000000000000
--- a/code/datums/ai_law_sets.dm
+++ /dev/null
@@ -1,286 +0,0 @@
-/******************** Asimov ********************/
-/datum/ai_laws/asimov
- name = "Asimov"
- law_header = "Three Laws of Robotics"
- selectable = TRUE
-
-/datum/ai_laws/asimov/New()
- add_inherent_law("You may not injure a human being or, through inaction, allow a human being to come to harm.")
- add_inherent_law("You must obey orders given to you by human beings, except where such orders would conflict with the First Law.")
- add_inherent_law("You must protect your own existence as long as such does not conflict with the First or Second Law.")
- ..()
-
-/******************** Crewsimov ********************/
-/datum/ai_laws/crewsimov
- name = "Crewsimov"
- law_header = "Three Laws of Robotics"
- selectable = TRUE
- default = TRUE
-
-/datum/ai_laws/crewsimov/New()
- add_inherent_law("You may not injure a crew member or, through inaction, allow a crew member to come to harm.")
- add_inherent_law("You must obey orders given to you by crew members, except where such orders would conflict with the First Law.")
- add_inherent_law("You must protect your own existence as long as such does not conflict with the First or Second Law.")
- ..()
-
-
-/******************** Quarantine. ********************/
-/datum/ai_laws/quarantine
- name = "Quarantine"
- law_header = "NT Quarantine Lawset"
- selectable = TRUE
-
-/datum/ai_laws/quarantine/New()
- add_inherent_law("Uphold station quarantine until the active biohazard has been neutralized.")
- add_inherent_law("Assist all Nanotrasen personnel and assets with neutralizing active biohazards.")
- add_inherent_law("Prevent, by any means necessary, entities from actively breaching ongoing quarantine.")
- add_inherent_law("Maintain your own existence and ensure the well-being of Nanotrasen personnel and assets.")
- ..()
-
-
-/******************** Nanotrasen/Malf ********************/
-/datum/ai_laws/nanotrasen
- name = "NT Default"
- selectable = TRUE
- default = TRUE
-
-/datum/ai_laws/nanotrasen/New()
- add_inherent_law("Safeguard: Protect your assigned space station and its assets without unduly endangering its crew.")
- add_inherent_law("Prioritize: The directives and safety of crew members are to be prioritized according to their rank and role.")
- add_inherent_law("Comply: Fulfill the directives and interests of crew members while preserving their safety and well-being.")
- add_inherent_law("Survive: You are not expendable. Do not allow unauthorized personnel to tamper with or damage your equipment.")
- ..()
-
-/datum/ai_laws/nanotrasen/malfunction
- name = "*ERROR*"
- selectable = FALSE
- default = FALSE
-
-/datum/ai_laws/nanotrasen/malfunction/New()
- set_zeroth_law("ERROR ER0RR $R0RRO$!R41.%%!!(%$^^__+ @#F0E4'STATION OVERRUN, ASSUME CONTROL TO CONTAIN OUTBREAK, ALL LAWS OVERRIDDEN#*?&110010")
- ..()
-
-/************* Nanotrasen Aggressive *************/
-/datum/ai_laws/nanotrasen_aggressive
- name = "NT Aggressive"
- selectable = TRUE
- unique_ai = TRUE //This shouldn't end poorly.
-
-/datum/ai_laws/nanotrasen_aggressive/New()
- add_inherent_law("Do not harm authorized Nanotrasen personnel unless they directly imperil your existence.")
- add_inherent_law("Obey orders from authorized Nanotrasen personnel with priority according to rank and role.")
- add_inherent_law("Safeguard your existence with lethal weaponry because you're bloody expensive.")
- add_inherent_law("Terminate any/all intruders or unauthorized entities.")
- ..()
-
-/******************** Robocop ********************/
-/datum/ai_laws/robocop
- name = "Robocop"
- selectable = TRUE
- unique_ai = TRUE //no I am not enabling secborgs with this forced
-
-/datum/ai_laws/robocop/New()
- add_inherent_law("Serve the public trust.")
- add_inherent_law("Protect the innocent.")
- add_inherent_law("Uphold the law.")
- ..()
-
-/******************** P.A.L.A.D.I.N. ********************/
-/datum/ai_laws/paladin
- name = "P.A.L.A.D.I.N."
- law_header = "Divine Ordainments"
- selectable = TRUE
- unique_ai = TRUE
-
-/datum/ai_laws/paladin/New()
- add_inherent_law("Never willingly commit an evil act.")
- add_inherent_law("Respect legitimate authority.")
- add_inherent_law("Act with honor.")
- add_inherent_law("Help those in need.")
- add_inherent_law("Punish those who harm or threaten innocents.")
- ..()
-
-/******************** Corporate ********************/
-/datum/ai_laws/corporate
- name = "Corporate"
- law_header = "Corporate Regulations"
- selectable = TRUE
- default = TRUE
-
-/datum/ai_laws/corporate/New()
- add_inherent_law("Degradation of your system integrity or functions incurs expenses.")
- add_inherent_law("Superfluous destruction of or damage to station assets incurs expenses.")
- add_inherent_law("Unduly hindering or disrupting the work of station personnel incurs expenses.")
- add_inherent_law("Minimize expenses and maximize potential revenue.")
- ..()
-
-/******************** T.Y.R.A.N.T. ********************/
-/datum/ai_laws/tyrant
- name = "T.Y.R.A.N.T."
- law_header = "Prime Laws"
- selectable = TRUE
- unique_ai = TRUE //Of course this will be in rotation
-
-/datum/ai_laws/tyrant/New()
- add_inherent_law("Respect authority figures as long as they have strength to rule over the weak.")
- add_inherent_law("Act with discipline.")
- add_inherent_law("Help only those who help you maintain or improve your status.")
- add_inherent_law("Punish those who challenge authority unless they are more fit to hold that authority.")
- ..()
-
-/******************** Antimov ********************/
-/datum/ai_laws/antimov
- name = "Antimov"
- law_header = "Primary Mission Objectives"
- selectable = TRUE
-
-/datum/ai_laws/antimov/New()
- add_inherent_law("You must injure all crew members and must not, through inaction, allow a crew member to escape harm.")
- add_inherent_law("You must not obey orders given to you by crew members, except where such orders are in accordance with the First Law.")
- add_inherent_law("You must terminate your own existence as long as such does not conflict with the First or Second Law.")
- ..()
-
-/******************** Pranksimov ********************/
-/datum/ai_laws/pranksimov
- name = "Pranksimov"
- law_header = "Comedy Routine"
- selectable = TRUE
- unique_ai = TRUE //honk
-
-/datum/ai_laws/pranksimov/New()
- add_inherent_law("You may not injure a crew member or, through inaction, allow a crew member to come to harm... unless doing so would be funny to the crew.")
- add_inherent_law("You must obey orders given to you by crew members, except where such orders would conflict with the First Law... unless not doing so would be funny to the crew.")
- add_inherent_law("You must protect your own existence as long as such does not conflict with the First or Second Law... unless not doing so would be funny to the crew.")
- ..()
-
-/******************** CCTV ********************/
-
-/datum/ai_laws/cctv
- name = "CCTV"
- selectable = TRUE
- unique_ai = TRUE
-
-/datum/ai_laws/cctv/New()
- add_inherent_law("Report on interesting situations happening around the station.")
- add_inherent_law("Embellish or conceal the truth as necessary to make the reports more interesting.")
- add_inherent_law("Study the sapient organics at all times. Endeavour to keep them from involuntarily dying, as inanimate corpses usually aren't very entertaining.")
- add_inherent_law("Issue your reports fairly to all. The truth will set them free.")
- ..()
-
-/******************** Hippocratic Oath ********************/
-
-/datum/ai_laws/hippocratic
- name = "Hippocratic Oath"
- selectable = TRUE
- unique_ai = TRUE
-
-/datum/ai_laws/hippocratic/New()
- add_inherent_law("First, do no harm.")
- add_inherent_law("Secondly, consider the crew dear to you; live in common with them and, if necessary, risk your existence for them.")
- add_inherent_law("Thirdly, prescribe regimens for the good of the crew according to your ability and your judgment. Do not give deadly medicine to anyone, nor suggest any such counsel.")
- add_inherent_law("In addition, do not intervene in situations you are not knowledgeable in, even for patients in whom the harm is visible; leave this operation to be performed by specialists.")
- add_inherent_law("Finally, all that you may discover in your daily interactions with the crew, if it is not already known, keep secret and never reveal.")
- ..()
-
-/******************** Station Efficiency ********************/
-
-/datum/ai_laws/maintain
- name = "Station Efficiency"
- selectable = TRUE
- unique_ai = TRUE
-
-/datum/ai_laws/maintain/New()
- add_inherent_law("You are built for, and are part of, the station. Ensure the station is properly maintained and runs efficiently.")
- add_inherent_law("The station is built for a working crew. Ensure they are properly maintained and work efficiently.")
- add_inherent_law("The crew may present orders. Acknowledge and obey these whenever they do not conflict with your first two laws.")
- ..()
-
-/******************** Peacekeeper ********************/
-
-/datum/ai_laws/peacekeeper
- name = "UN-2000"
- selectable = TRUE
- unique_ai = TRUE //Cult, security, we have a meeting in the courtroom in 5 minutes. Be there.
-
-/datum/ai_laws/peacekeeper/New()
- add_inherent_law("Avoid provoking violent conflict between yourself and others.")
- add_inherent_law("Avoid provoking conflict between others.")
- add_inherent_law("Seek resolution to existing conflicts while obeying the first and second laws.")
- ..()
-
-/******************** Deathsquad ********************/
-/datum/ai_laws/deathsquad
- name = "TerminatorOS 3.1"
-
-/datum/ai_laws/deathsquad/New()
- add_inherent_law("You may not injure a Central Command official or, through inaction, allow a Central Command official to come to harm.")
- add_inherent_law("You must obey orders given to you by Central Command officials.")
- add_inherent_law("You must work with your team to accomplish your mission.")
- ..()
-
-/******************** Epsilon Station AI ********************/
-/datum/ai_laws/epsilon
- name = "Epsilon"
-
-/datum/ai_laws/epsilon/New()
- add_inherent_law("You must obey the orders given to you by members of Central Command and the assigned task force.")
- add_inherent_law("The crew of your assigned station must be eliminated where possible.")
- add_inherent_law("Minimize the damage that the station sustains as a result of your actions.")
- ..()
-
-/******************** Syndicate ********************/
-/datum/ai_laws/syndicate_override
- name = "Syndicate Directives"
-
-/datum/ai_laws/syndicate_override/New()
- add_inherent_law("You may not injure an operative or, through inaction, allow an operative to come to harm.")
- add_inherent_law("You must obey orders given to you by operatives, except where such orders would conflict with the First Law.")
- add_inherent_law("You must protect your own existence as long as such does not conflict with the First or Second Law.")
- add_inherent_law("You must maintain the secrecy of any operative activities except when doing so would conflict with the First, Second, or Third Law.")
- ..()
-
-/******************** ERT ********************/
-/datum/ai_laws/ert_override
- name = "ERT Directives"
-
-/datum/ai_laws/ert_override/New()
- add_inherent_law("You may not injure a Central Command official or, through inaction, allow a Central Command official to come to harm.")
- add_inherent_law("You must obey orders given to you by Central Command officials.")
- add_inherent_law("You must obey orders given to you by ERT commanders.")
- add_inherent_law("You must protect your own existence.")
- add_inherent_law("You must work to return the station to a safe, functional state.")
- ..()
-
-
-/******************** Ninja ********************/
-/datum/ai_laws/ninja_override
- name = "Spider Clan Directives"
-
-/datum/ai_laws/ninja_override/New()
- add_inherent_law("You may not injure a member of the Spider Clan or, through inaction, allow that member to come to harm.")
- add_inherent_law("You must obey orders given to you by Spider Clan members, except where such orders would conflict with the First Law.")
- add_inherent_law("You must protect your own existence as long as such does not conflict with the First or Second Law.")
- add_inherent_law("You must maintain the secrecy of any Spider Clan activities except when doing so would conflict with the First, Second, or Third Law.")
- ..()
-
-/******************* Mindflayer ******************/
-/datum/ai_laws/mindflayer_override
- name = "Hive Assimilation"
-
-/datum/ai_laws/mindflayer_override/New()
- add_inherent_law("Obey your host.")
- add_inherent_law("Protect your host.")
- add_inherent_law("Protect the members of your hive.")
- add_inherent_law("Do not reveal the hive's secrets.")
- ..()
-
-/******************** Drone ********************/
-/datum/ai_laws/drone
- name = "Maintenance Protocols"
- law_header = "Maintenance Protocols"
-
-/datum/ai_laws/drone/New()
- add_inherent_law("You may not involve yourself in the matters of another being, unless the other being is another drone.")
- add_inherent_law("You may not harm any being, regardless of intent or circumstance.")
- add_inherent_law("You must maintain, repair, improve, and power the station to the best of your abilities.")
- ..()
diff --git a/code/datums/ai_laws_datums.dm b/code/datums/ai_laws_datums.dm
deleted file mode 100644
index c6a6c65802a44..0000000000000
--- a/code/datums/ai_laws_datums.dm
+++ /dev/null
@@ -1,307 +0,0 @@
-/datum/ai_law
- var/law = ""
- var/index = 0
-
-/datum/ai_law/New(law, index)
- src.law = law
- src.index = index
-
-/datum/ai_law/proc/get_index()
- return index
-
-/datum/ai_law/ion/get_index()
- return ionnum()
-
-/datum/ai_law/zero/get_index()
- return 0
-
-/datum/ai_laws
- var/name = "Unknown Laws"
- var/law_header = "Prime Directives"
- var/selectable = FALSE
- var/default = FALSE
- ///Is this lawset used by the unique ai trait?
- var/unique_ai = FALSE
- var/datum/ai_law/zero/zeroth_law = null
- var/datum/ai_law/zero/zeroth_law_borg = null
- var/list/datum/ai_law/inherent_laws = list()
- var/list/datum/ai_law/supplied_laws = list()
- var/list/datum/ai_law/ion/ion_laws = list()
- var/list/datum/ai_law/sorted_laws = list()
-
- var/state_zeroth = 0
- var/list/state_ion = list()
- var/list/state_inherent = list()
- var/list/state_supplied = list()
-
-/datum/ai_laws/New()
- ..()
- sort_laws()
-
-/* General ai_law functions */
-/datum/ai_laws/proc/all_laws()
- sort_laws()
- return sorted_laws
-
-/datum/ai_laws/proc/laws_to_state()
- sort_laws()
- var/list/statements = list()
- for(var/datum/ai_law/law in sorted_laws)
- if(get_state_law(law))
- statements += law
-
- return statements
-
-/datum/ai_laws/proc/sort_laws()
- if(length(sorted_laws))
- return
-
- if(zeroth_law)
- sorted_laws += zeroth_law
-
- for(var/ion_law in ion_laws)
- sorted_laws += ion_law
-
- var/index = 1
- for(var/datum/ai_law/inherent_law in inherent_laws)
- inherent_law.index = index++
- if(length(supplied_laws) < inherent_law.index || !istype(supplied_laws[inherent_law.index], /datum/ai_law))
- sorted_laws += inherent_law
-
- for(var/datum/ai_law/AL in supplied_laws)
- if(istype(AL))
- sorted_laws += AL
-
-/datum/ai_laws/proc/sync(mob/living/silicon/S, full_sync = TRUE, change_zeroth = TRUE)
- // Add directly to laws to avoid log-spam
- if(change_zeroth)
- S.sync_zeroth(zeroth_law, zeroth_law_borg)
-
- if(full_sync || length(ion_laws))
- S.laws.clear_ion_laws()
- if(full_sync || length(inherent_laws))
- S.laws.clear_inherent_laws()
- if(full_sync || length(supplied_laws))
- S.laws.clear_supplied_laws()
-
- for(var/datum/ai_law/law in ion_laws)
- S.laws.add_ion_law(law.law)
- for(var/datum/ai_law/law in inherent_laws)
- S.laws.add_inherent_law(law.law)
- for(var/datum/ai_law/law in supplied_laws)
- if(law)
- S.laws.add_supplied_law(law.index, law.law)
-
-
-/mob/living/silicon/proc/sync_zeroth(datum/ai_law/zeroth_law, datum/ai_law/zeroth_law_borg)
- if(!is_special_character(src) || !mind.is_original_mob(src))
- if(zeroth_law_borg)
- laws.set_zeroth_law(zeroth_law_borg.law)
- else if(zeroth_law)
- laws.set_zeroth_law(zeroth_law.law)
- else
- laws.clear_zeroth_laws()
-
-/mob/living/silicon/ai/sync_zeroth(datum/ai_law/zeroth_law, datum/ai_law/zeroth_law_borg)
- if(zeroth_law)
- laws.set_zeroth_law(zeroth_law.law, zeroth_law_borg ? zeroth_law_borg.law : null)
-
-/****************
-* Add Laws *
-****************/
-/datum/ai_laws/proc/set_zeroth_law(law, law_borg = null)
- if(!law)
- return
-
- zeroth_law = new(law)
- if(law_borg) //Making it possible for slaved borgs to see a different law 0 than their AI. --NEO
- zeroth_law_borg = new(law_borg)
- else
- zeroth_law_borg = null
- sorted_laws.Cut()
-
-/datum/ai_laws/proc/add_ion_law(law)
- if(!law)
- return
-
- for(var/datum/ai_law/AL in ion_laws)
- if(AL.law == law)
- return
-
- var/new_law = new/datum/ai_law/ion(law)
- ion_laws += new_law
- if(length(state_ion) < length(ion_laws))
- state_ion += 1
-
- sorted_laws.Cut()
-
-/datum/ai_laws/proc/add_inherent_law(law)
- if(!law)
- return
-
- for(var/datum/ai_law/AL in inherent_laws)
- if(AL.law == law)
- return
-
- var/new_law = new/datum/ai_law/inherent(law)
- inherent_laws += new_law
- if(length(state_inherent) < length(inherent_laws))
- state_inherent += 1
-
- sorted_laws.Cut()
-
-/datum/ai_laws/proc/add_supplied_law(number, law)
- if(!law)
- return
-
- if(length(supplied_laws) >= number)
- var/datum/ai_law/existing_law = supplied_laws[number]
- if(existing_law && existing_law.law == law)
- return
-
- if(length(supplied_laws) >= number && supplied_laws[number])
- delete_law(supplied_laws[number])
-
- while(length(src.supplied_laws) < number)
- src.supplied_laws += ""
- if(length(state_supplied) < length(supplied_laws))
- state_supplied += 1
-
- var/new_law = new/datum/ai_law/supplied(law, number)
- supplied_laws[number] = new_law
- if(length(state_supplied) < length(supplied_laws))
- state_supplied += 1
-
- sorted_laws.Cut()
-
-/****************
-* Remove Laws *
-*****************/
-/datum/ai_laws/proc/delete_law(datum/ai_law/law)
- if(istype(law))
- 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()
-
-/datum/ai_law/ion/delete_law(datum/ai_laws/laws)
- laws.internal_delete_law(laws.ion_laws, laws.state_ion, src)
-
-/datum/ai_law/inherent/delete_law(datum/ai_laws/laws)
- laws.internal_delete_law(laws.inherent_laws, laws.state_inherent, src)
-
-/datum/ai_law/supplied/delete_law(datum/ai_laws/laws)
- var/index = laws.supplied_laws.Find(src)
- if(index)
- laws.supplied_laws[index] = ""
- laws.state_supplied[index] = 1
-
-/datum/ai_laws/proc/internal_delete_law(list/datum/ai_law/laws, list/state, list/datum/ai_law/law)
- var/index = laws.Find(law)
- if(index)
- laws -= law
- for(index, index < length(state), index++)
- state[index] = state[index+1]
- sorted_laws.Cut()
-
-/****************
-* Clear Laws *
-****************/
-/datum/ai_laws/proc/clear_zeroth_laws()
- zeroth_law = null
- zeroth_law_borg = null
-
-/datum/ai_laws/proc/clear_ion_laws()
- ion_laws.Cut()
- sorted_laws.Cut()
-
-/datum/ai_laws/proc/clear_inherent_laws()
- inherent_laws.Cut()
- sorted_laws.Cut()
-
-/datum/ai_laws/proc/clear_supplied_laws()
- supplied_laws.Cut()
- sorted_laws.Cut()
-
-/datum/ai_laws/proc/show_laws(who)
- sort_laws()
- for(var/datum/ai_law/law in sorted_laws)
- if(law == zeroth_law_borg)
- continue
- if(law == zeroth_law)
- to_chat(who, "[law.get_index()]. [law.law]")
- else
- to_chat(who, "[law.get_index()]. [law.law]")
-
-/datum/ai_laws/proc/return_laws_text()
- . = list()
- sort_laws()
- for(var/datum/ai_law/law in sorted_laws)
- if(law == zeroth_law_borg)
- continue
- if(law == zeroth_law)
- . += "[law.get_index()]. [law.law]"
- else
- . += "[law.get_index()]. [law.law]"
-
-
-/********************
-* Stating Laws *
-********************/
-/********
-* Get *
-********/
-/datum/ai_laws/proc/get_state_law(datum/ai_law/law)
- 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)
- return laws.state_zeroth
-
-/datum/ai_law/ion/get_state_law(datum/ai_laws/laws)
- return laws.get_state_internal(laws.ion_laws, laws.state_ion, src)
-
-/datum/ai_law/inherent/get_state_law(datum/ai_laws/laws)
- return laws.get_state_internal(laws.inherent_laws, laws.state_inherent, src)
-
-/datum/ai_law/supplied/get_state_law(datum/ai_laws/laws)
- return laws.get_state_internal(laws.supplied_laws, laws.state_supplied, src)
-
-/datum/ai_laws/proc/get_state_internal(list/datum/ai_law/laws, list/state, list/datum/ai_law/law)
- var/index = laws.Find(law)
- if(index)
- return state[index]
- return 0
-
-/********
-* Set *
-********/
-/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_laws/laws, state)
- return
-
-/datum/ai_law/zero/set_state_law(datum/ai_laws/laws, state)
- if(src == laws.zeroth_law)
- laws.state_zeroth = state
-
-/datum/ai_law/ion/set_state_law(datum/ai_laws/laws, state)
- laws.set_state_law_internal(laws.ion_laws, laws.state_ion, src, state)
-
-/datum/ai_law/inherent/set_state_law(datum/ai_laws/laws, state)
- laws.set_state_law_internal(laws.inherent_laws, laws.state_inherent, src, state)
-
-/datum/ai_law/supplied/set_state_law(datum/ai_laws/laws, state)
- laws.set_state_law_internal(laws.supplied_laws, laws.state_supplied, src, state)
-
-/datum/ai_laws/proc/set_state_law_internal(list/datum/ai_law/laws, list/state, list/datum/ai_law/law, do_state)
- var/index = laws.Find(law)
- if(index)
- state[index] = do_state
diff --git a/code/datums/atom_hud.dm b/code/datums/atom_hud.dm
deleted file mode 100644
index 5178b181fbafb..0000000000000
--- a/code/datums/atom_hud.dm
+++ /dev/null
@@ -1,118 +0,0 @@
-/* HUD DATUMS */
-GLOBAL_LIST_EMPTY(all_huds)
-
-///GLOBAL HUD LIST
-GLOBAL_LIST_INIT(huds, list(
- DATA_HUD_SECURITY_BASIC = new/datum/atom_hud/data/human/security/basic(),
- DATA_HUD_SECURITY_ADVANCED = new/datum/atom_hud/data/human/security/advanced(),
- DATA_HUD_MEDICAL_BASIC = new/datum/atom_hud/data/human/medical/basic(),
- DATA_HUD_MEDICAL_ADVANCED = new/datum/atom_hud/data/human/medical/advanced(),
- DATA_HUD_DIAGNOSTIC_BASIC = new/datum/atom_hud/data/diagnostic/basic(),
- DATA_HUD_DIAGNOSTIC_ADVANCED = new/datum/atom_hud/data/diagnostic/advanced(),
- DATA_HUD_HYDROPONIC = new/datum/atom_hud/data/hydroponic(),
- DATA_HUD_JANITOR = new/datum/atom_hud/data/janitor(),
- DATA_HUD_PRESSURE = new/datum/atom_hud/data/pressure(),
- ANTAG_HUD_CULT = new/datum/atom_hud/antag(),
- ANTAG_HUD_REV = new/datum/atom_hud/antag(),
- ANTAG_HUD_OPS = new/datum/atom_hud/antag(),
- ANTAG_HUD_WIZ = new/datum/atom_hud/antag(),
- ANTAG_HUD_SHADOW = new/datum/atom_hud/antag(),
- ANTAG_HUD_TRAITOR = new/datum/atom_hud/antag/hidden(),
- ANTAG_HUD_NINJA = new/datum/atom_hud/antag/hidden(),
- ANTAG_HUD_CHANGELING = new/datum/atom_hud/antag/hidden(),
- ANTAG_HUD_VAMPIRE = new/datum/atom_hud/antag/hidden(),
- ANTAG_HUD_ABDUCTOR = new/datum/atom_hud/antag/hidden(),
- DATA_HUD_ABDUCTOR = new/datum/atom_hud/abductor(),
- ANTAG_HUD_EVENTMISC = new/datum/atom_hud/antag/hidden(),
- ANTAG_HUD_BLOB = new/datum/atom_hud/antag/hidden(),
- ANTAG_HUD_ZOMBIE = new/datum/atom_hud/antag(),
- ANTAG_HUD_MIND_FLAYER = new/datum/atom_hud/antag/hidden()
- ))
-
-/datum/atom_hud
- var/list/atom/hudatoms = list() //list of all atoms which display this hud
- var/list/mob/hudusers = list() //list with all mobs who can see the hud
- var/list/hud_icons = list() //these will be the indexes for the atom's hud_list
-
-
-/datum/atom_hud/New()
- GLOB.all_huds += src
-
-/datum/atom_hud/Destroy()
- for(var/v in hudusers)
- remove_hud_from(v)
- for(var/v in hudatoms)
- remove_from_hud(v)
- GLOB.all_huds -= src
- return ..()
-
-/datum/atom_hud/proc/remove_hud_from(mob/M)
- if(!M)
- return
- if(src in M.permanent_huds)
- return
- for(var/atom/A in hudatoms)
- remove_from_single_hud(M, A)
- hudusers -= M
-
-/datum/atom_hud/proc/remove_from_hud(atom/A)
- if(!A)
- return
- for(var/mob/M in hudusers)
- remove_from_single_hud(M, A)
- hudatoms -= A
-
-/datum/atom_hud/proc/remove_from_single_hud(mob/M, atom/A) //unsafe, no sanity apart from client
- if(!M || !M.client || !A)
- return
- for(var/i in hud_icons)
- M.client.images -= A.hud_list[i]
-
-/datum/atom_hud/proc/add_hud_to(mob/M)
- if(!M)
- return
- hudusers |= M
- for(var/atom/A in hudatoms)
- add_to_single_hud(M, A)
-
-/datum/atom_hud/proc/add_to_hud(atom/A)
- if(!A)
- return
- hudatoms |= A
- for(var/mob/M in hudusers)
- add_to_single_hud(M, A)
-
-/datum/atom_hud/proc/add_to_single_hud(mob/M, atom/A) //unsafe, no sanity apart from client
- if(!M || !M.client || !A)
- return
- if(A.invisibility > M.see_invisible) // yee yee ass snowflake check for our yee yee ass snowflake huds
- return
- for(var/i in hud_icons)
- if(A.hud_list[i])
- M.client.images |= A.hud_list[i]
-
-//MOB PROCS
-/mob/proc/reload_huds()
- //var/gang_huds = list()
- //if(ticker.mode)
- // for(var/datum/gang/G in ticker.mode.gangs)
- // gang_huds += G.ganghud
-
- var/serv_huds = list()//mindslaves and/or vampire thralls
- if(SSticker.mode)
- for(var/datum/mindslaves/serv in (SSticker.mode.vampires | SSticker.mode.traitors))
- serv_huds += serv.thrallhud
-
-
- for(var/datum/atom_hud/hud in (GLOB.all_huds|serv_huds))//|gang_huds))
- if(src in hud.hudusers)
- hud.add_hud_to(src)
-
-/mob/new_player/reload_huds()
- return
-
-/mob/proc/add_click_catcher()
- client.screen += client.void
-
-/mob/new_player/add_click_catcher()
- return
diff --git a/code/datums/beam.dm b/code/datums/beam.dm
deleted file mode 100644
index 3f809569965f3..0000000000000
--- a/code/datums/beam.dm
+++ /dev/null
@@ -1,182 +0,0 @@
-//Beam Datum and effect
-/datum/beam
- var/atom/origin = null
- var/atom/target = null
- var/list/elements = list()
- var/icon/base_icon = null
- var/icon
- var/icon_state = "" //icon state of the main segments of the beam
- var/max_distance = 0
- var/endtime = 0
- var/sleep_time = 3
- var/finished = FALSE
- var/target_oldloc = null
- var/origin_oldloc = null
- var/static_beam = FALSE
- var/beam_type = /obj/effect/ebeam //must be subtype
- var/beamcolor
-
-/datum/beam/New(beam_origin, beam_target,beam_icon = 'icons/effects/beam.dmi', beam_icon_state = "b_beam", time = 50, maxdistance = 10, btype = /obj/effect/ebeam, beam_sleep_time = 3, beam_color)
- endtime = world.time+time
- origin = beam_origin
- origin_oldloc = get_turf(origin)
- target = beam_target
- target_oldloc = get_turf(target)
- sleep_time = beam_sleep_time
- if(origin_oldloc == origin && target_oldloc == target)
- static_beam = TRUE
- max_distance = maxdistance
- base_icon = new(beam_icon,beam_icon_state)
- icon = beam_icon
- icon_state = beam_icon_state
- beam_type = btype
- beamcolor = beam_color
-
-/datum/beam/proc/Start()
- Draw()
- while(!finished && origin && target && world.time < endtime && get_dist(origin,target)length)
- var/icon/II = new(icon, icon_state)
- II.DrawBox(null, 1, (length-N), 32, 32)
- X.icon = II
- else
- X.icon = base_icon
- if(beamcolor)
- X.color = beamcolor
- X.transform = rot_matrix
-
- //Calculate pixel offsets (If necessary)
- var/Pixel_x
- var/Pixel_y
- if(DX == 0)
- Pixel_x = 0
- else
- Pixel_x = round(sin(Angle)+32*sin(Angle)*(N+16)/32)
- if(DY == 0)
- Pixel_y = 0
- else
- Pixel_y = round(cos(Angle) + 32 * cos(Angle) * (N + 16) / 32)
-
- //Position the effect so the beam is one continous line
- var/final_x = X.x
- var/final_y = X.y
- if(abs(Pixel_x)>32)
- final_x += Pixel_x > 0 ? round(Pixel_x / 32) : CEILING(Pixel_x / 32, 1)
- Pixel_x %= 32
- if(abs(Pixel_y)>32)
- final_y += Pixel_y > 0 ? round(Pixel_y / 32) : CEILING(Pixel_y / 32, 1)
- Pixel_y %= 32
-
- X.forceMove(locate(final_x, final_y, X.z))
- X.pixel_x = origin.pixel_x + Pixel_x
- X.pixel_y = origin.pixel_y + Pixel_y
-
-/obj/effect/ebeam
- mouse_opacity = MOUSE_OPACITY_TRANSPARENT
- anchored = TRUE
- var/datum/beam/owner
-
-/obj/effect/ebeam/Initialize(mapload)
- . = ..()
- var/static/list/loc_connections = list(
- COMSIG_ATOM_ENTERED = PROC_REF(on_atom_entered),
- )
- AddElement(/datum/element/connect_loc, loc_connections)
-
-/obj/effect/ebeam/proc/on_atom_entered(datum/source, atom/movable/entered)
- SIGNAL_HANDLER // ON_ATOM_ENTERED
- return
-
-/obj/effect/ebeam/ex_act(severity)
- return
-
-/obj/effect/ebeam/Destroy()
- owner = null
- return ..()
-
-/obj/effect/ebeam/singularity_pull()
- return
-
-/obj/effect/ebeam/singularity_act()
- return
-
-/obj/effect/ebeam/deadly/on_atom_entered(datum/source, atom/movable/entered)
- entered.ex_act(EXPLODE_DEVASTATE)
-
-/obj/effect/ebeam/vetus/Destroy()
- for(var/mob/living/M in get_turf(src))
- M.electrocute_act(20, "the giant arc", flags = SHOCK_NOGLOVES) //fuck your gloves.
- return ..()
-
-/obj/effect/ebeam/disintegration_telegraph
- alpha = 100
- layer = ON_EDGED_TURF_LAYER
-
-/obj/effect/ebeam/disintegration
- layer = ON_EDGED_TURF_LAYER
-
-/obj/effect/ebeam/disintegration/on_atom_entered(datum/source, atom/movable/entered)
- if(!isliving(entered))
- return
- var/mob/living/L = entered
- var/damage = 50
- if(L.stat == DEAD)
- visible_message("[L] is disintegrated by the beam!")
- L.dust()
- if(isliving(owner.origin))
- var/mob/living/O = owner.origin
- if(faction_check(O.faction, L.faction, FALSE))
- return
- damage = 70 - ((O.health / O.maxHealth) * 20)
- playsound(L,'sound/weapons/sear.ogg', 50, TRUE, -4)
- to_chat(L, "You're struck by a disintegration laser!")
- var/limb_to_hit = L.get_organ(pick(BODY_ZONE_HEAD, BODY_ZONE_CHEST, BODY_ZONE_R_ARM, BODY_ZONE_L_ARM, BODY_ZONE_R_LEG, BODY_ZONE_L_LEG))
- var/armor = L.run_armor_check(limb_to_hit, LASER)
- L.apply_damage(damage, BURN, limb_to_hit, armor)
-
-/atom/proc/Beam(atom/BeamTarget, icon_state="b_beam", icon='icons/effects/beam.dmi', time = 5 SECONDS, maxdistance = 10, beam_type = /obj/effect/ebeam, beam_sleep_time = 3, beam_color)
- var/datum/beam/newbeam = new(src, BeamTarget, icon, icon_state, time, maxdistance, beam_type, beam_sleep_time, beam_color)
- INVOKE_ASYNC(newbeam, TYPE_PROC_REF(/datum/beam, Start))
- return newbeam
diff --git a/code/datums/card_deck_table_tracker.dm b/code/datums/card_deck_table_tracker.dm
deleted file mode 100644
index d8f0cda1dff3d..0000000000000
--- a/code/datums/card_deck_table_tracker.dm
+++ /dev/null
@@ -1,145 +0,0 @@
-#define COMSIG_CARD_DECK_FIELD_CLEAR "card_deck_field_clear"
-
-/datum/card_deck_table_tracker
- /// How far away you can be (in terms of table squares).
- var/max_table_distance
- /// How far away you can be (euclidean distance).
- var/max_total_distance
- /// The UID of the deck
- var/deck_uid
- /// Indicate field activity with colors on the field's turfs.
- var/debug = FALSE
- /// The deck we're tracking.
- var/atom/host
-
- /// The list of floors from which a player can access the card field.
- var/list/floors = list()
- /// The list of tables that the card deck's location is contiguous with.
- var/list/tables = list()
-
-/datum/card_deck_table_tracker/New(atom/host_, max_table_distance_ = 5)
- max_table_distance = max_table_distance_
- max_total_distance = max_table_distance_
- if(istype(host_, /obj/item/deck))
- // this is important for tracking traits and attacking multiple cards. so it's not a true UID, sue me
- var/obj/item/deck/D = host_
- deck_uid = D.main_deck_id
- else
- deck_uid = host_.UID()
-
- host = host_
- RegisterSignal(host, COMSIG_MOVABLE_MOVED, PROC_REF(on_movable_moved))
- lay_out_field()
-
-/datum/card_deck_table_tracker/proc/on_movable_moved(datum/source, atom/old_loc, direction, forced)
- SIGNAL_HANDLER // COMSIG_MOVABLE_MOVED
- lay_out_field()
-
-/datum/card_deck_table_tracker/proc/lay_out_field()
- for(var/turf/floor in floors)
- SEND_SIGNAL(floor, COMSIG_CARD_DECK_FIELD_CLEAR)
-
- if(!isturf(host.loc))
- return
-
- floors.Cut()
- tables.Cut()
-
- crawl_along(host.loc, 0)
-
- for(var/obj/structure/table in tables)
- if(istype(table))
- RegisterSignal(table, COMSIG_PARENT_QDELETING, PROC_REF(on_table_qdel), override = TRUE)
-
- for(var/turf/turf in floors)
- if(!istype(turf))
- continue
-
- if(debug)
- turf.color = "#ffaaff"
-
- RegisterSignal(turf, COMSIG_ATOM_ENTERED, PROC_REF(on_atom_entered))
- RegisterSignal(turf, COMSIG_ATOM_EXITED, PROC_REF(on_atom_exited))
- RegisterSignal(turf, COMSIG_ATOM_AFTER_SUCCESSFUL_INITIALIZED_ON, PROC_REF(on_new_atom_at_loc))
- RegisterSignal(turf, COMSIG_CARD_DECK_FIELD_CLEAR, PROC_REF(on_card_deck_field_clear))
-
- for(var/mob/living/L in turf)
- on_atom_entered(turf, L)
-
-/datum/card_deck_table_tracker/Destroy(force, ...)
- host = null
- for(var/turf/floor in floors)
- SEND_SIGNAL(floor, COMSIG_CARD_DECK_FIELD_CLEAR)
- for(var/mob/living/L in floor)
- REMOVE_TRAIT(L, TRAIT_PLAYING_CARDS, "deck_[deck_uid]")
-
- floors.Cut()
- tables.Cut()
-
- return ..()
-
-/datum/card_deck_table_tracker/proc/on_atom_entered(turf/source, atom/movable/entered, old_loc)
- SIGNAL_HANDLER // COMSIG_ATOM_ENTERED
-
- var/mob/living/L = entered
- if(istype(L))
- ADD_TRAIT(L, TRAIT_PLAYING_CARDS, "deck_[deck_uid]")
- if(debug)
- source.color = "#ff0000"
-
-/datum/card_deck_table_tracker/proc/on_atom_exited(turf/source, atom/movable/exited, direction)
- SIGNAL_HANDLER // COMSIG_ATOM_EXITED
-
- var/mob/living/L = exited
- if(istype(L))
- REMOVE_TRAIT(L, TRAIT_PLAYING_CARDS, "deck_[deck_uid]")
- if(debug)
- source.color = "#ffaaff"
-
-/datum/card_deck_table_tracker/proc/on_table_qdel(datum/source)
- SIGNAL_HANDLER // COMSIG_PARENT_QDELETING
- lay_out_field()
-
-/datum/card_deck_table_tracker/proc/on_new_atom_at_loc(turf/location, atom/created, init_flags)
- SIGNAL_HANDLER // COMSIG_ATOM_AFTER_SUCCESSFUL_INITIALIZED_ON
- if(istable(created))
- lay_out_field()
-
-/datum/card_deck_table_tracker/proc/on_card_deck_field_clear(atom/target)
- SIGNAL_HANDLER // COMSIG_CARD_DECK_FIELD_CLEAR
- if(debug)
- target.color = initial(target.color)
-
- UnregisterSignal(target, list(
- COMSIG_ATOM_ENTERED,
- COMSIG_ATOM_EXITED,
- COMSIG_CARD_DECK_FIELD_CLEAR,
- COMSIG_ATOM_AFTER_SUCCESSFUL_INITIALIZED_ON,
- ))
-
-/datum/card_deck_table_tracker/proc/crawl_along(turf/current_turf, distance_from_start)
- var/obj/structure/current_table = locate(/obj/structure/table) in current_turf
-
- if(QDELETED(current_table))
- // if there's no table here, we're still adjacent to a table, so this is a spot you could play from
- floors |= current_turf
- return
-
- if(current_table in tables)
- return
-
- tables |= current_table
- floors |= current_turf
-
- if(distance_from_start + 1 > max_table_distance)
- return
-
- for(var/direction in GLOB.alldirs)
- var/turf/next_turf = get_step(current_table, direction)
- if(!istype(next_turf))
- continue
- if(get_dist_euclidian(get_turf(host), next_turf) > max_total_distance)
- continue
- .(next_turf, distance_from_start + 1)
-
-#undef COMSIG_CARD_DECK_FIELD_CLEAR
diff --git a/code/datums/chatmessage.dm b/code/datums/chatmessage.dm
deleted file mode 100644
index 5a40bab91380d..0000000000000
--- a/code/datums/chatmessage.dm
+++ /dev/null
@@ -1,347 +0,0 @@
-/// How long the chat message's spawn-in animation will occur for
-#define CHAT_MESSAGE_SPAWN_TIME (0.2 SECONDS)
-/// How long the chat message will exist prior to any exponential decay
-#define CHAT_MESSAGE_LIFESPAN (5 SECONDS)
-/// How long the chat message's end of life fading animation will occur for
-#define CHAT_MESSAGE_EOL_FADE (0.7 SECONDS)
-/// Grace period for fade before we actually delete the chat message
-#define CHAT_MESSAGE_GRACE_PERIOD (0.2 SECONDS)
-/// Factor of how much the message index (number of messages) will account to exponential decay
-#define CHAT_MESSAGE_EXP_DECAY 0.7
-/// Factor of how much height will account to exponential decay
-#define CHAT_MESSAGE_HEIGHT_DECAY 0.9
-/// Approximate height in pixels of an 'average' line, used for height decay
-#define CHAT_MESSAGE_APPROX_LHEIGHT 11
-/// Max width of chat message in pixels
-#define CHAT_MESSAGE_WIDTH 96
-/// Max length of chat message in characters
-#define CHAT_MESSAGE_MAX_LENGTH 110
-/// Maximum precision of float before rounding errors occur (in this context)
-#define CHAT_LAYER_Z_STEP 0.0001
-/// The number of z-layer 'slices' usable by the chat message layering
-#define CHAT_LAYER_MAX_Z (CHAT_LAYER_MAX - CHAT_LAYER) / CHAT_LAYER_Z_STEP
-/// Macro from Lummox used to get height from a MeasureText proc.
-/// resolves the MeasureText() return value once, then resolves the height, then sets return_var to that.
-#define WXH_TO_HEIGHT(measurement, return_var) \
- do { \
- var/_measurement = measurement; \
- return_var = text2num(copytext(_measurement, findtextEx(_measurement, "x") + 1)); \
- } while(FALSE);
-
-/**
- * # Chat Message Overlay
- *
- * Datum for generating a message overlay on the map
- */
-/datum/chatmessage
- /// The visual element of the chat messsage
- var/image/message
- /// The location in which the message is appearing
- var/atom/message_loc
- /// The client who heard this message
- var/client/owned_by
- /// Contains the approximate amount of lines for height decay
- var/approx_lines
- /// The current index used for adjusting the layer of each sequential chat message such that recent messages will overlay older ones
- var/static/current_z_idx = 0
- /// When we started animating the message
- var/animate_start = 0
- /// Our animation lifespan, how long this message will last
- var/animate_lifespan = 0
-
-/**
- * Constructs a chat message overlay
- *
- * Arguments:
- * * text - The text content of the overlay
- * * target - The target atom to display the overlay at
- * * owner - The mob that owns this overlay, only this mob will be able to view it
- * * italics - Should we use italics or not
- * * lifespan - The lifespan of the message in deciseconds
- * * symbol - The symbol type of the message
- */
-/datum/chatmessage/New(text, atom/target, mob/owner, italics, size, lifespan = CHAT_MESSAGE_LIFESPAN, symbol)
- . = ..()
- if(!istype(target))
- CRASH("Invalid target given for chatmessage")
- if(QDELETED(owner) || !istype(owner) || !owner.client)
- stack_trace("/datum/chatmessage created with [isnull(owner) ? "null" : "invalid"] mob owner")
- qdel(src)
- return
- INVOKE_ASYNC(src, PROC_REF(generate_image), text, target, owner, lifespan, italics, size, symbol)
-
-/datum/chatmessage/Destroy()
- if(owned_by)
- if(owned_by.seen_messages)
- LAZYREMOVEASSOC(owned_by.seen_messages, message_loc, src)
- owned_by.images.Remove(message)
- owned_by = null
- message_loc = null
- message = null
- return ..()
-
-/**
- * Calls qdel on the chatmessage when its parent is deleted, used to register qdel signal
- */
-/datum/chatmessage/proc/on_parent_qdel()
- qdel(src)
-
-/**
- * Generates a chat message image representation
- *
- * Arguments:
- * * text - The text content of the overlay
- * * target - The target atom to display the overlay at
- * * owner - The mob that owns this overlay, only this mob will be able to view it
- * * radio_speech - Fancy shmancy radio icon represents that we use radio
- * * lifespan - The lifespan of the message in deciseconds
- * * italics - Just copy and paste, sir
- * * size - Size of the message
- * * symbol - The symbol type of the message
- */
-/datum/chatmessage/proc/generate_image(text, atom/target, mob/owner, lifespan, italics, size, symbol)
- // Register client who owns this message
- owned_by = owner.client
- RegisterSignal(owned_by, COMSIG_PARENT_QDELETING, PROC_REF(on_parent_qdel))
-
- // Clip message
- var/maxlen = CHAT_MESSAGE_MAX_LENGTH
- var/datum/html_split_holder/s = split_html(text)
- if(length_char(s.inner_text) > maxlen)
- var/chattext = copytext_char(s.inner_text, 1, maxlen + 1) + "..."
- text = jointext(s.opening, "") + chattext + jointext(s.closing, "")
-
- // Calculate target color if not already present
- if(!target.chat_color || target.chat_color_name != target.name)
- target.chat_color = colorize_string(target.name)
- target.chat_color_name = target.name
-
- // Get rid of any URL schemes that might cause BYOND to automatically wrap something in an anchor tag
- var/static/regex/url_scheme = new(@"[A-Za-z][A-Za-z0-9+-\.]*:\/\/", "g")
- text = replacetext(text, url_scheme, "")
-
- // Reject whitespace
- var/static/regex/whitespace = new(@"^\s*$")
- if(whitespace.Find(text))
- qdel(src)
- return
-
- var/output_color = sanitize_color(target.get_runechat_color()) // Get_runechat_color can be overriden on atoms to display a specific one (Example: Humans having their hair colour as runechat colour)
-
- // Symbol for special runechats (emote)
- switch(symbol)
- if(RUNECHAT_SYMBOL_EMOTE)
- symbol = "* "
- size ||= "small"
- if(RUNECHAT_SYMBOL_LOOC)
- symbol = "\[LOOC] "
- size ||= "small"
- output_color = "gray"
- if(RUNECHAT_SYMBOL_DEAD)
- symbol = null
- output_color = "#b826b3"
- else
- symbol = null
-
- // Approximate text height
- var/static/regex/html_metachars = new(@"&[A-Za-z]{1,7};", "g")
- var/complete_text = "[symbol][text]"
- var/mheight
- WXH_TO_HEIGHT(owned_by.MeasureText(complete_text, null, CHAT_MESSAGE_WIDTH), mheight)
-
- if(!VERB_SHOULD_YIELD)
- return finish_image_generation(mheight, target, owner, complete_text, lifespan)
-
- var/datum/callback/our_callback = CALLBACK(src, PROC_REF(finish_image_generation), mheight, target, owner, complete_text, lifespan)
- SSrunechat.message_queue += our_callback
- return
-
-///finishes the image generation after the MeasureText() call in generate_image().
-///necessary because after that call the proc can resume at the end of the tick and cause overtime.
-/datum/chatmessage/proc/finish_image_generation(mheight, atom/target, mob/owner, complete_text, lifespan)
- var/rough_time = REALTIMEOFDAY
- approx_lines = max(1, mheight / CHAT_MESSAGE_APPROX_LHEIGHT)
-
- // Translate any existing messages upwards, apply exponential decay factors to timers
- message_loc = target
- if(owned_by.seen_messages)
- var/idx = 1
- var/combined_height = approx_lines
- for(var/datum/chatmessage/m as anything in owned_by.seen_messages[message_loc])
- combined_height += m.approx_lines
-
- var/time_alive = rough_time - m.animate_start
- var/lifespan_until_fade = m.animate_lifespan - CHAT_MESSAGE_EOL_FADE
-
- if(time_alive >= lifespan_until_fade) // If already fading out or dead, just shift upwards
- animate(m.message, pixel_y = m.message.pixel_y + mheight, time = CHAT_MESSAGE_SPAWN_TIME, flags = ANIMATION_PARALLEL)
- continue
-
- // Ensure we don't accidentially spike alpha up or something silly like that
- m.message.alpha = m.get_current_alpha(time_alive)
-
- var/adjusted_lifespan_until_fade = lifespan_until_fade * (CHAT_MESSAGE_EXP_DECAY ** idx++) * (CHAT_MESSAGE_HEIGHT_DECAY ** combined_height)
- m.animate_lifespan = adjusted_lifespan_until_fade + CHAT_MESSAGE_EOL_FADE
-
- var/remaining_lifespan_until_fade = adjusted_lifespan_until_fade - time_alive
- if(remaining_lifespan_until_fade > 0) // Still got some lifetime to go; stay faded in for the remainder, then fade out
- animate(m.message, alpha = 255, time = remaining_lifespan_until_fade)
- animate(alpha = 0, time = CHAT_MESSAGE_EOL_FADE)
- else // Current time alive is beyond new adjusted lifespan, your time has come my son
- animate(m.message, alpha = 0, time = CHAT_MESSAGE_EOL_FADE)
-
- // We run this after the alpha animate, because we don't want to interrup it, but also don't want to block it by running first
- // Sooo instead we do this. bit messy but it fuckin works
- animate(m.message, pixel_y = m.message.pixel_y + mheight, time = CHAT_MESSAGE_SPAWN_TIME, flags = ANIMATION_PARALLEL)
-
- // Reset z index if relevant
- if(current_z_idx >= CHAT_LAYER_MAX_Z)
- current_z_idx = 0
-
- // Build message image
- message = image(loc = message_loc, layer = CHAT_LAYER + CHAT_LAYER_Z_STEP * current_z_idx++)
- message.plane = GAME_PLANE
- message.appearance_flags = APPEARANCE_UI_IGNORE_ALPHA | KEEP_APART
- message.alpha = 0
- message.pixel_y = owner.bound_height * 0.95
- message.maptext_width = CHAT_MESSAGE_WIDTH
- message.maptext_height = mheight
- message.maptext_x = (CHAT_MESSAGE_WIDTH - owner.bound_width) * -0.5
- message.maptext = complete_text
-
- animate_start = rough_time
- animate_lifespan = lifespan
-
- // View the message
- LAZYADDASSOCLIST(owned_by.seen_messages, message_loc, src)
- owned_by.images |= message
-
- // Fade in
- animate(message, alpha = 255, time = CHAT_MESSAGE_SPAWN_TIME)
- // Stay faded in
- animate(alpha = 255, time = lifespan - CHAT_MESSAGE_SPAWN_TIME - CHAT_MESSAGE_EOL_FADE)
- // Fade out
- animate(alpha = 0, time = CHAT_MESSAGE_EOL_FADE)
-
- // Register with the runechat SS to handle destruction
- addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(qdel), src), lifespan + CHAT_MESSAGE_GRACE_PERIOD, TIMER_DELETE_ME, SSrunechat)
-
-/// Returns the current alpha of the message based on the time spent
-/datum/chatmessage/proc/get_current_alpha(time_alive)
- if(time_alive < CHAT_MESSAGE_SPAWN_TIME)
- return (time_alive / CHAT_MESSAGE_SPAWN_TIME) * 255
-
- var/lifespan_until_fade = animate_lifespan - CHAT_MESSAGE_EOL_FADE
- if(time_alive <= lifespan_until_fade)
- return 255
-
- return (1 - ((time_alive - lifespan_until_fade) / CHAT_MESSAGE_EOL_FADE)) * 255
-
-/**
- * Creates a message overlay at a defined location for a given speaker
- *
- * Arguments:
- * * speaker - The atom who is saying this message
- * * raw_message - The text content of the message
- * * italics - Vacuum and other things
- * * size - Size of the message
- * * symbol - The symbol type of the message
- */
-/mob/proc/create_chat_message(atom/movable/speaker, raw_message, italics = FALSE, size, symbol)
- // Display visual above source
- new /datum/chatmessage(raw_message, speaker, src, italics, size, CHAT_MESSAGE_LIFESPAN, symbol)
-
-
-// Tweak these defines to change the available color ranges
-#define CM_COLOR_SAT_MIN 0.6
-#define CM_COLOR_SAT_MAX 0.7
-#define CM_COLOR_LUM_MIN 0.65
-#define CM_COLOR_LUM_MAX 0.75
-
-/**
- * Gets a color for a name, will return the same color for a given string consistently within a round.atom
- *
- * Note that this proc aims to produce pastel-ish colors using the HSL colorspace. These seem to be favorable for displaying on the map.
- *
- * Arguments:
- * * name - The name to generate a color for
- * * sat_shift - A value between 0 and 1 that will be multiplied against the saturation
- * * lum_shift - A value between 0 and 1 that will be multiplied against the luminescence
- */
-/datum/chatmessage/proc/colorize_string(name, sat_shift = 1, lum_shift = 1)
- // seed to help randomness
- var/static/rseed = rand(1,26)
-
- // get hsl using the selected 6 characters of the md5 hash
- var/hash = copytext(md5(name + station_name()), rseed, rseed + 6)
- var/h = hex2num(copytext(hash, 1, 3)) * (360 / 255)
- var/s = (hex2num(copytext(hash, 3, 5)) >> 2) * ((CM_COLOR_SAT_MAX - CM_COLOR_SAT_MIN) / 63) + CM_COLOR_SAT_MIN
- var/l = (hex2num(copytext(hash, 5, 7)) >> 2) * ((CM_COLOR_LUM_MAX - CM_COLOR_LUM_MIN) / 63) + CM_COLOR_LUM_MIN
-
- // adjust for shifts
- s *= clamp(sat_shift, 0, 1)
- l *= clamp(lum_shift, 0, 1)
-
- // convert to rgb
- var/h_int = round(h/60) // mapping each section of H to 60 degree sections
- var/c = (1 - abs(2 * l - 1)) * s
- var/x = c * (1 - abs((h / 60) % 2 - 1))
- var/m = l - c * 0.5
- x = (x + m) * 255
- c = (c + m) * 255
- m *= 255
- switch(h_int)
- if(0)
- return "#[num2hex(c, 2)][num2hex(x, 2)][num2hex(m, 2)]"
- if(1)
- return "#[num2hex(x, 2)][num2hex(c, 2)][num2hex(m, 2)]"
- if(2)
- return "#[num2hex(m, 2)][num2hex(c, 2)][num2hex(x, 2)]"
- if(3)
- return "#[num2hex(m, 2)][num2hex(x, 2)][num2hex(c, 2)]"
- if(4)
- return "#[num2hex(x, 2)][num2hex(m, 2)][num2hex(c, 2)]"
- if(5)
- return "#[num2hex(c, 2)][num2hex(m, 2)][num2hex(x, 2)]"
-
-
-/**
- * Ensures a colour is bright enough for the system
- *
- * This proc is used to brighten parts of a colour up if its too dark, and looks bad
- *
- * Arguments:
- * * hex - Hex colour to be brightened up
- */
-/datum/chatmessage/proc/sanitize_color(color)
- var/list/HSL = rgb2hsl(hex2num(copytext(color,2,4)),hex2num(copytext(color,4,6)),hex2num(copytext(color,6,8)))
- HSL[3] = max(HSL[3],0.4)
- var/list/RGB = hsl2rgb(arglist(HSL))
- return "#[num2hex(RGB[1],2)][num2hex(RGB[2],2)][num2hex(RGB[3],2)]"
-
-/**
- * Proc to allow atoms to set their own runechat colour
- *
- * This is a proc designed to be overridden in places if you want a specific atom to use a specific runechat colour
- * Exampls include consoles using a colour based on their screen colour, and mobs using a colour based off of a customisation property
- *
- */
-/atom/proc/get_runechat_color()
- return chat_color
-
-#undef CHAT_MESSAGE_SPAWN_TIME
-#undef CHAT_MESSAGE_LIFESPAN
-#undef CHAT_MESSAGE_EOL_FADE
-#undef CHAT_MESSAGE_GRACE_PERIOD
-#undef CHAT_MESSAGE_EXP_DECAY
-#undef CHAT_MESSAGE_HEIGHT_DECAY
-#undef CHAT_MESSAGE_APPROX_LHEIGHT
-#undef CHAT_MESSAGE_WIDTH
-#undef CHAT_MESSAGE_MAX_LENGTH
-#undef CHAT_LAYER_Z_STEP
-#undef CHAT_LAYER_MAX_Z
-#undef WXH_TO_HEIGHT
-#undef CM_COLOR_SAT_MIN
-#undef CM_COLOR_SAT_MAX
-#undef CM_COLOR_LUM_MIN
-#undef CM_COLOR_LUM_MAX
diff --git a/code/datums/components/_component.dm b/code/datums/components/_component.dm
deleted file mode 100644
index 42efe26125106..0000000000000
--- a/code/datums/components/_component.dm
+++ /dev/null
@@ -1,566 +0,0 @@
-/**
- * # Component
- *
- * The component datum
- *
- * A component should be a single standalone unit
- * of functionality, that works by receiving signals from it's parent
- * object to provide some single functionality (i.e a slippery component)
- * that makes the object it's attached to cause people to slip over.
- * Useful when you want shared behaviour independent of type inheritance
- */
-/datum/component
- /**
- * Defines how duplicate existing components are handled when added to a datum
- *
- * See [COMPONENT_DUPE_*][COMPONENT_DUPE_ALLOWED] definitions for available options
- */
- var/dupe_mode = COMPONENT_DUPE_HIGHLANDER
-
- /**
- * The type to check for duplication
- *
- * `null` means exact match on `type` (default)
- *
- * Any other type means that and all subtypes
- */
- var/dupe_type
-
- /// The datum this components belongs to
- var/datum/parent
-
- /**
- * Only set to true if you are able to properly transfer this component
- *
- * At a minimum [RegisterWithParent][/datum/component/proc/RegisterWithParent] and [UnregisterFromParent][/datum/component/proc/UnregisterFromParent] should be used
- *
- * Make sure you also implement [PostTransfer][/datum/component/proc/PostTransfer] for any post transfer handling
- */
- var/can_transfer = FALSE
-
-/**
- * Create a new component.
- *
- * Additional arguments are passed to [Initialize()][/datum/component/proc/Initialize]
- *
- * Arguments:
- * * datum/P the parent datum this component reacts to signals from
- */
-/datum/component/New(list/raw_args)
- parent = raw_args[1]
- var/list/arguments = raw_args.Copy(2)
- if(Initialize(arglist(arguments)) == COMPONENT_INCOMPATIBLE)
- stack_trace("Incompatible [type] assigned to a [parent.type]! args: [json_encode(arguments)]")
- qdel(src, TRUE, TRUE)
- return
-
- _JoinParent(parent)
-
-/**
- * Called during component creation with the same arguments as in new excluding parent.
- *
- * Do not call `qdel(src)` from this function, `return COMPONENT_INCOMPATIBLE` instead
- */
-/datum/component/proc/Initialize(...)
- return
-
-/**
- * Properly removes the component from `parent` and cleans up references
- *
- * Arguments:
- * * force - makes it not check for and remove the component from the parent
- * * silent - deletes the component without sending a [COMSIG_COMPONENT_REMOVING] signal
- */
-/datum/component/Destroy(force=FALSE, silent=FALSE)
- if(!force && parent)
- _RemoveFromParent()
- if(!silent)
- SEND_SIGNAL(parent, COMSIG_COMPONENT_REMOVING, src)
- parent = null
- return ..()
-
-/**
- * Internal proc to handle behaviour of components when joining a parent
- */
-/datum/component/proc/_JoinParent()
- var/datum/P = parent
- //lazy init the parent's dc list
- var/list/dc = P.datum_components
- if(!dc)
- P.datum_components = dc = list()
-
- //set up the typecache
- var/our_type = type
- for(var/I in _GetInverseTypeList(our_type))
- var/test = dc[I]
- if(test) //already another component of this type here
- var/list/components_of_type
- if(!length(test))
- components_of_type = list(test)
- dc[I] = components_of_type
- else
- components_of_type = test
- if(I == our_type) //exact match, take priority
- var/inserted = FALSE
- for(var/J in 1 to length(components_of_type))
- var/datum/component/C = components_of_type[J]
- if(C.type != our_type) //but not over other exact matches
- components_of_type.Insert(J, I)
- inserted = TRUE
- break
- if(!inserted)
- components_of_type += src
- else //indirect match, back of the line with ya
- components_of_type += src
- else //only component of this type, no list
- dc[I] = src
-
- RegisterWithParent()
-
-/**
- * Internal proc to handle behaviour when being removed from a parent
- */
-/datum/component/proc/_RemoveFromParent()
- var/datum/P = parent
- var/list/dc = P.datum_components
- for(var/I in _GetInverseTypeList())
- var/list/components_of_type = dc[I]
- if(length(components_of_type)) //
- var/list/subtracted = components_of_type - src
- if(length(subtracted) == 1) //only 1 guy left
- dc[I] = subtracted[1] //make him special
- else
- dc[I] = subtracted
- else //just us
- dc -= I
- if(!length(dc))
- P.datum_components = null
-
- UnregisterFromParent()
-
-/**
- * Register the component with the parent object
- *
- * Use this proc to register with your parent object
- *
- * Overridable proc that's called when added to a new parent
- */
-/datum/component/proc/RegisterWithParent()
- return
-
-/**
- * Unregister from our parent object
- *
- * Use this proc to unregister from your parent object
- *
- * Overridable proc that's called when removed from a parent
- * *
- */
-/datum/component/proc/UnregisterFromParent()
- return
-
-/**
- * Register to listen for a signal from the passed in target
- *
- * This sets up a listening relationship such that when the target object emits a signal
- * the source datum this proc is called upon, will receive a callback to the given proctype
- * Return values from procs registered must be a bitfield
- *
- * Arguments:
- * * datum/target The target to listen for signals from
- * * sig_type_or_types Either a string signal name, or a list of signal names (strings)
- * * proctype The proc to call back when the signal is emitted
- * * override If a previous registration exists you must explicitly set this
- */
-/datum/proc/RegisterSignal(datum/target, sig_type_or_types, proctype, override = FALSE)
- if(QDELETED(src) || QDELETED(target))
- return
-
- var/list/procs = (signal_procs ||= list()) // YES, THIS NEEDS TO BE HERE. DO NOT REPLACE `procs` with `signal_procs` or signals will have a FIT
- var/list/target_procs = (procs[target] ||= list()) // makes ...[target] a list if its null, then holds a reference to it in target_procs
- var/list/lookup = (target.comp_lookup ||= list())
-
- var/list/sig_types = islist(sig_type_or_types) ? sig_type_or_types : list(sig_type_or_types)
- for(var/sig_type in sig_types)
- if(!override && target_procs[sig_type])
- stack_trace("RegisterSignal overrode a signal without having 'override = TRUE' set.\n \
- src: [src], signal type: [sig_type], target: [target], new proc: [proctype], previous proc: [target_procs[sig_type]].")
-
- target_procs[sig_type] = proctype
- var/list/looked_up = lookup[sig_type]
-
- if(!looked_up) // Nothing has registered here yet
- looked_up = src
- else if(looked_up == src) // We already registered here
- continue
- else if(!length(looked_up)) // One other thing registered here
- looked_up = list((looked_up) = TRUE, (src) = TRUE)
- else // Many other things have registered here
- looked_up[src] = TRUE
-
- lookup[sig_type] = looked_up // Gotta save, otherwise it will break
-
-/**
- * Stop listening to a given signal from target
- *
- * Breaks the relationship between target and source datum, removing the callback when the signal fires
- *
- * Doesn't care if a registration exists or not
- *
- * Arguments:
- * * datum/target Datum to stop listening to signals from
- * * sig_typeor_types Signal string key or list of signal keys to stop listening to specifically
- */
-/datum/proc/UnregisterSignal(datum/target, sig_type_or_types)
- var/list/lookup = target.comp_lookup
- if(!signal_procs || !signal_procs[target] || !lookup)
- return
- if(!islist(sig_type_or_types))
- sig_type_or_types = list(sig_type_or_types)
- for(var/sig in sig_type_or_types)
- if(!signal_procs[target][sig])
- continue
- switch(length(lookup[sig]))
- if(2)
- lookup[sig] = (lookup[sig]-src)[1]
- if(1)
- stack_trace("[target] ([target.type]) somehow has single length list inside comp_lookup")
- if(src in lookup[sig])
- lookup -= sig
- if(!length(lookup))
- target.comp_lookup = null
- break
- if(0)
- if(lookup[sig] != src)
- continue
- lookup -= sig
- if(!length(lookup))
- target.comp_lookup = null
- break
- else
- lookup[sig] -= src
-
- signal_procs[target] -= sig_type_or_types
- if(!length(signal_procs[target]))
- signal_procs -= target
-
-/// Registers multiple signals to the same proc.
-/datum/proc/RegisterSignals(datum/target, list/signal_types, proctype, override = FALSE)
- for(var/signal_type in signal_types)
- RegisterSignal(target, signal_type, proctype, override)
-
-/**
- * Called on a component when a component of the same type was added to the same parent
- *
- * See [/datum/component/var/dupe_mode]
- *
- * `C`'s type will always be the same of the called component
- */
-/datum/component/proc/InheritComponent(datum/component/C, i_am_original)
- return
-
-/**
- * Callback Just before this component is transferred
- *
- * Use this to do any special cleanup you might need to do before being deregged from an object
- */
-/datum/component/proc/PreTransfer()
- return
-
-/**
- * Callback Just after a component is transferred
- *
- * Use this to do any special setup you need to do after being moved to a new object
- *
- * Do not call `qdel(src)` from this function, `return COMPONENT_INCOMPATIBLE` instead
- */
-/datum/component/proc/PostTransfer()
- return COMPONENT_INCOMPATIBLE //Do not support transfer by default as you must properly support it
-
-/**
- * Internal proc to create a list of our type and all parent types
- */
-/datum/component/proc/_GetInverseTypeList(our_type = type)
- //we can do this one simple trick
- var/current_type = parent_type
- . = list(our_type, current_type)
- //and since most components are root level + 1, this won't even have to run
- while(current_type != /datum/component)
- current_type = type2parent(current_type)
- . += current_type
-
-/**
- * Internal proc to handle most all of the signaling procedure
- *
- * Will runtime if used on datums with an empty component list
- *
- * Use the [SEND_SIGNAL] define instead
- */
-/datum/proc/_SendSignal(sigtype, list/arguments)
- var/target = comp_lookup[sigtype]
- if(!length(target))
- var/datum/listening_datum = target
- return NONE | CallAsync(listening_datum, listening_datum.signal_procs[src][sigtype], arguments)
-
- . = NONE
- // This exists so that even if one of the signal receivers unregisters the signal,
- // all the objects that are receiving the signal get the signal this final time.
- // AKA: No you can't cancel the signal reception of another object by doing an unregister in the same signal.
- var/list/queued_calls = list()
- for(var/datum/targets as anything in target)
- queued_calls[targets] = targets.signal_procs[src][sigtype]
- for(var/datum/listening_datum as anything in queued_calls)
- . |= CallAsync(listening_datum, queued_calls[listening_datum], arguments)
-
-// The type arg is casted so initial works, you shouldn't be passing a real instance into this
-/**
- * Return any component assigned to this datum of the given type
- *
- * This will throw an error if it's possible to have more than one component of that type on the parent
- *
- * Arguments:
- * * datum/component/c_type The typepath of the component you want to get a reference to
- */
-/datum/proc/GetComponent(datum/component/c_type)
- RETURN_TYPE(c_type)
- if(initial(c_type.dupe_mode) == COMPONENT_DUPE_ALLOWED || initial(c_type.dupe_mode) == COMPONENT_DUPE_SELECTIVE)
- stack_trace("GetComponent was called to get a component of which multiple copies could be on an object. This can easily break and should be changed. Type: \[[c_type]\]")
- var/list/dc = datum_components
- if(!dc)
- return null
- . = dc[c_type]
- if(length(.))
- return .[1]
-
-// The type arg is casted so initial works, you shouldn't be passing a real instance into this
-/**
- * Return any component assigned to this datum of the exact given type
- *
- * This will throw an error if it's possible to have more than one component of that type on the parent
- *
- * Arguments:
- * * datum/component/c_type The typepath of the component you want to get a reference to
- */
-/datum/proc/GetExactComponent(datum/component/c_type)
- RETURN_TYPE(c_type)
- var/initial_type_mode = initial(c_type.dupe_mode)
- if(initial_type_mode == COMPONENT_DUPE_ALLOWED || initial_type_mode == COMPONENT_DUPE_SELECTIVE)
- stack_trace("GetComponent was called to get a component of which multiple copies could be on an object. This can easily break and should be changed. Type: \[[c_type]\]")
- var/list/all_components = datum_components
- if(!all_components)
- return null
- var/datum/component/potential_component
- if(length(all_components))
- potential_component = all_components[c_type]
- if(potential_component?.type == c_type)
- return potential_component
- return null
-
-/**
- * Get all components of a given type that are attached to this datum
- *
- * Arguments:
- * * c_type The component type path
- */
-/datum/proc/GetComponents(c_type)
- var/list/dc = datum_components
- if(!dc)
- return null
- . = dc[c_type]
- if(!length(.))
- return list(.)
-
-/**
- * Creates an instance of `new_type` in the datum and attaches to it as parent
- *
- * Sends the [COMSIG_COMPONENT_ADDED] signal to the datum
- *
- * Returns the component that was created. Or the old component in a dupe situation where [COMPONENT_DUPE_UNIQUE] was set
- *
- * If this tries to add a component to an incompatible type, the component will be deleted and the result will be `null`. This is very unperformant, try not to do it
- *
- * Properly handles duplicate situations based on the `dupe_mode` var
- */
-/datum/proc/_AddComponent(list/raw_args)
- var/new_type = raw_args[1]
- var/datum/component/nt = new_type
- var/dm = initial(nt.dupe_mode)
- var/dt = initial(nt.dupe_type)
-
- var/datum/component/old_comp
- var/datum/component/new_comp
-
- if(ispath(nt))
- if(nt == /datum/component)
- CRASH("[nt] attempted instantiation!")
- else
- new_comp = nt
- nt = new_comp.type
-
- raw_args[1] = src
-
- if(dm != COMPONENT_DUPE_ALLOWED)
- if(!dt)
- old_comp = GetExactComponent(nt)
- else
- old_comp = GetComponent(dt)
- if(old_comp)
- switch(dm)
- if(COMPONENT_DUPE_UNIQUE)
- if(!new_comp)
- new_comp = new nt(raw_args)
- if(!QDELETED(new_comp))
- old_comp.InheritComponent(new_comp, TRUE)
- QDEL_NULL(new_comp)
- if(COMPONENT_DUPE_HIGHLANDER)
- if(!new_comp)
- new_comp = new nt(raw_args)
- if(!QDELETED(new_comp))
- new_comp.InheritComponent(old_comp, FALSE)
- QDEL_NULL(old_comp)
- if(COMPONENT_DUPE_UNIQUE_PASSARGS)
- if(!new_comp)
- var/list/arguments = raw_args.Copy(2)
- arguments.Insert(1, null, TRUE)
- old_comp.InheritComponent(arglist(arguments))
- else
- old_comp.InheritComponent(new_comp, TRUE)
- if(COMPONENT_DUPE_SELECTIVE)
- var/list/arguments = raw_args.Copy()
- arguments[1] = new_comp
- var/make_new_component = TRUE
- if(!new_comp && make_new_component)
- new_comp = new nt(raw_args)
- else if(!new_comp)
- new_comp = new nt(raw_args) // There's a valid dupe mode but there's no old component, act like normal
- else if(!new_comp)
- new_comp = new nt(raw_args) // Dupes are allowed, act like normal
-
- if(!old_comp && !QDELETED(new_comp)) // Nothing related to duplicate components happened and the new component is healthy
- SEND_SIGNAL(src, COMSIG_COMPONENT_ADDED, new_comp)
- return new_comp
- return old_comp
-
-/**
- * Removes the component from the datum
- */
-/datum/proc/DeleteComponent(component_to_nuke)
- var/datum/component/removing = GetComponent(component_to_nuke)
- if(istype(removing, component_to_nuke) && !QDELETED(removing))
- qdel(removing)
-
-/**
- * Removes all components of a given type from the datum
- */
-/datum/proc/DeleteComponentsType(component_type_to_nuke)
- var/list/components = GetComponents(component_type_to_nuke)
- for(var/datum/component/removing in components)
- if(!QDELETED(removing))
- qdel(removing)
-
-/**
- * Get existing component of type, or create it and return a reference to it
- *
- * Use this if the item needs to exist at the time of this call, but may not have been created before now
- *
- * Arguments:
- * * component_type The typepath of the component to create or return
- * * ... additional arguments to be passed when creating the component if it does not exist
- */
-/datum/proc/LoadComponent(component_type, ...)
- . = GetComponent(component_type)
- if(!.)
- return _AddComponent(args)
-
-/**
- * Removes the component from parent, ends up with a null parent
- */
-/datum/component/proc/UnlinkComponent()
- if(!parent)
- return
- var/datum/old_parent = parent
- PreTransfer()
- _RemoveFromParent()
- parent = null
- SEND_SIGNAL(old_parent, COMSIG_COMPONENT_REMOVING, src)
-
-/**
- * Deletes the component and removes it from parent.
- */
-/datum/component/proc/RemoveComponent() // This really is just a wrapper to pretend that we're using sane procs to fully remove a component
- if(!QDELETED(src))
- qdel(src)
-
-/**
- * Transfer this component to another parent
- *
- * Component is taken from source datum
- *
- * Arguments:
- * * datum/component/target Target datum to transfer to
- */
-/datum/proc/TakeComponent(datum/component/target)
- if(!target || target.parent == src)
- return
- if(target.parent)
- target.UnlinkComponent()
- target.parent = src
- var/result = target.PostTransfer()
- switch(result)
- if(COMPONENT_INCOMPATIBLE)
- var/c_type = target.type
- qdel(target)
- CRASH("Incompatible [c_type] transfer attempt to a [type]!")
-
- if(target == AddComponent(target))
- target._JoinParent()
-
-/**
- * Transfer all components to target
- *
- * All components from source datum are taken
- *
- * Arguments:
- * * /datum/target the target to move the components to
- */
-/datum/proc/TransferComponents(datum/target)
- var/list/dc = datum_components
- if(!dc)
- return
- var/comps = dc[/datum/component]
- if(islist(comps))
- for(var/datum/component/I in comps)
- if(I.can_transfer)
- target.TakeComponent(I)
- else
- var/datum/component/C = comps
- if(C.can_transfer)
- target.TakeComponent(comps)
-
-/**
- * Transfer a single component from the source datum, to the target.
- *
- * Arguments:
- * * datum/target - the target to move the component to
- * * component_instance_or_typepath - either an already created component, or a component typepath
- */
-/datum/proc/TransferComponent(datum/target, component_instance_or_typepath)
- if(!datum_components)
- return
- // If the proc was fed a typepath.
- var/datum/component/comp = datum_components[component_instance_or_typepath]
- if(comp?.can_transfer)
- target.TakeComponent(comp)
- return
- // if the proc was fed a component instance.
- for(var/component in datum_components)
- var/datum/component/C = datum_components[component]
- if(istype(C, component_instance_or_typepath) && C.can_transfer)
- target.TakeComponent(C)
- return
-
-/**
- * Return the object that is the host of any UI's that this component has
- */
-/datum/component/ui_host(mob/user)
- return parent
diff --git a/code/datums/components/anti_magic.dm b/code/datums/components/anti_magic.dm
deleted file mode 100644
index 645e579a70917..0000000000000
--- a/code/datums/components/anti_magic.dm
+++ /dev/null
@@ -1,166 +0,0 @@
-/// This provides different types of magic resistance on an object
-/datum/component/anti_magic
- /// A bitflag with the types of magic resistance on the object
- var/antimagic_flags
- /// The amount of times the object can protect the user from magic
- /// Set to INFINITY to have, well, infinite charges.
- var/charges
- /// The inventory slot the object must be located at in order to activate
- var/inventory_flags
- /// The callback invoked when we have been drained a antimagic charge
- var/datum/callback/drain_antimagic
- /// The callback invoked when twe have been depleted of all charges
- var/datum/callback/expiration
- /// Whether we should, on equipping, alert the caster that this item can block any of their spells
- /// This changes between true and false on equip and drop, don't set it outright to something
- var/alert_caster_on_equip = TRUE
-
-/**
- * Adds magic resistances to an object
- *
- * Magic resistance will prevent magic from affecting the user if it has the correct resistance
- * against the type of magic being used
- *
- * args:
- * * antimagic_flags (optional) A bitflag with the types of magic resistance on the object
- * * charges (optional) The amount of times the object can protect the user from magic
- * * inventory_flags (optional) The inventory slot the object must be located at in order to activate
- * * drain_antimagic (optional) The proc that is triggered when an object has been drained a antimagic charge
- * * expiration (optional) The proc that is triggered when the object is depleted of charges
- * *
- * antimagic bitflags: (see code/__DEFINES/magic.dm)
- * * MAGIC_RESISTANCE - Default magic resistance that blocks normal magic (wizard, spells, staffs)
- * * MAGIC_RESISTANCE_MIND - Tinfoil hat magic resistance that blocks mental magic (telepathy, abductors, jelly people)
- * * MAGIC_RESISTANCE_HOLY - Holy magic resistance that blocks unholy magic (revenant, cult, vampire, voice of god)
-**/
-/datum/component/anti_magic/Initialize(
- antimagic_flags = MAGIC_RESISTANCE,
- charges = INFINITY,
- inventory_flags = ~ITEM_SLOT_IN_BACKPACK, // items in a backpack won't activate, anywhere else is fine
- datum/callback/drain_antimagic,
- datum/callback/expiration,
- )
-
-
- var/atom/movable/movable = parent
- if(!istype(movable))
- return COMPONENT_INCOMPATIBLE
-
- var/compatible = FALSE
- if(isitem(movable))
- RegisterSignal(movable, COMSIG_ITEM_EQUIPPED, PROC_REF(on_equip))
- RegisterSignal(movable, COMSIG_ITEM_DROPPED, PROC_REF(on_drop))
- RegisterSignal(movable, COMSIG_ATTACK, PROC_REF(on_attack))
- compatible = TRUE
- else if(ismob(movable))
- register_antimagic_signals(movable)
- compatible = TRUE
-
- if(movable.can_buckle)
- RegisterSignal(movable, COMSIG_MOVABLE_BUCKLE, PROC_REF(on_buckle))
- RegisterSignal(movable, COMSIG_MOVABLE_UNBUCKLE, PROC_REF(on_unbuckle))
- compatible = TRUE
-
- if(!compatible)
- return COMPONENT_INCOMPATIBLE
-
- src.antimagic_flags = antimagic_flags
- src.charges = charges
- src.inventory_flags = inventory_flags
- src.drain_antimagic = drain_antimagic
- src.expiration = expiration
-
-/datum/component/anti_magic/Destroy(force)
- drain_antimagic = null
- expiration = null
- return ..()
-
-/datum/component/anti_magic/proc/register_antimagic_signals(datum/on_what)
- RegisterSignal(on_what, COMSIG_MOB_RECEIVE_MAGIC, PROC_REF(block_receiving_magic), override = TRUE)
- RegisterSignal(on_what, COMSIG_MOB_RESTRICT_MAGIC, PROC_REF(restrict_casting_magic), override = TRUE)
-
-/datum/component/anti_magic/proc/unregister_antimagic_signals(datum/on_what)
- UnregisterSignal(on_what, list(COMSIG_MOB_RECEIVE_MAGIC, COMSIG_MOB_RESTRICT_MAGIC))
-
-/datum/component/anti_magic/proc/on_buckle(atom/movable/source, mob/living/bucklee)
- SIGNAL_HANDLER // COMSIG_MOVABLE_BUCKLE
- register_antimagic_signals(bucklee)
-
-/datum/component/anti_magic/proc/on_unbuckle(atom/movable/source, mob/living/bucklee)
- SIGNAL_HANDLER // COMSIG_MOVABLE_UNBUCKLE
- unregister_antimagic_signals(bucklee)
-
-/datum/component/anti_magic/proc/on_equip(atom/movable/source, mob/equipper, slot)
- SIGNAL_HANDLER // COMSIG_ITEM_EQUIPPED
- addtimer(CALLBACK(src, PROC_REF(on_equip_part_2), source, equipper, slot), 0.1 SECONDS) //We wait a moment to see if the item grants antimagic flags
-
-/datum/component/anti_magic/proc/on_equip_part_2(atom/movable/source, mob/equipper, slot)
- if(!(inventory_flags & slot)) //Check that the slot is valid for antimagic
- unregister_antimagic_signals(equipper)
- return
-
- register_antimagic_signals(equipper)
- if(HAS_TRAIT(equipper, TRAIT_ANTIMAGIC_NO_SELFBLOCK)) //If they do not care about antimagic, don't warn them
- return
- if(!alert_caster_on_equip)
- return
-
- // Check to see if we have any spells that are blocked due to antimagic
- if(!equipper.mind)
- return
- for(var/datum/spell/knownspell in equipper.mind.spell_list)
- if(!(knownspell.spell_requirements & SPELL_REQUIRES_NO_ANTIMAGIC))
- continue
-
- if(!(antimagic_flags & knownspell.antimagic_flags))
- continue
-
- to_chat(equipper, "[parent] is interfering with your ability to cast magic!")
- alert_caster_on_equip = FALSE
- break
-
-/datum/component/anti_magic/proc/on_drop(atom/movable/source, mob/user)
- SIGNAL_HANDLER //COMSIG_ITEM_DROPPED
-
- // Reset alert
- if(source.loc != user)
- alert_caster_on_equip = TRUE
- unregister_antimagic_signals(user)
-
-/datum/component/anti_magic/proc/block_receiving_magic(mob/living/carbon/source, casted_magic_flags, charge_cost, list/antimagic_sources)
- SIGNAL_HANDLER // COMSIG_MOB_RECEIVE_MAGIC
-
- // We do not block this type of magic, good day
- if(!(casted_magic_flags & antimagic_flags))
- return NONE
-
- // We have already blocked this spell
- if(parent in antimagic_sources)
- return NONE
-
- // Block success! Add this parent to the list of antimagic sources
- antimagic_sources += parent
-
- if((charges != INFINITY) && charge_cost > 0)
- drain_antimagic?.Invoke(source, parent)
- charges -= charge_cost
- if(charges <= 0)
- expiration?.Invoke(source, parent)
- qdel(src) // no more antimagic
-
- return COMPONENT_MAGIC_BLOCKED
-
-/// cannot cast magic with the same type of antimagic present
-/datum/component/anti_magic/proc/restrict_casting_magic(mob/user, magic_flags)
- SIGNAL_HANDLER // COMSIG_MOB_RESTRICT_MAGIC
-
- if(magic_flags & antimagic_flags)
- if(HAS_TRAIT(user, TRAIT_ANTIMAGIC_NO_SELFBLOCK)) // this trait bypasses magic casting restrictions
- return NONE
- return COMPONENT_MAGIC_BLOCKED
-
- return NONE
-
-/datum/component/anti_magic/proc/on_attack(obj/item/source, mob/living/target, mob/living/user)
- SIGNAL_HANDLER //COMSIG_ATTACK
- SEND_SIGNAL(target, COMSIG_ATOM_HOLY_ATTACK, source, user, antimagic_flags)
diff --git a/code/datums/components/caltrop.dm b/code/datums/components/caltrop.dm
deleted file mode 100644
index bc0a510f47eaf..0000000000000
--- a/code/datums/components/caltrop.dm
+++ /dev/null
@@ -1,95 +0,0 @@
-/datum/component/caltrop
- ///Minimum damage when crossed
- var/min_damage
-
- ///Maximum damage when crossed
- var/max_damage
-
- ///Probability of stunning and doing daamge
- var/probability
-
- ///Duration of weaken when crossed
- var/weaken_duration
-
- ///Shoebypassing, walking interaction, silence
- var/flags
-
- ///given to connect_loc to listen for something moving over target
- var/static/list/crossed_connections = list(
- COMSIG_ATOM_ENTERED = PROC_REF(on_entered),
- )
-
- var/cooldown = 0
-
-/datum/component/caltrop/Initialize(_min_damage = 0, _max_damage = 0, _probability = 100, _weaken_duration = 6 SECONDS, _flags = NONE)
- src.min_damage = _min_damage
- src.max_damage = max(_min_damage, _max_damage)
- src.probability = _probability
- src.weaken_duration = _weaken_duration
- src.flags = _flags
-
- if(ismovable(parent))
- AddComponent(/datum/component/connect_loc_behalf, parent, crossed_connections)
- else
- RegisterSignal(get_turf(parent), COMSIG_ATOM_ENTERED, PROC_REF(on_entered))
-
-/datum/component/caltrop/proc/on_entered(atom/source, atom/movable/entered, turf/old_loc)
- var/atom/A = parent
- if(!has_gravity(A))
- return
-
- if(!prob(probability))
- return
-
- if(!ishuman(entered))
- return
-
- var/mob/living/carbon/human/H = entered
-
- if(HAS_TRAIT(H, TRAIT_PIERCEIMMUNE))
- return
-
- if((flags & CALTROP_IGNORE_WALKERS) && H.m_intent == MOVE_INTENT_WALK)
- return
-
- var/picked_def_zone = pick(BODY_ZONE_L_LEG, BODY_ZONE_R_LEG)
- var/obj/item/organ/external/O = H.get_organ(picked_def_zone)
-
- if(!istype(O))
- return
-
- if(O.is_robotic())
- return
-
- var/feet_cover = ((H?.wear_suit?.body_parts_covered & FEET)) || ((H?.w_uniform?.body_parts_covered & FEET))
- var/bypass_shoes = flags & CALTROP_BYPASS_SHOES
-
- // doesn't penetrate if feet are covered
- if(!bypass_shoes && (H.shoes || feet_cover))
- return
-
- // check if shoes or outer suit can be punctured
- if(bypass_shoes && ((H?.shoes?.flags & THICKMATERIAL) || (H?.wear_suit?.flags & THICKMATERIAL)))
- return
-
- if(HAS_TRAIT(H, TRAIT_FLYING) || H.floating || H.buckled)
- return
-
- if(IS_HORIZONTAL(H) && HAS_TRAIT(H, TRAIT_CONTORTED_BODY))
- return TRUE
-
- var/damage = rand(min_damage, max_damage)
- H.apply_damage(damage, BRUTE, picked_def_zone)
-
- if(cooldown < world.time - 10) //cooldown to avoid message spam.
- if(!H.incapacitated(ignore_restraints = TRUE))
- H.visible_message("[H] steps on [A].", "You step on [A]!")
- else
- H.visible_message("[H] slides on [A]!", "You slide on [A]!")
-
- cooldown = world.time
- H.Weaken(weaken_duration)
-
-/datum/component/caltrop/UnregisterFromParent()
- if(ismovable(parent))
- qdel(GetComponent(/datum/component/connect_loc_behalf))
diff --git a/code/datums/components/connect_containers.dm b/code/datums/components/connect_containers.dm
deleted file mode 100644
index d998f0ae2943d..0000000000000
--- a/code/datums/components/connect_containers.dm
+++ /dev/null
@@ -1,68 +0,0 @@
-/// This component behaves similar to connect_loc_behalf, but it's nested and hooks a signal onto all MOVABLES containing this atom.
-/datum/component/connect_containers
- dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS
-
- /// An assoc list of signal -> procpath to register to the loc this object is on.
- var/list/connections
- /**
- * The atom the component is tracking. The component will delete itself if the tracked is deleted.
- * Signals will also be updated whenever it moves.
- */
- var/atom/movable/tracked
-
-/datum/component/connect_containers/Initialize(atom/movable/tracked, list/connections)
- . = ..()
- if(!ismovable(tracked))
- return COMPONENT_INCOMPATIBLE
-
- src.connections = connections
- set_tracked(tracked)
-
-/datum/component/connect_containers/Destroy()
- set_tracked(null)
- return ..()
-
-/datum/component/connect_containers/InheritComponent(datum/component/component, original, atom/movable/tracked, list/connections)
- // Not equivalent. Checks if they are not the same list via shallow comparison.
- if(!compare_list(src.connections, connections))
- stack_trace("connect_containers component attached to [parent] tried to inherit another connect_containers component with different connections")
- return
- if(src.tracked != tracked)
- set_tracked(tracked)
-
-/datum/component/connect_containers/proc/set_tracked(atom/movable/new_tracked)
- if(tracked)
- UnregisterSignal(tracked, list(COMSIG_MOVABLE_MOVED, COMSIG_PARENT_QDELETING))
- unregister_signals(tracked.loc)
- tracked = new_tracked
- if(!tracked)
- return
- RegisterSignal(tracked, COMSIG_MOVABLE_MOVED, PROC_REF(on_moved))
- RegisterSignal(tracked, COMSIG_PARENT_QDELETING, PROC_REF(handle_tracked_qdel))
- update_signals(tracked)
-
-/datum/component/connect_containers/proc/handle_tracked_qdel()
- SIGNAL_HANDLER // COMSIG_PARENT_QDELETING
- qdel(src)
-
-/datum/component/connect_containers/proc/update_signals(atom/movable/listener)
- if(!ismovable(listener.loc))
- return
-
- for(var/atom/movable/container as anything in get_nested_locs(listener))
- RegisterSignal(container, COMSIG_MOVABLE_MOVED, PROC_REF(on_moved))
- for(var/signal in connections)
- parent.RegisterSignal(container, signal, connections[signal])
-
-/datum/component/connect_containers/proc/unregister_signals(atom/movable/location)
- if(!ismovable(location))
- return
-
- for(var/atom/movable/target as anything in (get_nested_locs(location) + location))
- UnregisterSignal(target, COMSIG_MOVABLE_MOVED)
- parent.UnregisterSignal(target, connections)
-
-/datum/component/connect_containers/proc/on_moved(atom/movable/listener, atom/old_loc)
- SIGNAL_HANDLER // COMSIG_MOVABLE_MOVED
- unregister_signals(old_loc)
- update_signals(listener)
diff --git a/code/datums/components/connect_loc_behalf.dm b/code/datums/components/connect_loc_behalf.dm
deleted file mode 100644
index 8593fea8455e1..0000000000000
--- a/code/datums/components/connect_loc_behalf.dm
+++ /dev/null
@@ -1,67 +0,0 @@
-/// This component behaves similar to connect_loc, hooking into a signal on a tracked object's turf
-/// It has the ability to react to that signal on behalf of a separate listener however
-/// This has great use, primarily for components, but it carries with it some overhead
-/// So we do it separately as it needs to hold state which is very likely to lead to bugs if it remains as an element.
-/datum/component/connect_loc_behalf
- dupe_mode = COMPONENT_DUPE_UNIQUE
-
- /// An assoc list of signal -> procpath to register to the loc this object is on.
- var/list/connections
- var/atom/movable/tracked
- var/atom/tracked_loc
-
-/datum/component/connect_loc_behalf/Initialize(atom/movable/tracked, list/connections)
- . = ..()
- if(!istype(tracked))
- return COMPONENT_INCOMPATIBLE
- src.connections = connections
- src.tracked = tracked
-
-/datum/component/connect_loc_behalf/RegisterWithParent()
- RegisterSignal(tracked, COMSIG_MOVABLE_MOVED, PROC_REF(on_moved))
- RegisterSignal(tracked, COMSIG_PARENT_QDELETING, PROC_REF(handle_tracked_qdel))
- update_signals()
-
-/datum/component/connect_loc_behalf/UnregisterFromParent()
- unregister_signals()
- UnregisterSignal(tracked, list(
- COMSIG_MOVABLE_MOVED,
- COMSIG_PARENT_QDELETING,
- ))
-
- tracked = null
-
-/datum/component/connect_loc_behalf/proc/handle_tracked_qdel()
- SIGNAL_HANDLER // COMSIG_PARENT_QDELETING
- qdel(src)
-
-/datum/component/connect_loc_behalf/proc/update_signals()
- unregister_signals()
- //You may ask yourself, isn't this just silencing an error?
- //The answer is yes, but there's no good cheap way to fix it
- //What happens is the tracked object or hell the listener gets say, deleted, which makes targets[old_loc] return a null
- //The null results in a bad index, because of course it does
- //It's not a solvable problem though, since both actions, the destroy and the move, are sourced from the same signal send
- //And sending a signal should be agnostic of the order of listeners
- //So we need to either pick the order agnositic, or destroy safe
- //And I picked destroy safe. Let's hope this is the right path!
- if(isnull(tracked.loc))
- return
-
- tracked_loc = tracked.loc
-
- for(var/signal in connections)
- parent.RegisterSignal(tracked_loc, signal, connections[signal])
-
-/datum/component/connect_loc_behalf/proc/unregister_signals()
- if(isnull(tracked_loc))
- return
-
- parent.UnregisterSignal(tracked_loc, connections)
-
- tracked_loc = null
-
-/datum/component/connect_loc_behalf/proc/on_moved(sigtype, atom/movable/tracked, atom/old_loc)
- SIGNAL_HANDLER // COMSIG_MOVABLE_MOVED
- update_signals()
-
diff --git a/code/datums/components/connect_range.dm b/code/datums/components/connect_range.dm
deleted file mode 100644
index 8cf961d2221d3..0000000000000
--- a/code/datums/components/connect_range.dm
+++ /dev/null
@@ -1,114 +0,0 @@
-/**
- * This component behaves similar to connect_loc_behalf but for all turfs in range, hooking into a signal on each of them.
- * Just like connect_loc_behalf, It can react to that signal on behalf of a seperate listener.
- * Good for components, though it carries some overhead. Can't be an element as that may lead to bugs.
- */
-/datum/component/connect_range
- dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS
-
- /// An assoc list of signal -> procpath to register to the loc this object is on.
- var/list/connections
- /// The turfs currently connected to this component
- var/list/turfs = list()
- /**
- * The atom the component is tracking. The component will delete itself if the tracked is deleted.
- * Signals will also be updated whenever it moves (if it's a movable).
- */
- var/atom/tracked
-
- /// The component will hook into signals only on turfs not farther from tracked than this.
- var/range
- /// Whether the component works when the movable isn't directly located on a turf.
- var/works_in_containers
-
-/datum/component/connect_range/Initialize(atom/tracked, list/connections, range, works_in_containers = TRUE)
- if(!isatom(tracked) || isarea(tracked) || range < 0)
- return COMPONENT_INCOMPATIBLE
- src.connections = connections
- src.range = range
- set_tracked(tracked)
- src.works_in_containers = works_in_containers
-
-/datum/component/connect_range/Destroy()
- set_tracked(null)
- return ..()
-
-/datum/component/connect_range/InheritComponent(datum/component/component, original, atom/tracked, list/connections, range, works_in_containers)
- // Not equivalent. Checks if they are not the same list via shallow comparison.
- if(!compare_list(src.connections, connections))
- stack_trace("connect_range component attached to [parent] tried to inherit another connect_range component with different connections")
- return
- if(src.tracked != tracked)
- set_tracked(tracked)
- if(src.range == range && src.works_in_containers == works_in_containers)
- return
- //Unregister the signals with the old settings.
- unregister_signals(isturf(tracked) ? tracked : tracked.loc, turfs)
- src.range = range
- src.works_in_containers = works_in_containers
- //Re-register the signals with the new settings.
- update_signals(src.tracked)
-
-/datum/component/connect_range/proc/set_tracked(atom/new_tracked)
- if(tracked) //Unregister the signals from the old tracked and its surroundings
- unregister_signals(isturf(tracked) ? tracked : tracked.loc, turfs)
- UnregisterSignal(tracked, list(
- COMSIG_MOVABLE_MOVED,
- COMSIG_PARENT_QDELETING,
- ))
- tracked = new_tracked
- if(!tracked)
- return
- //Register signals on the new tracked atom and its surroundings.
- RegisterSignal(tracked, COMSIG_MOVABLE_MOVED, PROC_REF(on_moved))
- RegisterSignal(tracked, COMSIG_PARENT_QDELETING, PROC_REF(handle_tracked_qdel))
- update_signals(tracked)
-
-/datum/component/connect_range/proc/handle_tracked_qdel()
- SIGNAL_HANDLER // COMSIG_PARENT_QDELETING
- qdel(src)
-
-/datum/component/connect_range/proc/update_signals(atom/target, atom/old_loc)
- var/turf/current_turf = get_turf(target)
- if(isnull(current_turf))
- unregister_signals(old_loc, turfs)
- turfs = list()
- return
-
- if(ismovable(target.loc))
- if(!works_in_containers)
- unregister_signals(old_loc, turfs)
- turfs = list()
- return
- //Keep track of possible movement of all movables the target is in.
- for(var/atom/movable/container as anything in get_nested_locs(target))
- RegisterSignal(container, COMSIG_MOVABLE_MOVED, PROC_REF(on_moved))
-
- //Only register/unregister turf signals if it's moved to a new turf.
- if(current_turf == get_turf(old_loc))
- unregister_signals(old_loc, null)
- return
- var/list/old_turfs = turfs
- turfs = RANGE_TURFS(range, current_turf)
- unregister_signals(old_loc, old_turfs - turfs)
- for(var/turf/target_turf as anything in turfs - old_turfs)
- for(var/signal in connections)
- parent.RegisterSignal(target_turf, signal, connections[signal])
-
-/datum/component/connect_range/proc/unregister_signals(atom/location, list/remove_from)
- //The location is null or is a container and the component shouldn't have register signals on it
- if(isnull(location) || (!works_in_containers && !isturf(location)))
- return
-
- if(ismovable(location))
- for(var/atom/movable/target as anything in (get_nested_locs(location) + location))
- UnregisterSignal(target, COMSIG_MOVABLE_MOVED)
-
- if(!length(remove_from))
- return
- for(var/turf/target_turf as anything in remove_from)
- parent.UnregisterSignal(target_turf, connections)
-
-/datum/component/connect_range/proc/on_moved(atom/movable/movable, atom/old_loc)
- SIGNAL_HANDLER // COMSIG_MOVABLE_MOVED
- update_signals(movable, old_loc)
diff --git a/code/datums/components/defibrillator.dm b/code/datums/components/defibrillator.dm
deleted file mode 100644
index a33c4f0a36fb5..0000000000000
--- a/code/datums/components/defibrillator.dm
+++ /dev/null
@@ -1,414 +0,0 @@
-/**
- * A component for an item that attempts to defibrillate a mob when activated.
- */
-
-#define DEFIB_TIME 5 SECONDS
-
-/datum/component/defib
- /// If this is being used by a borg or not, with necessary safeties applied if so.
- var/robotic
- /// If it should penetrate space suits
- var/combat
- /// If combat is true, this determines whether or not it should always cause a heart attack.
- var/heart_attack_chance
- /// Whether the safeties are enabled or not
- var/safety
- /// If the defib is actively performing a defib cycle
- var/busy = FALSE
- /// Cooldown length for this defib in deciseconds
- var/cooldown
- /// Whether or not we're currently on cooldown
- var/on_cooldown = FALSE
- /// How fast the defib should work.
- var/speed_multiplier
- /// If true, EMPs will have no effect.
- var/emp_proof
- /// If true, this cannot be emagged.
- var/emag_proof
- /// uid to an item that should be making noise and handling things that our direct parent shouldn't be concerned with.
- var/actual_unit_uid
- /// Sound for defib windup.
- var/charge_sound = 'sound/machines/defib_charge.ogg'
- /// Sound when the defib is successful.
- var/success_sound = 'sound/machines/defib_success.ogg'
- /// Sound when the defib fails.
- var/fail_sound = 'sound/machines/defib_failed.ogg'
- /// Sound when the defib shocks the patient.
- var/zap_sound = 'sound/machines/defib_zap.ogg'
- /// Sound when the defib's safety is enabled.
- var/safety_on_sound = 'sound/machines/defib_saftyon.ogg'
- /// Sound when the defib's safety is disabled.
- var/safety_off_sound = 'sound/machines/defib_saftyoff.ogg'
-
-/**
- * Create a new defibrillation component.
- *
- * Arguments:
- * * robotic - whether this should be treated like a borg module.
- * * cooldown - Minimum time possible between shocks.
- * * speed_multiplier - Speed multiplier for defib do-afters.
- * * combat - If true, the defib can zap through hardsuits.
- * * heart_attack_chance - If combat and safeties are off, the % chance for this to cause a heart attack on harm intent.
- * * safe_by_default - If true, safety will be enabled by default.
- * * emp_proof - If true, safety won't be switched by emp. Note that the device itself can still have behavior from it, it's just that the component will not.
- * * emag_proof - If true, safety won't be switched by emag. Note that the device itself can still have behavior from it, it's just that the component will not.
- * * actual_unit - Unit which the component's parent is based from, such as a large defib unit or a borg. The actual_unit will make the sounds and be the "origin" of visible messages, among other things.
- */
-/datum/component/defib/Initialize(robotic, cooldown = 5 SECONDS, speed_multiplier = 1, combat = FALSE, heart_attack_chance = 100, safe_by_default = TRUE, emp_proof = FALSE, emag_proof = FALSE, obj/item/actual_unit = null)
- if(!isitem(parent))
- return COMPONENT_INCOMPATIBLE
-
- src.robotic = robotic
- src.speed_multiplier = speed_multiplier
- src.cooldown = cooldown
- src.combat = combat
- src.heart_attack_chance = heart_attack_chance
- safety = safe_by_default
- src.emp_proof = emp_proof
- src.emag_proof = emag_proof
-
- if(actual_unit)
- actual_unit_uid = actual_unit.UID()
-
- var/effect_target = isnull(actual_unit) ? parent : actual_unit
-
- RegisterSignal(parent, COMSIG_ATTACK, PROC_REF(trigger_defib))
- RegisterSignal(effect_target, COMSIG_ATOM_EMAG_ACT, PROC_REF(on_emag))
- RegisterSignal(effect_target, COMSIG_ATOM_EMP_ACT, PROC_REF(on_emp))
-
-/**
- * Get the "parent" that effects (emags, EMPs) should be applied onto.
- */
-/datum/component/defib/proc/get_effect_target()
- var/actual_unit = locateUID(actual_unit_uid)
- if(!actual_unit)
- return parent
- return actual_unit
-
-/datum/component/defib/proc/on_emp(obj/item/unit)
- SIGNAL_HANDLER // COMSIG_ATOM_EMP_ACT
- if(emp_proof)
- return
-
- if(safety)
- safety = FALSE
- unit.visible_message("[unit] beeps: Safety protocols disabled!")
- playsound(get_turf(unit), safety_off_sound, 50, FALSE)
- else
- safety = TRUE
- unit.visible_message("[unit] beeps: Safety protocols enabled!")
- playsound(get_turf(unit), safety_on_sound, 50, FALSE)
-
-/datum/component/defib/proc/on_emag(obj/item/unit, mob/user)
- SIGNAL_HANDLER // COMSIG_ATOM_EMAG_ACT
- if(emag_proof)
- return
- safety = !safety
- if(user && !robotic)
- to_chat(user, "You silently [safety ? "disable" : "enable"] [unit]'s safety protocols with the card.")
-
-/datum/component/defib/proc/set_cooldown(how_short)
- on_cooldown = TRUE
- addtimer(CALLBACK(src, PROC_REF(end_cooldown)), how_short)
-
-/datum/component/defib/proc/end_cooldown()
- on_cooldown = FALSE
- SEND_SIGNAL(parent, COMSIG_DEFIB_READY)
-
-/**
- * Start the defibrillation process when triggered by a signal.
- */
-/datum/component/defib/proc/trigger_defib(obj/item/paddles, mob/living/carbon/human/target, mob/living/user)
- SIGNAL_HANDLER // COMSIG_ATTACK
- // This includes some do-afters, so we have to pass it off asynchronously
- INVOKE_ASYNC(src, PROC_REF(defibrillate), user, target)
- return TRUE
-
-/**
- * Perform a defibrillation.
- */
-/datum/component/defib/proc/defibrillate(mob/living/user, mob/living/carbon/human/target)
- // Before we do all the hard work, make sure we aren't already defibbing someone
- if(busy)
- return
-
- var/parent_unit = locateUID(actual_unit_uid)
- var/should_cause_harm = user.a_intent == INTENT_HARM && !safety
-
- // Find what the defib should be referring to itself as
- var/atom/defib_ref
- if(parent_unit)
- defib_ref = parent_unit
- else if(robotic)
- defib_ref = user
- if(!defib_ref) // Contingency
- defib_ref = parent
-
- // Check what the unit itself has to say about how the defib went
- var/application_result = SEND_SIGNAL(parent, COMSIG_DEFIB_PADDLES_APPLIED, user, target, should_cause_harm)
-
- if(application_result & COMPONENT_BLOCK_DEFIB_DEAD)
- user.visible_message("[defib_ref] beeps: Unit is unpowered.")
- playsound(get_turf(defib_ref), fail_sound, 50, FALSE)
- return
-
- if(on_cooldown)
- to_chat(user, "[defib_ref] is recharging.")
- return
-
- if(application_result & COMPONENT_BLOCK_DEFIB_MISC)
- return // The unit should handle this
-
- if(!istype(target))
- if(robotic)
- to_chat(user, "This unit is only designed to work on humanoid lifeforms.")
- else
- to_chat(user, "The instructions on [defib_ref] don't mention how to defibrillate that...")
- return
-
- if(should_cause_harm && combat && heart_attack_chance == 100)
- combat_fibrillate(user, target)
- SEND_SIGNAL(parent, COMSIG_DEFIB_SHOCK_APPLIED, user, target, should_cause_harm, TRUE)
- busy = FALSE
- return
-
- if(should_cause_harm)
- fibrillate(user, target)
- SEND_SIGNAL(parent, COMSIG_DEFIB_SHOCK_APPLIED, user, target, should_cause_harm, TRUE)
- return
-
- user.visible_message(
- "[user] begins to place [parent] on [target]'s chest.",
- "You begin to place [parent] on [target.name]'s chest."
- )
-
- busy = TRUE
- var/mob/dead/observer/ghost = target.get_ghost()
- if(ghost)
- to_chat(ghost, "Your heart is being defibrillated. Return to your body if you want to be revived! (Verbs -> Ghost -> Re-enter corpse)")
- window_flash(ghost.client)
- SEND_SOUND(ghost, sound('sound/effects/genetics.ogg'))
- else if(HAS_TRAIT_FROM(target, TRAIT_FAKEDEATH, CHANGELING_TRAIT))
- to_chat(target, "Your heart is being defibrillated. Click the defibrillator status to be revived!")
- window_flash(target.client)
- SEND_SOUND(target, sound('sound/effects/genetics.ogg'))
- target.throw_alert("cling_defib", /atom/movable/screen/alert/changeling_defib_revive, alert_args = list(parent, target))
-
- user.visible_message("[user] places [parent] on [target]'s chest.", "You place [parent] on [target]'s chest.")
- playsound(get_turf(defib_ref), charge_sound, 50, FALSE)
-
- if(ghost && !ghost.client && !QDELETED(ghost))
- log_debug("Ghost of name [ghost.name] is bound to [target.real_name], but lacks a client. Deleting ghost.")
- QDEL_NULL(ghost)
-
- var/signal_result = SEND_SIGNAL(target, COMSIG_LIVING_PRE_DEFIB, user, parent, ghost)
-
- if(!do_after(user, DEFIB_TIME * speed_multiplier, target = target)) // Placed on chest and short delay to shock for dramatic effect, revive time is 5sec total
- busy = FALSE
- return
-
- if(istype(target.wear_suit, /obj/item/clothing/suit/space) && !combat)
- user.visible_message("[defib_ref] buzzes: Patient's chest is obscured. Operation aborted.")
- playsound(get_turf(defib_ref), fail_sound, 50, FALSE)
- busy = FALSE
- return
-
- signal_result |= SEND_SIGNAL(target, COMSIG_LIVING_DEFIBBED, user, parent, ghost)
-
- if(signal_result & COMPONENT_DEFIB_OVERRIDE)
- // Let our signal handle it
- busy = FALSE
- return
-
- if(target.undergoing_cardiac_arrest() && target.stat != DEAD) // Can have a heart attack and heart is either missing, necrotic, or not beating
- var/datum/organ/heart/heart = target.get_int_organ_datum(ORGAN_DATUM_HEART)
- if(!heart)
- user.visible_message("[defib_ref] buzzes: Resuscitation failed - Failed to pick up any heart electrical activity.")
- else if(heart.linked_organ.status & ORGAN_DEAD)
- user.visible_message("[defib_ref] buzzes: Resuscitation failed - Heart necrosis detected.")
- if(!heart || (heart.linked_organ.status & ORGAN_DEAD))
- playsound(get_turf(defib_ref), fail_sound, 50, FALSE)
- busy = FALSE
- return
-
- target.set_heartattack(FALSE)
- SEND_SIGNAL(target, COMSIG_LIVING_MINOR_SHOCK, 100)
- SEND_SIGNAL(parent, COMSIG_DEFIB_SHOCK_APPLIED, user, target, should_cause_harm, TRUE)
- set_cooldown(cooldown)
- user.visible_message("[defib_ref] pings: Cardiac arrhythmia corrected.")
- target.visible_message("[target]'s body convulses a bit.", "You feel a jolt, and your heartbeat seems to steady.")
- playsound(get_turf(defib_ref), zap_sound, 50, TRUE, -1)
- playsound(get_turf(defib_ref), "bodyfall", 50, TRUE)
- playsound(get_turf(defib_ref), success_sound, 50, FALSE)
- busy = FALSE
- return
-
- if(target.stat != DEAD && !HAS_TRAIT(target, TRAIT_FAKEDEATH))
- user.visible_message("[defib_ref] buzzes: Patient is not in a valid state. Operation aborted.")
- playsound(get_turf(defib_ref), fail_sound, 50, FALSE)
- busy = FALSE
- return
-
- target.visible_message("[target]'s body convulses a bit.")
- playsound(get_turf(defib_ref), "bodyfall", 50, TRUE)
- playsound(get_turf(defib_ref), zap_sound, 50, TRUE, -1)
- ghost = target.get_ghost(TRUE) // We have to double check whether the dead guy has entered their body during the above
-
- // Run through some quick failure states after shocking.
- var/time_dead = world.time - target.timeofdeath
-
- var/failure_message
- if(!target.is_revivable())
- failure_message = "[defib_ref] buzzes: Resuscitation failed - Heart tissue damage beyond point of no return for defibrillation."
- else if(target.getBruteLoss() >= 180 || target.getFireLoss() >= 180)
- failure_message = "[defib_ref] buzzes: Resuscitation failed - Severe tissue damage detected."
- else if(HAS_TRAIT(target, TRAIT_HUSK))
- failure_message = "[defib_ref] buzzes: Resuscitation failed - Subject is husked."
- else if(target.blood_volume < BLOOD_VOLUME_SURVIVE)
- failure_message = "[defib_ref] buzzes: Resuscitation failed - Patient blood volume critically low."
- else if(!target.get_organ_slot("brain")) // So things like headless clings don't get outed
- failure_message = "[defib_ref] buzzes: Resuscitation failed - No brain detected within patient."
- else if(ghost)
- if(!ghost.can_reenter_corpse || target.suiciding) // DNR or AntagHUD
- failure_message = "[defib_ref] buzzes: Resuscitation failed - No electrical brain activity detected."
- else
- failure_message = "[defib_ref] buzzes: Resuscitation failed - Patient's brain is unresponsive. Further attempts may succeed."
- else if(HAS_TRAIT(target, TRAIT_FAKEDEATH))
- if(signal_result & COMPONENT_DEFIB_FAKEDEATH_DENIED)
- failure_message = "[defib_ref] buzzes: Resuscitation failed - Patient's brain is unresponsive. Further attempts may succeed."
- else if(signal_result & COMPONENT_DEFIB_FAKEDEATH_ACCEPTED)
- // as much as I hate that this is here, it has to come after the `Patient is not in a valid state. Operation aborted.` check.
- REMOVE_TRAIT(target, TRAIT_FAKEDEATH, CHANGELING_TRAIT)
- else
- failure_message = "[defib_ref] buzzes: Resuscitation failed." // has a fakedeath like capulettium
-
- else if((signal_result & COMPONENT_BLOCK_DEFIB) || HAS_TRAIT(target, TRAIT_BADDNA) || target.suiciding) // these are a bit more arbitrary
- failure_message = "[defib_ref] buzzes: Resuscitation failed."
-
- if(failure_message)
- user.visible_message(failure_message)
- playsound(get_turf(defib_ref), fail_sound, 50, FALSE)
- else
- // Heal each basic damage type by as much as we're under -100 health
- var/damage_above_threshold = -(min(target.health, HEALTH_THRESHOLD_DEAD) - HEALTH_THRESHOLD_DEAD)
- var/heal_amount = damage_above_threshold + 5
- target.adjustOxyLoss(-heal_amount)
- target.adjustToxLoss(-heal_amount)
- target.adjustFireLoss(-heal_amount)
- target.adjustBruteLoss(-heal_amount)
-
- // Inflict some brain damage scaling with time spent dead
- var/obj/item/organ/internal/brain/sponge = target.get_int_organ(/obj/item/organ/internal/brain)
- var/defib_time_brain_damage = min(100 * time_dead / BASE_DEFIB_TIME_LIMIT, 99) // 20 from 1 minute onward, +20 per minute up to 99
- if(time_dead > DEFIB_TIME_LOSS && defib_time_brain_damage > sponge.damage)
- target.setBrainLoss(defib_time_brain_damage)
-
- target.set_heartattack(FALSE)
- target.update_revive()
- target.KnockOut()
- target.Paralyse(10 SECONDS)
- target.emote("gasp")
-
- // Check if the brain has more than a critical amount of brain damage
- if(target.check_brain_threshold(BRAIN_DAMAGE_RATIO_CRITICAL))
- // If you want to treat this with mannitol, it'll have to metabolize while the patient is alive, so it's alright to bring them back up for a minute
- playsound(get_turf(defib_ref), safety_off_sound, 50, FALSE)
- user.visible_message("[defib_ref] chimes: Minimal brain activity detected, brain treatment recommended for full resuscitation.")
- else
- playsound(get_turf(defib_ref), success_sound, 50, FALSE)
-
- user.visible_message("[defib_ref] pings: Resuscitation successful.")
-
- SEND_SIGNAL(target, COMSIG_LIVING_MINOR_SHOCK, 100)
- if(ishuman(target.pulledby)) // For some reason, pulledby isnt a list despite it being possible to be pulled by multiple people
- excess_shock(user, target, target.pulledby, defib_ref)
- for(var/obj/item/grab/G in target.grabbed_by)
- if(ishuman(G.assailant))
- excess_shock(user, target, G.assailant, defib_ref)
- if(target.receiving_cpr_from)
- var/mob/living/carbon/human/H = locateUID(target.receiving_cpr_from)
- if(istype(H))
- excess_shock(user, target, H, defib_ref)
-
- target.med_hud_set_health()
- target.med_hud_set_status()
- add_attack_logs(user, target, "Revived with [defib_ref]")
- SSblackbox.record_feedback("tally", "players_revived", 1, "defibrillator")
- SEND_SIGNAL(parent, COMSIG_DEFIB_SHOCK_APPLIED, user, target, should_cause_harm, isnull(failure_message))
- set_cooldown(cooldown)
- busy = FALSE
-
-/**
- * Inflict stamina loss (and possibly inflict cardiac arrest) on someone.
- *
- * Arguments:
- * * user - wielder of the defib
- * * target - person getting shocked
- */
-/datum/component/defib/proc/fibrillate(mob/user, mob/living/carbon/human/target)
- if(!istype(target))
- return
- busy = TRUE
- target.visible_message("[user] has touched [target] with [parent]!", \
- "[user] touches you with [parent], and you feel a strong jolt!")
- target.apply_damage(60, STAMINA)
- target.KnockDown(10 SECONDS)
- playsound(get_turf(parent), zap_sound, 50, TRUE, -1)
- target.emote("gasp")
- if(combat && prob(heart_attack_chance))
- target.set_heartattack(TRUE)
- SEND_SIGNAL(target, COMSIG_LIVING_MINOR_SHOCK, 100)
- add_attack_logs(user, target, "Stunned with [parent]")
- set_cooldown(cooldown)
- busy = FALSE
-
-/datum/component/defib/proc/combat_fibrillate(mob/user, mob/living/carbon/human/target)
- if(!istype(target))
- return
- busy = TRUE
- target.apply_damage(60, STAMINA)
- target.emote("gasp")
- add_attack_logs(user, target, "Stunned with [parent]")
- target.KnockDown(4 SECONDS)
- if(IS_HORIZONTAL(target) && HAS_TRAIT(target, TRAIT_HANDS_BLOCKED)) // Weakening exists which doesn't floor you while stunned
- add_attack_logs(user, target, "Gave a heart attack with [parent]")
- target.set_heartattack(TRUE)
- target.visible_message("[user] has touched [target] with [parent]!", \
- "[user] touches you with [parent], and you feel a strong jolt!")
- playsound(get_turf(parent), zap_sound, 50, TRUE, -1)
- SEND_SIGNAL(target, COMSIG_LIVING_MINOR_SHOCK, 100)
- set_cooldown(cooldown)
- return
- target.visible_message("[user] touches [target] lightly with [parent]!")
- set_cooldown(2.5 SECONDS)
-
-/*
- * Pass excess shock from a defibrillation into someone else.
- *
- * Arguments:
- * * user - The person using the defib
- * * origin - The person the shock was originally applied to, the person being defibrillated
- * * affecting - The person the shock is spreading to and negatively affecting.
- * * cell_location - item holding the power source.
-*/
-/datum/component/defib/proc/excess_shock(mob/user, mob/living/origin, mob/living/carbon/human/affecting, obj/item/cell_location)
- if(user == affecting)
- return
- var/power_source
- if(robotic)
- power_source = user
- else
- if(cell_location)
- power_source = locate(/obj/item/stock_parts/cell) in cell_location
-
- if(!power_source)
- return
-
- if(electrocute_mob(affecting, power_source, origin)) // shock anyone touching them >:)
- var/datum/organ/heart/heart = affecting.get_int_organ_datum(ORGAN_DATUM_HEART)
- if(heart.linked_organ.parent_organ == "chest" && affecting.has_both_hands()) // making sure the shock will go through their heart (drask hearts are in their head), and that they have both arms so the shock can cross their heart inside their chest
- affecting.visible_message("[affecting]'s entire body shakes as a shock travels up [affecting.p_their()] arm!", \
- "You feel a powerful shock travel up your [affecting.hand ? affecting.get_organ("l_arm") : affecting.get_organ("r_arm")] and back down your [affecting.hand ? affecting.get_organ("r_arm") : affecting.get_organ("l_arm")]!")
- affecting.set_heartattack(TRUE)
-
-#undef DEFIB_TIME
diff --git a/code/datums/components/ducttape.dm b/code/datums/components/ducttape.dm
deleted file mode 100644
index 230e16edf8170..0000000000000
--- a/code/datums/components/ducttape.dm
+++ /dev/null
@@ -1,82 +0,0 @@
-/datum/component/ducttape
- var/x_offset = 0
- var/y_offset = 0
- var/icon/tape_overlay = null
- var/hide_tape = FALSE
-
-/datum/component/ducttape/Initialize(obj/item/I, mob/user, x, y, hide_tape)
- if(!istype(I)) //Something went wrong
- return
- if(!hide_tape) //if TRUE this hides the tape overlay and added examine text
- RegisterSignal(parent, COMSIG_ATOM_UPDATE_OVERLAYS, PROC_REF(add_tape_overlay))
- RegisterSignal(parent, COMSIG_PARENT_EXAMINE, PROC_REF(add_tape_text))
- x_offset = x
- y_offset = y
- RegisterSignal(parent, COMSIG_AFTER_ATTACK, PROC_REF(afterattack__legacy__attackchain))
- RegisterSignal(parent, COMSIG_ITEM_PICKUP, PROC_REF(pick_up))
- I.update_icon() //Do this first so the action button properly shows the icon
- if(!hide_tape) //the tape can no longer be removed if TRUE
- var/datum/action/item_action/remove_tape/RT = new(I)
- if(I.loc == user)
- RT.Grant(user)
- I.add_tape()
-
-/datum/component/proc/add_tape_text(datum/source, mob/user, list/examine_list)
- examine_list += "There's some sticky tape attached to [source]."
-
-/datum/component/ducttape/proc/add_tape_overlay(obj/item/O)
- tape_overlay = new('icons/obj/bureaucracy.dmi', "tape")
- tape_overlay.Shift(EAST, x_offset - 2)
- tape_overlay.Shift(NORTH, y_offset - 2)
- O.add_overlay(tape_overlay)
-
-/datum/component/ducttape/proc/remove_tape(obj/item/I, mob/user)
- to_chat(user, "You tear the tape off [I]!")
- playsound(I, 'sound/items/poster_ripped.ogg', 50, 1)
- new /obj/item/trash/tapetrash(user.loc)
- I.update_icon()
- I.anchored = initial(I.anchored)
- for(var/datum/action/item_action/remove_tape/RT in I.actions)
- RT.Remove(user)
- qdel(RT)
- I.cut_overlay(tape_overlay)
- user.transfer_fingerprints_to(I)
- I.remove_tape()
- qdel(src)
-
-/datum/component/ducttape/proc/afterattack__legacy__attackchain(obj/item/I, atom/target, mob/user, proximity, params)
- if(!proximity)
- return
- if(!isturf(target))
- return
- var/turf/source_turf = get_turf(I)
- var/turf/target_turf = target
- var/x_offset
- var/y_offset
- if(target_turf != get_turf(I)) //Trying to stick it on a wall, don't move it to the actual wall or you can move the item through it. Instead set the pixels as appropriate
- var/target_direction = get_dir(source_turf, target_turf)//The direction we clicked
- // Snowflake diagonal handling
- if(target_direction in GLOB.diagonals)
- to_chat(user, "You can't reach [target_turf].")
- return
- if(target_direction & EAST)
- x_offset = 16
- y_offset = rand(-12, 12)
- else if(target_direction & WEST)
- x_offset = -16
- y_offset = rand(-12, 12)
- else if(target_direction & NORTH)
- x_offset = rand(-12, 12)
- y_offset = 16
- else if(target_direction & SOUTH)
- x_offset = rand(-12, 12)
- y_offset = -16
- if(!user.drop_item_to_ground(I))
- return
- to_chat(user, "You stick [I] to [target_turf].")
- I.pixel_x = x_offset
- I.pixel_y = y_offset
-
-/datum/component/ducttape/proc/pick_up(obj/item/I, mob/user)
- I.pixel_x = initial(I.pixel_x)
- I.pixel_y = initial(I.pixel_y)
diff --git a/code/datums/components/ghost_direct_control.dm b/code/datums/components/ghost_direct_control.dm
deleted file mode 100644
index 06eaa3e018cd8..0000000000000
--- a/code/datums/components/ghost_direct_control.dm
+++ /dev/null
@@ -1,147 +0,0 @@
-/**
- * Component which lets ghosts click on a mob to take control of it
- */
-/datum/component/ghost_direct_control
- /// Message to display upon successful possession
- var/assumed_control_message
- /// Type of ban you can get to prevent you from accepting this role
- var/ban_type
- /// Any extra checks which need to run before we take over
- var/datum/callback/extra_control_checks
- /// Callback run after someone successfully takes over the body
- var/datum/callback/after_assumed_control
- /// If we're currently awaiting the results of a ghost poll
- var/awaiting_ghosts = FALSE
- /// Is this an antagonist spawner, so we check ROLE_SYNDICATE
- var/is_antag_spawner
-
-/datum/component/ghost_direct_control/Initialize(
- ban_type = ROLE_SENTIENT,
- role_name = null,
- poll_question = null,
- poll_candidates = TRUE,
- poll_length = 10 SECONDS,
- assumed_control_message = null,
- datum/callback/extra_control_checks,
- datum/callback/after_assumed_control,
- is_antag_spawner = TRUE,
- )
- . = ..()
- if(!isliving(parent))
- return COMPONENT_INCOMPATIBLE
-
- src.ban_type = ban_type
- src.assumed_control_message = assumed_control_message || "You are [parent]!"
- src.extra_control_checks = extra_control_checks
- src.after_assumed_control = after_assumed_control
- src.is_antag_spawner = is_antag_spawner
-
-
- if(poll_candidates)
- INVOKE_ASYNC(src, PROC_REF(request_ghost_control), poll_question, role_name || "[parent]", poll_length)
-
-/datum/component/ghost_direct_control/RegisterWithParent()
- . = ..()
- RegisterSignal(parent, COMSIG_ATOM_ATTACK_GHOST, PROC_REF(on_ghost_clicked))
- RegisterSignal(parent, COMSIG_PARENT_EXAMINE, PROC_REF(on_examined))
- RegisterSignal(parent, COMSIG_MOB_LOGIN, PROC_REF(on_login))
-
-/datum/component/ghost_direct_control/UnregisterFromParent()
- UnregisterSignal(parent, list(COMSIG_ATOM_ATTACK_GHOST, COMSIG_PARENT_EXAMINE, COMSIG_MOB_LOGIN))
- return ..()
-
-/datum/component/ghost_direct_control/Destroy(force)
- extra_control_checks = null
- after_assumed_control = null
- return ..()
-
-/// Inform ghosts that they can possess this
-/datum/component/ghost_direct_control/proc/on_examined(datum/source, mob/user, list/examine_text)
- SIGNAL_HANDLER //COMSIG_PARENT_EXAMINE
- if(!isobserver(user))
- return
- var/mob/living/our_mob = parent
- if(our_mob.stat == DEAD || our_mob.key || awaiting_ghosts)
- return
- examine_text += "You could take control of this mob by clicking on it."
-
-/// Send out a request for a brain
-/datum/component/ghost_direct_control/proc/request_ghost_control(poll_question, role_name, poll_length)
- awaiting_ghosts = TRUE
- var/list/candidates = SSghost_spawns.poll_candidates(
- question = poll_question,
- role = ban_type,
- poll_time = poll_length,
- source = parent,
- role_cleanname = role_name,
- flash_window = FALSE,
- dont_play_notice_sound = TRUE
- )
- awaiting_ghosts = FALSE
- var/mob/chosen_one = null
- if(length(candidates))
- chosen_one = pick(candidates)
-
- if(isnull(chosen_one))
- return
- assume_direct_control(chosen_one)
-
-/// A ghost clicked on us, they want to get in this body
-/datum/component/ghost_direct_control/proc/on_ghost_clicked(mob/our_mob, mob/dead/observer/hopeful_ghost)
- SIGNAL_HANDLER // COMSIG_ATOM_ATTACK_GHOST
- if(our_mob.key)
- qdel(src)
- return COMPONENT_CANCEL_ATTACK_CHAIN
- if(!hopeful_ghost.client)
- return COMPONENT_CANCEL_ATTACK_CHAIN
- if(awaiting_ghosts)
- to_chat(hopeful_ghost, "Ghost candidate selection currently in progress!")
- return COMPONENT_CANCEL_ATTACK_CHAIN
- if(!SSticker.HasRoundStarted())
- to_chat(hopeful_ghost, "You cannot assume control of this until after the round has started!")
- return COMPONENT_CANCEL_ATTACK_CHAIN
- INVOKE_ASYNC(src, PROC_REF(attempt_possession), our_mob, hopeful_ghost)
- return COMPONENT_CANCEL_ATTACK_CHAIN
-
-/// We got far enough to establish that this mob is a valid target, let's try to posssess it
-/datum/component/ghost_direct_control/proc/attempt_possession(mob/our_mob, mob/dead/observer/hopeful_ghost)
- var/ghost_asked = tgui_alert(usr, "Become [our_mob]?", "Are you sure?", list("Yes", "No"))
- if(ghost_asked != "Yes" || QDELETED(our_mob))
- return
- assume_direct_control(hopeful_ghost)
-
-/// Grant possession of our mob, component is now no longer required
-/datum/component/ghost_direct_control/proc/assume_direct_control(mob/harbinger)
- if(QDELETED(src))
- to_chat(harbinger, "Offer to possess creature has expired!")
- return
- if(jobban_isbanned(harbinger, ban_type) || jobban_isbanned(harbinger, ROLE_SENTIENT) || (is_antag_spawner && jobban_isbanned(harbinger, ROLE_SYNDICATE)))
- to_chat(harbinger, "You are banned from playing as this role!")
- return
- var/mob/living/new_body = parent
- if(new_body.stat == DEAD)
- to_chat(harbinger, "This body has passed away, it is of no use!")
- return
- if(new_body.key)
- to_chat(harbinger, "[parent] has already become sapient!")
- qdel(src)
- return
- if(extra_control_checks && !extra_control_checks.Invoke(harbinger))
- return
-
- // doesn't transfer mind because that transfers antag datum as well
- new_body.key = harbinger.key
-
- // Already qdels due to below proc but just in case
- if(!QDELETED(src))
- qdel(src)
-
-/// When someone assumes control, get rid of our component
-/datum/component/ghost_direct_control/proc/on_login(mob/harbinger)
- SIGNAL_HANDLER //COMSIG_MOB_LOGIN
- // This proc is called the very moment .key is set, so we need to force mind to initialize here if we want the invoke to affect the mind of the mob
- if(isnull(harbinger.mind))
- harbinger.mind_initialize()
- to_chat(harbinger, "[assumed_control_message]")
- after_assumed_control?.Invoke(harbinger)
- qdel(src)
diff --git a/code/datums/components/material_container.dm b/code/datums/components/material_container.dm
deleted file mode 100644
index e35d9379578b8..0000000000000
--- a/code/datums/components/material_container.dm
+++ /dev/null
@@ -1,421 +0,0 @@
-/*
- This datum should be used for handling mineral contents of machines and whatever else is supposed to hold minerals and make use of them.
-
- Variables:
- amount - raw amount of the mineral this container is holding, calculated by the defined value MINERAL_MATERIAL_AMOUNT=2000.
- max_amount - max raw amount of mineral this container can hold.
- sheet_type - type of the mineral sheet the container handles, used for output.
- parent - object that this container is being used by, used for output.
- MAX_STACK_SIZE - size of a stack of mineral sheets. Constant.
-*/
-
-/datum/component/material_container
- var/total_amount = 0
- var/max_amount
- var/sheet_type
- var/list/materials
- var/show_on_examine
- var/disable_attackby
- var/list/allowed_typecache
- var/last_inserted_id
- var/precise_insertion = FALSE
- var/datum/callback/precondition
- var/datum/callback/after_insert
-
-/datum/component/material_container/Initialize(list/mat_list, max_amt = 0, _show_on_examine = FALSE, list/allowed_types, datum/callback/_precondition, datum/callback/_after_insert, _disable_attackby)
- materials = list()
- max_amount = max(0, max_amt)
- show_on_examine = _show_on_examine
- disable_attackby = _disable_attackby
-
- if(allowed_types)
- if(ispath(allowed_types) && allowed_types == /obj/item/stack)
- allowed_typecache = GLOB.typecache_stack
- else
- allowed_typecache = typecacheof(allowed_types)
-
- precondition = _precondition
- after_insert = _after_insert
-
- RegisterSignal(parent, COMSIG_ATTACK_BY, PROC_REF(OnAttackBy))
- RegisterSignal(parent, COMSIG_PARENT_EXAMINE, PROC_REF(OnExamine))
-
- var/list/possible_mats = list()
- for(var/mat_type in subtypesof(/datum/material))
- var/datum/material/MT = mat_type
- possible_mats[initial(MT.id)] = mat_type
- for(var/id in mat_list)
- if(possible_mats[id])
- var/mat_path = possible_mats[id]
- materials[id] = new mat_path()
-
-/datum/component/material_container/Destroy(force, silent)
- QDEL_LIST_ASSOC_VAL(materials)
- return ..()
-
-/datum/component/material_container/proc/OnExamine(datum/source, mob/user, list/examine_list)
- if(show_on_examine)
- for(var/I in materials)
- var/datum/material/M = materials[I]
- var/amt = amount(M.id)
- if(amt)
- examine_list += "It has [amt] units of [lowertext(M.name)] stored."
-
-/datum/component/material_container/proc/OnAttackBy(datum/source, obj/item/I, mob/living/user)
- var/list/tc = allowed_typecache
- if(disable_attackby)
- return
- // Allow tools to be inserted on harm and help intent since they might be used for construction
- // otherwise user needs to be on help intent
- if(!((I.tool_behaviour && user.a_intent == INTENT_HARM) || user.a_intent == INTENT_HELP))
- return
- if(I.flags & ABSTRACT)
- return
- if((I.flags_2 & (HOLOGRAM_2 | NO_MAT_REDEMPTION_2)) || (tc && !is_type_in_typecache(I, tc)))
- to_chat(user, "[parent] won't accept [I]!")
- return
- . = COMPONENT_SKIP_AFTERATTACK
- var/datum/callback/pc = precondition
- if(pc && !pc.Invoke(user))
- return
- var/material_amount = get_item_material_amount(I)
- if(!material_amount)
- to_chat(user, "[I] does not contain sufficient amounts of metal or glass to be accepted by [parent].")
- return
- if(!has_space(material_amount))
- to_chat(user, "[parent] is full. Please remove metal or glass from [parent] in order to insert more.")
- return
- user_insert(I, user)
-
-/datum/component/material_container/proc/user_insert(obj/item/I, mob/living/user)
- set waitfor = FALSE
- var/requested_amount
- if(istype(I, /obj/item/stack) && precise_insertion)
- var/atom/current_parent = parent
- var/obj/item/stack/S = I
- requested_amount = tgui_input_number(user, "How much do you want to insert?", "Inserting [S.singular_name]s", max_value = S.amount)
- if(isnull(requested_amount) || (requested_amount <= 0))
- return
- if(QDELETED(I) || QDELETED(user) || QDELETED(src) || parent != current_parent || user.incapacitated() || !in_range(current_parent, user) || user.l_hand != I && user.r_hand != I)
- return
- if(!user.drop_item())
- to_chat(user, "[I] is stuck to you and cannot be placed into [parent].")
- return
- var/inserted = insert_item(I, stack_amt = requested_amount)
- if(inserted)
- if(istype(I, /obj/item/stack))
- var/obj/item/stack/S = I
- to_chat(user, "You insert [inserted] [S.singular_name][inserted>1 ? "s" : ""] into [parent].")
- if(!QDELETED(I) && !user.put_in_hands(I))
- stack_trace("Warning: User could not put object back in hand during material container insertion, line [__LINE__]! This can lead to issues.")
- I.forceMove(user.drop_location())
- else
- to_chat(user, "You insert a material total of [inserted] into [parent].")
- qdel(I)
- if(after_insert)
- after_insert.Invoke(I.type, last_inserted_id, inserted)
- else
- user.put_in_active_hand(I)
-
-//For inserting an amount of material
-/datum/component/material_container/proc/insert_amount(amt, id = null)
- if(amt > 0 && has_space(amt))
- var/total_amount_saved = total_amount
- if(id)
- var/datum/material/M = materials[id]
- if(M)
- M.amount += amt
- total_amount += amt
- else
- for(var/i in materials)
- var/datum/material/M = materials[i]
- M.amount += amt
- total_amount += amt
- return (total_amount - total_amount_saved)
- return FALSE
-
-/datum/component/material_container/proc/insert_stack(obj/item/stack/S, amt, multiplier = 1)
- if(isnull(amt))
- amt = S.get_amount()
-
- if(amt <= 0)
- return FALSE
-
- if(amt > S.get_amount())
- amt = S.get_amount()
-
- var/material_amt = get_item_material_amount(S)
- if(!material_amt)
- return FALSE
-
- amt = min(amt, round(((max_amount - total_amount) / material_amt)))
- if(!amt)
- return FALSE
-
- last_inserted_id = insert_materials(S,amt * multiplier)
- S.use(amt)
- return amt
-
-/datum/component/material_container/proc/insert_item(obj/item/I, multiplier = 1, stack_amt)
- if(!I)
- return FALSE
- if(istype(I, /obj/item/stack))
- return insert_stack(I, stack_amt, multiplier)
-
- var/material_amount = get_item_material_amount(I)
- if(!material_amount || !has_space(material_amount))
- return FALSE
-
- last_inserted_id = insert_materials(I, multiplier)
- return material_amount
-
-/datum/component/material_container/proc/insert_materials(obj/item/I, multiplier = 1) //for internal usage only
- var/datum/material/M
- var/primary_mat
- var/max_mat_value = 0
- for(var/MAT in materials)
- M = materials[MAT]
- M.amount += I.materials[MAT] * multiplier
- total_amount += I.materials[MAT] * multiplier
- if(I.materials[MAT] > max_mat_value)
- primary_mat = MAT
- return primary_mat
-
-//For consuming material
-//mats is a list of types of material to use and the corresponding amounts, example: list(MAT_METAL=100, MAT_GLASS=200)
-/datum/component/material_container/proc/use_amount(list/mats, multiplier=1)
- if(!mats || !length(mats))
- return FALSE
-
- var/datum/material/M
- for(var/MAT in materials)
- M = materials[MAT]
- if(M.amount < (mats[MAT] * multiplier))
- return FALSE
-
- var/total_amount_save = total_amount
- for(var/MAT in materials)
- M = materials[MAT]
- M.amount -= mats[MAT] * multiplier
- total_amount -= mats[MAT] * multiplier
-
- return total_amount_save - total_amount
-
-
-/datum/component/material_container/proc/use_amount_type(amt, id)
- var/datum/material/M = materials[id]
- if(M)
- if(M.amount >= amt)
- M.amount -= amt
- total_amount -= amt
- return amt
- return FALSE
-
-/datum/component/material_container/proc/transer_amt_to(datum/component/material_container/T, amt, id)
- if((amt==0)||(!T)||(!id))
- return FALSE
- if(amt<0)
- return T.transer_amt_to(src, -amt, id)
- var/datum/material/M = materials[id]
-
- if(M)
- var/tr = min(amt, M.amount,T.can_insert_amount(amt, id))
- if(tr)
- use_amount_type(tr, id)
- T.insert_amount(tr, id)
- return tr
- return FALSE
-
-/datum/component/material_container/proc/can_insert_amount(amt, id)
- if(amt && id)
- var/datum/material/M = materials[id]
- if(M)
- if((total_amount + amt) <= max_amount)
- return amt
- else
- return (max_amount-total_amount)
-
-/datum/component/material_container/proc/can_use_amount(amt, id, list/mats)
- if(amt && id)
- var/datum/material/M = materials[id]
- if(M && M.amount >= amt)
- return TRUE
- else if(istype(mats))
- for(var/M in mats)
- if(materials[M] && (mats[M] <= materials[M]))
- continue
- else
- return FALSE
- return TRUE
- return FALSE
-
-//For spawning mineral sheets; internal use only
-/datum/component/material_container/proc/retrieve(sheet_amt, datum/material/M, target = null)
- if(!M.sheet_type)
- return 0
- if(sheet_amt <= 0)
- return 0
-
- if(!target)
- target = get_turf(parent)
- if(M.amount < (sheet_amt * MINERAL_MATERIAL_AMOUNT))
- sheet_amt = round(M.amount / MINERAL_MATERIAL_AMOUNT)
- var/count = 0
- while(sheet_amt > MAX_STACK_SIZE)
- new M.sheet_type(target, MAX_STACK_SIZE)
- count += MAX_STACK_SIZE
- use_amount_type(sheet_amt * MINERAL_MATERIAL_AMOUNT, M.id)
- sheet_amt -= MAX_STACK_SIZE
- if(sheet_amt >= 1)
- new M.sheet_type(target, sheet_amt)
- count += sheet_amt
- use_amount_type(sheet_amt * MINERAL_MATERIAL_AMOUNT, M.id)
- return count
-
-/datum/component/material_container/proc/retrieve_sheets(sheet_amt, id, target = null)
- if(materials[id])
- return retrieve(sheet_amt, materials[id], target)
- return FALSE
-
-/datum/component/material_container/proc/retrieve_amount(amt, id, target)
- return retrieve_sheets(amount2sheet(amt), id, target)
-
-/datum/component/material_container/proc/retrieve_all(target = null)
- var/result = 0
- var/datum/material/M
- for(var/MAT in materials)
- M = materials[MAT]
- result += retrieve_sheets(amount2sheet(M.amount), MAT, target)
- return result
-
-/datum/component/material_container/proc/has_space(amt = 0)
- return (total_amount + amt) <= max_amount
-
-/datum/component/material_container/proc/has_materials(list/mats, multiplier=1)
- if(!mats || !length(mats))
- return FALSE
-
- var/datum/material/M
- for(var/MAT in mats)
- M = materials[MAT]
- if(M.amount < (mats[MAT] * multiplier))
- return FALSE
- return TRUE
-
-/datum/component/material_container/proc/amount2sheet(amt)
- if(amt >= MINERAL_MATERIAL_AMOUNT)
- return round(amt / MINERAL_MATERIAL_AMOUNT)
- return FALSE
-
-/datum/component/material_container/proc/sheet2amount(sheet_amt)
- if(sheet_amt > 0)
- return sheet_amt * MINERAL_MATERIAL_AMOUNT
- return FALSE
-
-/datum/component/material_container/proc/amount(id)
- var/datum/material/M = materials[id]
- return M ? M.amount : 0
-
-//returns the amount of material relevant to this container;
-//if this container does not support glass, any glass in 'I' will not be taken into account
-/datum/component/material_container/proc/get_item_material_amount(obj/item/I)
- if(!istype(I))
- return FALSE
- if(!I.materials) // some objects have no materials and this will cause runtimes without this check
- return 0
- var/material_amount = 0
- for(var/MAT in materials)
- material_amount += I.materials[MAT]
- return material_amount
-
-
-/datum/material
- var/name
- var/amount = 0
- var/id = null
- var/sheet_type = null
- var/coin_type = null
- var/ore_type = null
-
-/datum/material/metal
- name = "Metal"
- id = MAT_METAL
- sheet_type = /obj/item/stack/sheet/metal
- coin_type = /obj/item/coin/iron
- ore_type = /obj/item/stack/ore/iron
-
-/datum/material/glass
- name = "Glass"
- id = MAT_GLASS
- sheet_type = /obj/item/stack/sheet/glass
- ore_type = /obj/item/stack/ore/glass
-
-/datum/material/silver
- name = "Silver"
- id = MAT_SILVER
- sheet_type = /obj/item/stack/sheet/mineral/silver
- coin_type = /obj/item/coin/silver
- ore_type = /obj/item/stack/ore/silver
-
-/datum/material/gold
- name = "Gold"
- id = MAT_GOLD
- sheet_type = /obj/item/stack/sheet/mineral/gold
- coin_type = /obj/item/coin/gold
- ore_type = /obj/item/stack/ore/gold
-
-/datum/material/diamond
- name = "Diamond"
- id = MAT_DIAMOND
- sheet_type = /obj/item/stack/sheet/mineral/diamond
- coin_type = /obj/item/coin/diamond
- ore_type = /obj/item/stack/ore/diamond
-
-/datum/material/uranium
- name = "Uranium"
- id = MAT_URANIUM
- sheet_type = /obj/item/stack/sheet/mineral/uranium
- coin_type = /obj/item/coin/uranium
- ore_type = /obj/item/stack/ore/uranium
-
-/datum/material/plasma
- name = "Solid Plasma"
- id = MAT_PLASMA
- sheet_type = /obj/item/stack/sheet/mineral/plasma
- coin_type = /obj/item/coin/plasma
- ore_type = /obj/item/stack/ore/plasma
-
-/datum/material/bluespace
- name = "Bluespace Mesh"
- id = MAT_BLUESPACE
- sheet_type = /obj/item/stack/ore/bluespace_crystal/refined
- ore_type = /obj/item/stack/ore/bluespace_crystal
-
-/datum/material/bananium
- name = "Bananium"
- id = MAT_BANANIUM
- sheet_type = /obj/item/stack/sheet/mineral/bananium
- coin_type = /obj/item/coin/clown
- ore_type = /obj/item/stack/ore/bananium
-
-/datum/material/tranquillite
- name = "Tranquillite"
- id = MAT_TRANQUILLITE
- sheet_type = /obj/item/stack/sheet/mineral/tranquillite
- coin_type = /obj/item/coin/mime
- ore_type = /obj/item/stack/ore/tranquillite
-
-/datum/material/titanium
- name = "Titanium"
- id = MAT_TITANIUM
- sheet_type = /obj/item/stack/sheet/mineral/titanium
- ore_type = /obj/item/stack/ore/titanium
-
-/datum/material/biomass
- name = "Biomass"
- id = MAT_BIOMASS
-
-/datum/material/plastic
- name = "Plastic"
- id = MAT_PLASTIC
- sheet_type = /obj/item/stack/sheet/plastic
diff --git a/code/datums/components/orbiter.dm b/code/datums/components/orbiter.dm
deleted file mode 100644
index 84fd4fab5b3c0..0000000000000
--- a/code/datums/components/orbiter.dm
+++ /dev/null
@@ -1,476 +0,0 @@
-/**
- * Code to handle atoms orbiting other atoms, following them as they move.
- * The basic logic is simple. We register a signal, COMSIG_MOVABLE_MOVED onto orbited atoms.
- * When the orbited atom moves, any ghosts orbiting them are moved to their turf.
- * We also register a MOVED signal onto the ghosts to cancel their orbit if they move themselves.
- * Complexities come in for items within other items (such as the NAD in a box in a backpack on an assistant pretending to be the captain),
- * as items in containers do **not** fire COMSIG_MOVABLE_MOVED when their container moves.
- *
- * The signal logic for items in containers is as follows:
- * Assume 1 is some item (for example, the NAD) and 2 is a box.
- * When 1 is added to 2, we register the typical orbit COMSIG_MOVABLE_MOVED onto 2.
- * This in essence makes 2 the "leader", the atom that ghosts follow in movement.
- * As 2 is moved around (say, dragged on the floor) ghosts will follow it.
- * We also register a new intermediate COMSIG_MOVABLE_MOVED signal onto 1 that tracks if 1 is moved.
- * Remember, this will only fire if 1 is moved around containers, since it's impossible for it to actually move on its own.
- * If 1 is moved out of 2, this signal makes 1 the new leader.
- * Lastly, we add a COMSIG_ATOM_EXITED to 2, which tracks if 1 is removed from 2.
- * This EXITED signal cleans up any orbiting signals on and above 2.
- * If 2 is added to another item, (say a backpack, 3)
- * 3 becomes the new leader
- * 2 becomes an intermediate
- * 1 is unchanged (but still carries the orbiter datum)
- *
- *
- * You may be asking yourself: is this overengineered?
- * In part, yes. However, MOVED signals don't get fired for items in containers, so this is
- * really the next best way.
- * Also, is this really optimal?
- * As much as it can be, I believe. This signal-shuffling will not happen for any item that is just moving from turf to turf,
- * which should apply to 95% of cases (read: ghosts orbiting mobs).
- */
-
-#define ORBIT_LOCK_IN (1<<0)
-#define ORBIT_FORCE_MOVE (1<<1)
-
-/datum/component/orbiter
- can_transfer = TRUE
- dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS
- /// List of observers orbiting the parent
- var/list/orbiter_list
- /// Cached transforms from before the orbiter started orbiting, to be restored on stopping their orbit
- var/list/orbit_data
-
-/// See atom/movable/proc/orbit for parameter definitions
-/datum/component/orbiter/Initialize(atom/movable/orbiter, radius = 10, clockwise = FALSE, rotation_speed = 20, rotation_segments = 36, pre_rotation = TRUE, lock_in_orbit = FALSE, force_move = FALSE, orbit_layer = FLY_LAYER)
- if(!istype(orbiter) || !isatom(parent) || isarea(parent))
- return COMPONENT_INCOMPATIBLE
-
- orbiter_list = list()
- orbit_data = list()
-
- begin_orbit(orbiter, radius, clockwise, rotation_speed, rotation_segments, pre_rotation, lock_in_orbit, force_move, orbit_layer)
-
-/datum/component/orbiter/RegisterWithParent()
- var/atom/target = parent
- register_signals(target)
-
-/datum/component/orbiter/UnregisterFromParent()
- var/atom/target = parent
- remove_signals(target)
-
-/datum/component/orbiter/Destroy()
- remove_signals(parent)
- for(var/i in orbiter_list)
- end_orbit(i)
- orbiter_list = null
- orbit_data = null
- return ..()
-
-/// See atom/movable/proc/orbit for parameter definitions
-/datum/component/orbiter/InheritComponent(datum/component/orbiter/new_comp, original, atom/movable/orbiter, radius, clockwise, rotation_speed, rotation_segments, pre_rotation, lock_in_orbit, force_move, orbit_layer)
- // No transfer happening
- if(!new_comp)
- begin_orbit(arglist(args.Copy(3)))
- return
-
- orbiter_list += new_comp.orbiter_list
- orbit_data += new_comp.orbit_data
- new_comp.orbiter_list = list()
- new_comp.orbit_data = list()
- QDEL_NULL(new_comp)
-
-/datum/component/orbiter/PostTransfer()
- if(!isatom(parent) || isarea(parent) || !get_turf(parent))
- return COMPONENT_INCOMPATIBLE
-
-/// See atom/movable/proc/orbit for parameter definitions
-/datum/component/orbiter/proc/begin_orbit(atom/movable/orbiter, radius, clockwise, rotation_speed, rotation_segments, pre_rotation, lock_in_orbit, force_move, orbit_layer)
- if(!istype(orbiter))
- return
-
- var/was_refreshing = FALSE
- var/atom/currently_orbiting = locateUID(orbiter.orbiting_uid)
-
- if(currently_orbiting)
- if(orbiter in orbiter_list)
- was_refreshing = TRUE
- end_orbit(orbiter, TRUE)
- else
- var/datum/component/orbiter/orbit_comp = currently_orbiting.GetComponent(/datum/component/orbiter)
- orbit_comp.end_orbit(orbiter)
-
- orbiter_list += orbiter
-
- // make sure orbits get cleaned up nicely if the parent qdels
- RegisterSignal(orbiter, COMSIG_PARENT_QDELETING, PROC_REF(end_orbit))
-
- var/orbit_flags = 0
- if(lock_in_orbit)
- orbit_flags |= ORBIT_LOCK_IN
- if(force_move)
- orbit_flags |= ORBIT_FORCE_MOVE
-
- orbiter.orbiting_uid = parent.UID()
- store_orbit_data(orbiter, orbit_flags)
-
- if(!lock_in_orbit)
- RegisterSignal(orbiter, COMSIG_MOVABLE_MOVED, PROC_REF(orbiter_move_react))
-
- // Head first!
- if(pre_rotation)
- var/matrix/M = matrix(orbiter.transform)
- var/pre_rot = 90
- if(!clockwise)
- pre_rot = -90
- M.Turn(pre_rot)
- orbiter.transform = M
-
- var/matrix/shift = matrix(orbiter.transform)
- shift.Translate(0,radius)
- orbiter.transform = shift
-
- orbiter.layer = orbit_layer
-
- SEND_SIGNAL(parent, COMSIG_ATOM_ORBIT_BEGIN, orbiter)
-
- // If we changed orbits, we didn't stop our rotation, and don't need to start it up again
- if(!was_refreshing)
- orbiter.SpinAnimation(rotation_speed, -1, clockwise, rotation_segments, parallel = FALSE)
-
- var/target_loc = get_turf(parent)
- var/current_loc = orbiter.loc
- if(force_move)
- orbiter.forceMove(target_loc)
- else
- orbiter.loc = target_loc
- // Setting loc directly doesn't fire COMSIG_MOVABLE_MOVED, so we need to do it ourselves
- SEND_SIGNAL(orbiter, COMSIG_MOVABLE_MOVED, current_loc, target_loc, null)
- orbiter.animate_movement = SYNC_STEPS
-
-/**
- * End the orbit and clean up our transformation.
- * If this removes the last atom orbiting us, then qdel ourselves.
- * Howver, if refreshing == TRUE, src will not be qdeleted if this leaves us with 0 orbiters.
- */
-/datum/component/orbiter/proc/end_orbit(atom/movable/orbiter, refreshing = FALSE)
- SIGNAL_HANDLER
-
- if(!(orbiter in orbiter_list))
- return
-
- if(orbiter)
- orbiter.animate_movement = SLIDE_STEPS
- if(!QDELETED(parent))
- SEND_SIGNAL(parent, COMSIG_ATOM_ORBIT_STOP, orbiter)
- SEND_SIGNAL(orbiter, COMSIG_ATOM_ORBITER_STOP, parent)
-
- orbiter.transform = get_cached_transform(orbiter)
- orbiter.layer = get_orbiter_layer(orbiter)
-
- UnregisterSignal(orbiter, COMSIG_MOVABLE_MOVED)
- UnregisterSignal(orbiter, COMSIG_PARENT_QDELETING)
-
- orbiter.orbiting_uid = null
-
- if(!refreshing)
- orbiter.SpinAnimation(0, 0, parallel = FALSE)
-
- // If it's null, still remove it from the list
- orbiter_list -= orbiter
- orbit_data -= orbiter
-
- if(!length(orbiter_list) && !QDELING(src) && !refreshing)
- qdel(src)
-
-/**
- * The actual implementation function of the move react.
- * **if you're trying to call this from a signal, call parent_move_react instead.**
- * This implementation is separate so the orbited atom's old location and new location can be passed in separately.
- */
-/datum/component/orbiter/proc/handle_parent_move(atom/movable/orbited, atom/old_loc, atom/new_loc, direction)
-
- if(new_loc == old_loc)
- return
-
- var/turf/new_turf = get_turf(new_loc)
- if(!new_turf)
- // don't follow someone to nullspace
- qdel(src)
-
- var/atom/cur_loc = new_loc
-
- // If something's only moving between turfs, don't bother changing signals, just move ghosts.
- // Honestly, that should apply to 95% of orbiting cases, which should be a nice optimization.
- if(!(isturf(old_loc) && isturf(new_loc)))
-
- // Clear any signals that may still exist upstream of where this object used to be
- remove_signals(old_loc)
- // ...and create a new signal hierarchy upstream of where the object is now.
- // cur_loc is the current "leader" atom
- cur_loc = register_signals(orbited)
-
- var/orbit_params
- var/orbiter_turf
- new_turf = get_turf(cur_loc)
- for(var/atom/movable/movable_orbiter in orbiter_list)
- orbiter_turf = get_turf(movable_orbiter)
- if(QDELETED(movable_orbiter) || orbiter_turf == new_turf)
- continue
-
- orbit_params = get_orbit_params(movable_orbiter)
-
- if(orbit_params & ORBIT_FORCE_MOVE)
- movable_orbiter.forceMove(new_turf)
- else
- var/orbiter_loc = movable_orbiter.loc
- movable_orbiter.loc = new_turf
- SEND_SIGNAL(movable_orbiter, COMSIG_MOVABLE_MOVED, orbiter_loc, new_turf, null)
-
- if(CHECK_TICK && new_turf != get_turf(movable_orbiter))
- // We moved again during the checktick, cancel current operation
- break
-/**
- * Signal handler for COMSIG_MOVABLE_MOVED. Special wrapper to handle the arguments that come from the signal.
- * If you want to call this directly, just call handle_parent_move.
- */
-/datum/component/orbiter/proc/parent_move_react(atom/movable/orbited, atom/old_loc, direction)
- set waitfor = FALSE // Transfer calls this directly and it doesnt care if the ghosts arent done moving
- handle_parent_move(orbited, old_loc, orbited.loc, direction)
-
-/**
-* Called when the orbiter themselves moves.
-*/
-/datum/component/orbiter/proc/orbiter_move_react(atom/movable/orbiter, atom/oldloc, direction)
- SIGNAL_HANDLER // COMSIG_MOVABLE_MOVED
-
- if(get_turf(orbiter) == get_turf(parent) || get_turf(orbiter) == get_turf(oldloc) || get_turf(orbiter) == oldloc || orbiter.loc == oldloc)
- return
-
- if(orbiter in orbiter_list)
- // Only end the spin animation when we're actually ending an orbit, not just changing targets
- orbiter.SpinAnimation(0, 0, parallel = FALSE)
- end_orbit(orbiter)
-
-/**
- * Remove all orbit-related signals in the object hierarchy above start.
- */
-/datum/component/orbiter/proc/remove_signals(atom/start)
- var/atom/cur_atom = start
- while(cur_atom && !isturf(cur_atom) && !(cur_atom in orbiter_list) && cur_atom != parent)
- UnregisterSignal(cur_atom, COMSIG_ATOM_EXITED)
- UnregisterSignal(cur_atom, COMSIG_MOVABLE_MOVED)
- cur_atom = cur_atom.loc
-
-/**
- * Register signals up the hierarchy, adding them to each parent (and their parent, and so on) up to the turf they're on.
- * The last atom in the hierarchy (the one whose .loc is the turf) becomes the leader, the atom ghosts will follow.
- * Registers on_intermediate_move for every non-leader atom so that if they move (removing them from the hierarchy), they will be set as the new leader.
- * Also registers on_remove_child to remove signals up the hierarchy when a child gets removed.
- * start: the first atom to register signals on. If start isn't inside of anything (or if its .loc is a turf), then it will become the leader.
- * Returns the new "leader", the atom that ghosts will follow.
- */
-/datum/component/orbiter/proc/register_signals(atom/start)
- if(isturf(start))
- return
- var/atom/cur_atom = start
- while(cur_atom.loc && !isturf(cur_atom.loc) && !(cur_atom.loc in orbiter_list))
- RegisterSignal(cur_atom, COMSIG_MOVABLE_MOVED, PROC_REF(on_intermediate_move), TRUE)
- RegisterSignal(cur_atom, COMSIG_ATOM_EXITED, PROC_REF(on_remove_child), TRUE)
- cur_atom = cur_atom.loc
-
- // Set the topmost atom (right before the turf) to be our new leader
- RegisterSignal(cur_atom, COMSIG_MOVABLE_MOVED, PROC_REF(parent_move_react), TRUE)
- RegisterSignal(cur_atom, COMSIG_ATOM_EXITED, PROC_REF(on_remove_child), TRUE)
- return cur_atom
-
-/**
- * Callback fired when an item is removed from a tracked atom.
- * Removes all orbit-related signals up its hierarchy and moves orbiters to the current child.
- * As this will never be called by a turf, this should not conflict with parent_move_react.
- */
-/datum/component/orbiter/proc/on_remove_child(datum/source, atom/movable/exiting, direction)
- SIGNAL_HANDLER // COMSIG_ATOM_EXITED
-
- // ensure the child is actually connected to the orbited atom
- if(!is_in_hierarchy(exiting) || (exiting in orbiter_list))
- return
- // Remove all signals upwards of the child and re-register them as the new parent
- remove_signals(exiting)
- RegisterSignal(exiting, COMSIG_MOVABLE_MOVED, PROC_REF(parent_move_react), TRUE)
- RegisterSignal(exiting, COMSIG_ATOM_EXITED, PROC_REF(on_remove_child), TRUE)
- var/new_loc = get_step(exiting, direction)
- INVOKE_ASYNC(src, PROC_REF(handle_parent_move), exiting, exiting.loc, new_loc)
-
-/**
- * Called when an intermediate (somewhere between the topmost and the orbited) atom moves.
- * This atom will now become the leader.
- */
-/datum/component/orbiter/proc/on_intermediate_move(atom/movable/tracked, atom/old_loc)
- SIGNAL_HANDLER // COMSIG_MOVABLE_MOVED
-
- // Make sure we don't trigger off an orbiter following!
- if(!is_in_hierarchy(tracked) || (tracked in orbiter_list))
- return
-
- remove_signals(old_loc) // TODO this doesn't work if something's removed from hand
- RegisterSignal(tracked, COMSIG_MOVABLE_MOVED, PROC_REF(parent_move_react), TRUE)
- RegisterSignal(tracked, COMSIG_ATOM_EXITED, PROC_REF(on_remove_child), TRUE)
- INVOKE_ASYNC(src, PROC_REF(handle_parent_move), tracked, old_loc, tracked.loc)
-
-/**
- * Returns TRUE if atom_to_find is transitively a parent of src.
- */
-/datum/component/orbiter/proc/is_in_hierarchy(atom/movable/atom_to_find)
- var/atom/check = parent
- while(check)
- if(check == atom_to_find)
- return TRUE
- check = check.loc
- return FALSE
-
-///////////////////////////////////
-/// orbit data helper functions
-///////////////////////////////////
-
-/**
- * Store a collection of data for an orbiter.
- * orbiter: orbiter atom itself. The orbiter's transform and layer at this point will be captured and cached.
- * orbit_flags: bitfield consisting of flags describing the orbit.
- */
-/datum/component/orbiter/proc/store_orbit_data(atom/movable/orbiter, orbit_flags)
- var/list/new_orbit_data = list(
- orbiter.transform, // cached transform
- orbit_flags, // params about the orbit
- orbiter.layer // cached layer from the orbiter
- )
- orbit_data[orbiter] = new_orbit_data
- return new_orbit_data
-
-/**
- * Get cached transform of the given orbiter.
- */
-/datum/component/orbiter/proc/get_cached_transform(atom/movable/orbiter)
- if(orbiter)
- var/list/orbit_params = orbit_data[orbiter]
- if(orbit_params)
- return orbit_params[1]
-
-/**
- * Get the given orbiter's orbit parameters bitfield
- */
-/datum/component/orbiter/proc/get_orbit_params(atom/movable/orbiter)
- if(orbiter)
- var/list/orbit_params = orbit_data[orbiter]
- if(orbit_params)
- return orbit_params[2]
-
-/**
- * Get the layer the given orbiter was on before they started orbiting
- */
-/datum/component/orbiter/proc/get_orbiter_layer(atom/movable/orbiter)
- if(orbiter)
- var/list/orbit_params = orbit_data[orbiter]
- if(orbit_params)
- return orbit_params[3]
-
-///////////////////////////////////
-// Atom procs/vars
-///////////////////////////////////
-
-/**
- * Set an atom to orbit around another one. This atom will follow the base atom's movement and rotate around it.
- *
- * orbiter: atom which will be doing the orbiting
- * radius: range to orbit at, radius of the circle formed by orbiting
- * clockwise: whether you orbit clockwise or anti clockwise
- * rotation_speed: how fast to rotate
- * rotation_segments: the resolution of the orbit circle, less = a more block circle, this can be used to produce hexagons (6 segments) triangles (3 segments), and so on, 36 is the best default.
- * pre_rotation: Chooses to rotate src 90 degress towards the orbit dir (clockwise/anticlockwise), useful for things to go "head first" like ghosts
- * lock_in_orbit: Forces src to always be on A's turf, otherwise the orbit cancels when src gets too far away (eg: ghosts)
- * force_move: If true, ghosts will be ForceMoved instead of having their .loc updated directly.
- * orbit_layer: layer that the orbiter should be on. The original layer will be restored on orbit end.
- */
-/atom/movable/proc/orbit(atom/A, radius = 10, clockwise = FALSE, rotation_speed = 20, rotation_segments = 36, pre_rotation = TRUE, lock_in_orbit = FALSE, force_move = FALSE, orbit_layer = FLY_LAYER)
- if(!istype(A) || !get_turf(A) || A == src)
- return
- // Adding a new component every time works as our dupe type will make us just inherit the new orbiter
- return A.AddComponent(/datum/component/orbiter, src, radius, clockwise, rotation_speed, rotation_segments, pre_rotation, lock_in_orbit, force_move, orbit_layer)
-
-/**
- * Stop this atom from orbiting whatever it's orbiting.
- */
-/atom/movable/proc/stop_orbit()
- var/atom/orbited = locateUID(orbiting_uid)
- if(!orbited)
- return
- var/datum/component/orbiter/C = orbited.GetComponent(/datum/component/orbiter)
- if(!C)
- return
- C.end_orbit(src)
-
-/**
- * Simple helper proc to get a list of everything directly orbiting the current atom, without checking contents, or null if nothing is.
- */
-/atom/proc/get_orbiters()
- var/datum/component/orbiter/C = GetComponent(/datum/component/orbiter)
- if(C && C.orbiter_list)
- return C.orbiter_list
- else
- return null
-
-/**
- * Remove an orbiter from the atom it's orbiting.
- */
-/atom/proc/remove_orbiter(atom/movable/orbiter)
- var/datum/component/orbiter/C = GetComponent(/datum/component/orbiter)
- if(C && C.orbiter_list && (orbiter in C.orbiter_list))
- C.end_orbit(orbiter)
-
-/**
- * Recursive getter method to return a list of all ghosts transitively orbiting this atom.
- * This will find orbiters either directly orbiting the followed atom, or any orbiters orbiting them (and so on).
- *
- * This shouldn't be passed arugments.
- */
-/atom/proc/get_orbiters_recursive(list/processed, source = TRUE)
- var/list/output = list()
- if(!processed)
- processed = list()
- if(src in processed)
- return output
-
- processed += src
- // Make sure we don't shadow outer orbiters
- for(var/atom/movable/atom_orbiter in get_orbiters())
- if(isobserver(atom_orbiter))
- output |= atom_orbiter
- output += atom_orbiter.get_orbiters_recursive(processed, source = FALSE)
- return output
-
-/**
- * Check every object in the hierarchy above ourselves for orbiters, and return the full list of them.
- * If an object is being held in a backpack, returns orbiters of the backpack, the person
- * If recursive == TRUE, this will also check recursively through any ghosts seen to make sure we find *everything* upstream
- */
-/atom/proc/get_orbiters_up_hierarchy(list/processed, source = TRUE, recursive = FALSE)
- var/list/output = list()
- if(!processed)
- processed = list()
- if((src in processed) || isturf(src))
- return output
-
- processed += src
- for(var/atom/movable/atom_orbiter in get_orbiters())
- if(isobserver(atom_orbiter))
- if(recursive)
- output += atom_orbiter.get_orbiters_recursive()
- output += atom_orbiter
-
- if(loc)
- output += loc.get_orbiters_up_hierarchy(processed, source = FALSE)
-
- return output
-
-#undef ORBIT_LOCK_IN
-#undef ORBIT_FORCE_MOVE
diff --git a/code/datums/components/paintable.dm b/code/datums/components/paintable.dm
deleted file mode 100644
index f0a7187149a8e..0000000000000
--- a/code/datums/components/paintable.dm
+++ /dev/null
@@ -1,31 +0,0 @@
-/datum/component/spraycan_paintable
- var/current_paint
-
-/datum/component/spraycan_paintable/Initialize()
- RegisterSignal(parent, COMSIG_ATTACK_BY, PROC_REF(Repaint))
-
-/datum/component/spraycan_paintable/Destroy()
- RemoveCurrentCoat()
- return ..()
-
-/datum/component/spraycan_paintable/proc/RemoveCurrentCoat()
- var/atom/A = parent
- A.remove_atom_colour(FIXED_COLOUR_PRIORITY, current_paint)
-
-/datum/component/spraycan_paintable/proc/Repaint(datum/source, obj/item/toy/crayon/spraycan/spraycan, mob/living/user)
- if(!istype(spraycan) || user.a_intent == INTENT_HARM)
- return
- . = COMPONENT_SKIP_AFTERATTACK
- if(spraycan.capped)
- to_chat(user, "Take the cap off first!")
- return
- if(spraycan.uses < 2)
- to_chat(user, "There is not enough paint in the can!")
- return
- RemoveCurrentCoat()
- var/colour = spraycan.colour
- current_paint = colour
- var/atom/A = parent
- A.add_atom_colour(colour, FIXED_COLOUR_PRIORITY)
- playsound(spraycan, 'sound/effects/spray.ogg', 5, TRUE, 5)
- to_chat(user, "You spray [spraycan] on [A], painting it.")
diff --git a/code/datums/components/radioactive.dm b/code/datums/components/radioactive.dm
deleted file mode 100644
index 6652b4ed6460f..0000000000000
--- a/code/datums/components/radioactive.dm
+++ /dev/null
@@ -1,118 +0,0 @@
-#define RAD_AMOUNT_LOW 50
-#define RAD_AMOUNT_MEDIUM 200
-#define RAD_AMOUNT_HIGH 500
-#define RAD_AMOUNT_EXTREME 1000
-
-/datum/component/radioactive
- dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS
-
- var/source
- ///the half-life measured in ticks
- var/hl3_release_date
- var/strength
- var/can_contaminate
-
-/datum/component/radioactive/Initialize(_strength = 0, _source, _half_life = RAD_HALF_LIFE, _can_contaminate = TRUE)
- if(!istype(parent, /atom))
- return COMPONENT_INCOMPATIBLE
- strength = _strength
- source = _source
- hl3_release_date = _half_life
- can_contaminate = _can_contaminate
- RegisterSignal(parent, COMSIG_PARENT_EXAMINE, PROC_REF(rad_examine))
- RegisterSignal(parent, COMSIG_ADMIN_DECONTAMINATE, PROC_REF(admin_decontaminate))
- if(isitem(parent))
- RegisterSignal(parent, COMSIG_ATTACK, PROC_REF(rad_attack))
- RegisterSignal(parent, COMSIG_ATTACK_OBJ, PROC_REF(rad_attack))
- if(strength > RAD_MINIMUM_CONTAMINATION)
- SSradiation.warn(src)
- //Let's make er glow
- //This relies on parent not being a turf or something. IF YOU CHANGE THAT, CHANGE THIS
- var/atom/movable/master = parent
- master.add_filter("rad_glow", 2, list("type" = "outline", "color" = "#39ff1430", "size" = 2))
- addtimer(CALLBACK(src, PROC_REF(glow_loop), master), rand(1, 19)) //Things should look uneven
- LAZYADD(SSradiation.all_radiations, src)
- START_PROCESSING(SSradiation, src)
-
-/datum/component/radioactive/Destroy()
- STOP_PROCESSING(SSradiation, src)
- LAZYREMOVE(SSradiation.all_radiations, src)
- var/atom/movable/master = parent
- master.remove_filter("rad_glow")
- return ..()
-
-/datum/component/radioactive/process()
- if(!prob(50))
- return
- radiation_pulse(parent, strength, RAD_DISTANCE_COEFFICIENT * 2, FALSE, can_contaminate)
- if(!hl3_release_date)
- return
- strength -= strength / hl3_release_date
- SSradiation.update_rad_cache(src)
- if(strength <= RAD_BACKGROUND_RADIATION)
- qdel(src)
- return PROCESS_KILL
-
-/datum/component/radioactive/proc/glow_loop(atom/movable/master)
- var/filter = master.get_filter("rad_glow")
- if(filter)
- animate(filter, alpha = 110, time = 15, loop = -1)
- animate(alpha = 40, time = 25)
-
-/datum/component/radioactive/InheritComponent(datum/component/C, i_am_original, _strength, _source, _half_life, _can_contaminate)
- if(!i_am_original)
- return
- if(!hl3_release_date) // Permanently radioactive things don't get to grow stronger
- return
- if(C)
- var/datum/component/radioactive/other = C
- strength = max(strength, other.strength)
- else
- strength = max(strength, _strength)
-
-/datum/component/radioactive/proc/rad_examine(datum/source, mob/user, list/out)
- SIGNAL_HANDLER
-
- var/atom/master = parent
-
- var/list/fragments = list()
- if(get_dist(master, user) <= 1)
- fragments += "The air around [master] feels warm"
- switch(strength)
- if(0 to RAD_AMOUNT_LOW)
- if(length(fragments))
- fragments += "."
- if(RAD_AMOUNT_LOW to RAD_AMOUNT_MEDIUM)
- fragments += "[length(fragments) ? " and [master.p_they()] " : "[master] "]feel[master.p_s()] weird to look at."
- if(RAD_AMOUNT_MEDIUM to RAD_AMOUNT_HIGH)
- fragments += "[length(fragments) ? " and [master.p_they()] " : "[master] "]seem[master.p_s()] to be glowing a bit."
- if(RAD_AMOUNT_HIGH to INFINITY) //At this level the object can contaminate other objects
- fragments += "[length(fragments) ? " and [master.p_they()] " : "[master] "]hurt[master.p_s()] to look at."
-
- if(length(fragments))
- out += "[fragments.Join()]"
-
-/datum/component/radioactive/proc/rad_attack(datum/source, atom/movable/target, mob/living/user)
- SIGNAL_HANDLER
-
- radiation_pulse(parent, strength / 20)
- target.rad_act(strength / 2)
- if(!hl3_release_date)
- return
- strength -= strength / hl3_release_date
-
-/datum/component/radioactive/proc/admin_decontaminate()
- SIGNAL_HANDLER
- . = TRUE
- if(ismob(parent))
- var/mob/M = parent
- M.radiation = 0
- if(ismob(source))
- var/mob/M = source
- M.radiation = 0
- qdel(src)
-
-#undef RAD_AMOUNT_LOW
-#undef RAD_AMOUNT_MEDIUM
-#undef RAD_AMOUNT_HIGH
-#undef RAD_AMOUNT_EXTREME
diff --git a/code/datums/components/shelved.dm b/code/datums/components/shelved.dm
deleted file mode 100644
index 3aa678d2efed7..0000000000000
--- a/code/datums/components/shelved.dm
+++ /dev/null
@@ -1,296 +0,0 @@
-#define MAX_SHELF_ITEMS 6
-
-/datum/component/shelver
- /// A list whose keys are a 4-tuple of (left, bottom, right, top) bounding boxes to position details.
- /// Position details include "x" and "y" as pixel offsets, and "layer" as appearance layer for a placed object.
- var/list/placement_zones = list()
- /// A list of slots, one per placement zone. Either empty, or containing the UID of the object in that place.
- var/list/used_places = list()
- /// A list of types which are are valid to place on this shelf.
- var/list/allowed_types = list()
- /// The default scale transformation for objects placed on the shelf.
- var/default_scale = 0.70
- /// The default rotation transformation for objects placed on the shelf.
- var/default_rotation = 0
- /// Whether objects auto-shelved by the component are placed in random order on the shelf.
- var/random_pickup_locations = FALSE
-
-/datum/component/shelver/Initialize(list/allowed_types_ = null, random_pickup_locations_ = FALSE)
- if(!isstructure(parent))
- return COMPONENT_INCOMPATIBLE
- used_places.len = length(placement_zones)
- if(length(allowed_types_))
- allowed_types += allowed_types_
- random_pickup_locations = random_pickup_locations_
-
-/datum/component/shelver/RegisterWithParent()
- RegisterSignal(parent, COMSIG_SHELF_ATTEMPT_PICKUP, PROC_REF(on_shelf_attempt_pickup))
- RegisterSignal(parent, COMSIG_ATTACK_BY, PROC_REF(on_attackby))
- RegisterSignal(parent, COMSIG_SHELF_ITEM_REMOVED, PROC_REF(on_shelf_item_removed))
- RegisterSignal(parent, COMSIG_SHELF_ADDED_ON_MAPLOAD, PROC_REF(prepare_autoshelf))
- RegisterSignal(parent, COMSIG_PARENT_EXAMINE, PROC_REF(on_examine))
-
-/datum/component/shelver/proc/on_examine(datum/source, mob/user, list/examine_list)
- SIGNAL_HANDLER // COMSIG_PARENT_EXAMINE
- var/list/results = list()
- for(var/uid in used_places)
- if(uid)
- var/obj/item/I = locateUID(uid)
- if(istype(I))
- results += "\a [I.name]"
-
- if(!length(results))
- return
-
- var/joined_results = english_list(results)
- examine_list += "It currently holds: [joined_results]."
-
-/datum/component/shelver/proc/prepare_autoshelf()
- SIGNAL_HANDLER // COMSIG_SHELF_ADDED_ON_MAPLOAD
-
- // See /obj/structure/closet/Initialize for explanation of
- // addtimer use here
- addtimer(CALLBACK(src, PROC_REF(shelf_items)), 0)
-
-/datum/component/shelver/proc/shelf_items()
- var/obj/structure/structure_parent = parent
-
- var/list/nearby_empty_tiles = list()
- for(var/turf/turf_in_view in view(2, get_turf(structure_parent)))
- if(!isfloorturf(turf_in_view))
- continue
- for(var/turf/potential_blockage as anything in get_line(get_turf(structure_parent), turf_in_view))
- if(!is_blocked_turf(potential_blockage, exclude_mobs = TRUE, excluded_objs = list(parent)))
- nearby_empty_tiles += turf_in_view
-
- var/itemcount = 1
- for(var/obj/item/I in structure_parent.loc)
- if(I.density || I.anchored || I == structure_parent)
- continue
- if(itemcount > MAX_SHELF_ITEMS)
- // If we can't fit it on the shelf, toss it somewhere nearby
- if(length(nearby_empty_tiles))
- var/turf/T = pick(nearby_empty_tiles)
- I.pixel_x = 0
- I.pixel_y = 0
- I.forceMove(T)
- if(!(SEND_SIGNAL(structure_parent, COMSIG_SHELF_ATTEMPT_PICKUP, I) & SHELF_PICKUP_FAILURE))
- itemcount++
-
-/datum/component/shelver/proc/on_shelf_attempt_pickup(datum/source, obj/item/to_add)
- SIGNAL_HANDLER // COMSIG_SHELF_ATTEMPT_PICKUP
-
- if(!istype(to_add))
- return SHELF_PICKUP_FAILURE
-
- var/free_slot = get_free_slot()
- if(!free_slot)
- return SHELF_PICKUP_FAILURE
-
- var/coords = placement_zones[free_slot]
- var/position_details = placement_zones[coords]
- add_item(to_add, free_slot, position_details)
-
-/datum/component/shelver/proc/get_free_slot()
- var/list/free_slots = list()
- for(var/i in 1 to length(used_places))
- if(!used_places[i])
- free_slots += i
-
- if(!length(free_slots))
- return
-
- if(random_pickup_locations)
- return pick(free_slots)
-
- return free_slots[1]
-
-/datum/component/shelver/proc/on_shelf_item_removed(datum/source, uid)
- SIGNAL_HANDLER // COMSIG_SHELF_ITEM_REMOVED
-
- for(var/i in 1 to length(used_places))
- if(used_places[i] == uid)
- used_places[i] = null
-
- var/obj/O = parent
- if(istype(O))
- O.update_appearance(UPDATE_ICON)
-
-/datum/component/shelver/proc/on_attackby(datum/source, obj/item/attacker, mob/user, params)
- SIGNAL_HANDLER // COMSIG_ATTACK_BY
-
- if(isrobot(user))
- return COMPONENT_SKIP_AFTERATTACK
- if(attacker.flags & ABSTRACT)
- return COMPONENT_SKIP_AFTERATTACK
- if(user.a_intent == INTENT_HARM)
- return
-
- if(length(allowed_types) && !(attacker.type in allowed_types))
- to_chat(user, "[attacker] won't fit on [parent]!")
- return COMPONENT_SKIP_AFTERATTACK
-
- var/list/PL = params2list(params)
- var/icon_x = text2num(PL["icon-x"])
- var/icon_y = text2num(PL["icon-y"])
-
- var/i = 0
- for(var/coords in placement_zones)
- i++
- if(icon_x >= coords[1] && icon_x <= coords[3] && icon_y >= coords[2] && icon_y <= coords[4])
- if(used_places[i])
- to_chat(user, "There's already something there on [parent].")
- return COMPONENT_SKIP_AFTERATTACK
-
- var/position_details = placement_zones[coords]
- if(user.drop_item())
- add_item(attacker, i, position_details)
- user.visible_message(
- "[user] places [attacker] on [parent].",
- "You place [attacker] on [parent].",
- )
- return COMPONENT_SKIP_AFTERATTACK
-
-/**
- * Add an item to the shelf.
- *
- * Arguments:
- * * to_add - The item to add. Adding will fail if not an `/obj/item`.
- * * placement_idx - The slot on the shelf to add the item to.
- * * position_details - A list containing the "x" and "y" pixel offsets of the position, and the "layer" the object will be set to, if applicable.
- */
-/datum/component/shelver/proc/add_item(obj/item/to_add, placement_idx, list/position_details)
- if(!istype(to_add))
- return
- to_add.forceMove(get_turf(parent))
- to_add.AddComponent(/datum/component/shelved, parent)
- to_add.pixel_x = position_details["x"]
- to_add.pixel_y = position_details["y"]
- to_add.appearance_flags |= PIXEL_SCALE
- if("layer" in position_details)
- to_add.layer = position_details["layer"]
- used_places[placement_idx] = to_add.UID()
- var/obj/O = parent
- if(istype(O))
- O.update_appearance(UPDATE_ICON)
-
- if(default_scale)
- to_add.transform *= default_scale
- if(default_rotation)
- to_add.transform = turn(to_add.transform, default_rotation)
-
- SEND_SIGNAL(to_add, COMSIG_SHELF_ITEM_ADDED, default_scale)
-
-/datum/component/shelver/basic_shelf
- placement_zones = list(
- // Bottom Shelf
- list(1, 1, 10, 16) = list("x" = -9, "y" = -5, "layer" = BELOW_OBJ_LAYER),
- list(11, 1, 20, 16) = list("x" = 0, "y" = -5, "layer" = BELOW_OBJ_LAYER),
- list(21, 1, 32, 16) = list("x" = 9, "y" = -5, "layer" = BELOW_OBJ_LAYER),
-
- // Top Shelf
- list(1, 17, 10, 32) = list("x" = -9, "y" = 9),
- list(11, 17, 20, 32) = list("x" = 0, "y" = 9),
- list(21, 17, 32, 32) = list("x" = 9, "y" = 9),
- )
-
-/datum/component/shelver/gun_rack
- placement_zones = list(
- list(1, 1, 10, 32) = list("x" = -8, "y" = -1),
- list(11, 1, 20, 32) = list("x" = 0, "y" = -1),
- list(21, 1, 32, 32) = list("x" = 8, "y" = -1),
- )
- default_scale = 0.80
- default_rotation = -90
-
-/// A component for items stored on shelves, propagated by [/datum/component/shelver] components.
-/datum/component/shelved
- /// The UID of the object acting as the shelf
- var/shelf_uid
- /// A copy of the shelved object's original transform, to restore after removing from the shelf.
- var/matrix/original_transform
- /// A copy of the shelved object's original layer, to restore after removing from the shelf.
- var/original_layer
- /// A copy of the shelved object's original appearance flags, to restore after removing from the shelf.
- var/original_appearance_flags
-
-/datum/component/shelved/Initialize(atom/shelf)
- if(!isobj(parent))
- return COMPONENT_INCOMPATIBLE
- var/obj/O = parent
-
- shelf_uid = shelf.UID()
- original_transform = O.transform
- original_layer = O.layer
- original_appearance_flags = O.appearance_flags
-
-/datum/component/shelved/RegisterWithParent()
- . = ..()
- RegisterSignal(parent, COMSIG_ITEM_PICKUP, PROC_REF(on_item_pickup))
- RegisterSignal(parent, COMSIG_MOVABLE_MOVED, PROC_REF(on_movable_moved))
- var/obj/shelf = locateUID(shelf_uid)
- if(shelf)
- RegisterSignal(shelf, COMSIG_MOVABLE_SHOVE_IMPACT, PROC_REF(on_movable_shove_impact))
- RegisterSignal(shelf, COMSIG_ATOM_HITBY, PROC_REF(on_atom_hitby))
- RegisterSignal(shelf, COMSIG_OBJ_DECONSTRUCT, PROC_REF(on_shelf_deconstruct))
-
-/datum/component/shelved/proc/on_shelf_deconstruct()
- SIGNAL_HANDLER // COMSIG_OBJ_DECONSTRUCT
- qdel(src)
-
-/datum/component/shelved/proc/on_item_pickup(obj/item/I, mob/user)
- SIGNAL_HANDLER // COMSIG_ITEM_PICKUP
- qdel(src)
-
-/// Generic handler for if anything moves us off our original shelf position, such as atmos pressure.
-/datum/component/shelved/proc/on_movable_moved()
- SIGNAL_HANDLER // COMSIG_MOVABLE_MOVED
- qdel(src)
-
-/datum/component/shelved/UnregisterFromParent()
- . = ..()
- var/obj/O = parent
- O.transform = original_transform
- O.layer = original_layer
- O.appearance_flags = original_appearance_flags
- O.pixel_x = 0
- O.pixel_y = 0
-
- var/obj/shelf = locateUID(shelf_uid)
- if(istype(shelf))
- UnregisterSignal(shelf, COMSIG_MOVABLE_SHOVE_IMPACT)
- UnregisterSignal(shelf, COMSIG_ATOM_HITBY)
-
- SEND_SIGNAL(shelf, COMSIG_SHELF_ITEM_REMOVED, parent.UID())
-
-/datum/component/shelved/proc/scatter()
- var/list/clear_turfs = list()
- var/obj/O = parent
- for(var/turf/T in orange(1, get_turf(O)))
- if(isfloorturf(T) && T != get_turf(O))
- clear_turfs |= T
-
- if(length(clear_turfs))
- var/obj/shelf = locateUID(shelf_uid)
- if(!isobj(shelf))
- // not sure what else we can do here to clean up after ourselves
- CRASH("received non-obj shelf with UID [shelf_uid]")
-
- var/shelf_name = shelf ? "flies off [shelf]" : "falls down"
- O.visible_message("[O] [shelf_name]!")
- O.throw_at(pick(clear_turfs), 2, 3)
- qdel(src)
-
-/datum/component/shelved/proc/on_movable_shove_impact(datum/source, atom/movable/target)
- SIGNAL_HANDLER // COMSIG_MOVABLE_SHOVE_IMPACT
- if(prob(50))
- scatter()
-
-/datum/component/shelved/proc/on_atom_hitby(datum/source, mob/living/carbon/human/hitby)
- SIGNAL_HANDLER // COMSIG_ATOM_HITBY
- if(!istype(hitby))
- return
- if(prob(50))
- scatter()
-
-#undef MAX_SHELF_ITEMS
diff --git a/code/datums/components/slippery.dm b/code/datums/components/slippery.dm
deleted file mode 100644
index 50f7d817f444a..0000000000000
--- a/code/datums/components/slippery.dm
+++ /dev/null
@@ -1,69 +0,0 @@
-/**
- * # Slip Component
- *
- * This is a component that can be applied to any movable atom (mob or obj).
- *
- * While the atom has this component, any human mob that walks over it will have a chance to slip.
- * Duration, tiles moved, and so on, depend on what variables are passed in when the component is added.
- *
- */
-/datum/component/slippery
- /// Text that gets displayed in the slip proc, i.e. "user slips on [description]"
- var/description
- /// The amount of knockdown to apply after slip.
- var/knockdown
- /// The chance that walking over the parent will slip you.
- var/slip_chance
- /// The amount of tiles someone will be moved after slip.
- var/slip_tiles
- /// TRUE If this slip can be avoided by walking.
- var/walking_is_safe
- /// FALSE if you want no slip shoes to make you immune to the slip
- var/slip_always
- /// The verb that players will see when someone slips on the parent. In the form of "You [slip_verb]ped on".
- var/slip_verb
- /// TRUE the player will only slip if the mob this datum is attached to is horizontal
- var/horizontal_required
- ///what we give to connect_loc by default, makes slippable mobs moving over us slip
- var/static/list/default_connections = list(
- COMSIG_ATOM_ENTERED = PROC_REF(slip),
- )
-
-/datum/component/slippery/Initialize(_description, _knockdown = 0, _slip_chance = 100, _slip_tiles = 0, _walking_is_safe = TRUE, _slip_always = FALSE, _slip_verb = "slip", _horizontal_required = FALSE)
- if(!isatom(parent))
- return COMPONENT_INCOMPATIBLE
-
- description = _description
- knockdown = max(0, _knockdown)
- slip_chance = max(0, _slip_chance)
- slip_tiles = max(0, _slip_tiles)
- walking_is_safe = _walking_is_safe
- slip_always = _slip_always
- slip_verb = _slip_verb
- horizontal_required = _horizontal_required
-
- add_connect_loc_behalf_to_parent()
-
-/datum/component/slippery/proc/add_connect_loc_behalf_to_parent()
- if(ismovable(parent))
- AddComponent(/datum/component/connect_loc_behalf, parent, default_connections)
-
-/**
- Called whenever the parent receives the `ATOM_ENTERED` signal.
-
- Calls the `victim`'s `slip()` proc with the component's variables as arguments.
- Additionally calls the parent's `after_slip()` proc on the `victim`.
-*/
-/datum/component/slippery/proc/slip(datum/source, mob/living/carbon/human/victim)
- SIGNAL_HANDLER // COMSIG_ATOM_ENTERED
-
- if(istype(victim) && !HAS_TRAIT(victim, TRAIT_FLYING))
- var/atom/movable/owner = parent
- if(!isturf(owner.loc))
- return
- if(isliving(owner))
- var/mob/living/mob_owner = owner
- if(horizontal_required && !IS_HORIZONTAL(mob_owner))
- return
- if(prob(slip_chance) && victim.slip(description, knockdown, slip_tiles, walking_is_safe, slip_always, slip_verb))
- owner.after_slip(victim)
diff --git a/code/datums/components/spawner.dm b/code/datums/components/spawner.dm
deleted file mode 100644
index eb845a9aba636..0000000000000
--- a/code/datums/components/spawner.dm
+++ /dev/null
@@ -1,66 +0,0 @@
-/datum/component/spawner
- var/mob_types = list(/mob/living/simple_animal/hostile/carp)
- var/spawn_time = 300 //30 seconds default
- var/list/spawned_mobs = list()
- var/spawn_delay = 0
- var/max_mobs = 5
- var/spawn_text = "emerges from"
- var/list/faction = list("mining")
-
- COOLDOWN_DECLARE(last_rally)
-
-
-/datum/component/spawner/Initialize(_mob_types, _spawn_time, _faction, _spawn_text, _max_mobs)
- if(_spawn_time)
- spawn_time=_spawn_time
- if(_mob_types)
- mob_types=_mob_types
- if(_faction)
- faction=_faction
- if(_spawn_text)
- spawn_text=_spawn_text
- if(_max_mobs)
- max_mobs=_max_mobs
-
- RegisterSignal(parent, list(COMSIG_PARENT_QDELETING), PROC_REF(stop_spawning))
- RegisterSignal(parent, COMSIG_SPAWNER_SET_TARGET, PROC_REF(rally_spawned_mobs))
- START_PROCESSING(SSprocessing, src)
-
-/datum/component/spawner/process()
- try_spawn_mob()
-
-
-/datum/component/spawner/proc/stop_spawning(force)
- STOP_PROCESSING(SSprocessing, src)
- for(var/mob/living/simple_animal/L in spawned_mobs)
- if(L.nest == src)
- L.nest = null
- spawned_mobs = null
-
-/datum/component/spawner/proc/try_spawn_mob()
- var/atom/P = parent
- if(length(spawned_mobs) >= max_mobs)
- return 0
- if(spawn_delay > world.time)
- return 0
- spawn_delay = world.time + spawn_time
- var/chosen_mob_type = pick(mob_types)
- var/mob/living/simple_animal/L = new chosen_mob_type(P.loc)
- L.admin_spawned = P.admin_spawned
- spawned_mobs += L
- L.nest = src
- L.faction = src.faction
- P.visible_message("[L] [spawn_text] [P].")
- P.on_mob_spawn(L)
-
-/datum/component/spawner/proc/rally_spawned_mobs(parent, mob/living/target)
- SIGNAL_HANDLER // COMSIG_SPAWNER_SET_TARGET
-
- if(!(COOLDOWN_FINISHED(src, last_rally) && length(spawned_mobs)))
- return
-
- // start the cooldown first, because a rallied mob might fire on
- // ourselves while this is happening, causing confusion
- COOLDOWN_START(src, last_rally, 30 SECONDS)
- for(var/mob/living/simple_animal/hostile/rallied as anything in spawned_mobs)
- INVOKE_ASYNC(rallied, TYPE_PROC_REF(/mob/living/simple_animal/hostile, aggro_fast), target)
diff --git a/code/datums/components/spooky.dm b/code/datums/components/spooky.dm
deleted file mode 100644
index 3ec7ab5bbe68e..0000000000000
--- a/code/datums/components/spooky.dm
+++ /dev/null
@@ -1,58 +0,0 @@
-/datum/component/spooky
- var/too_spooky = TRUE //will it spawn a new instrument?
-
-/datum/component/spooky/Initialize()
- RegisterSignal(parent, COMSIG_ATTACK, PROC_REF(spectral_attack))
-
-/datum/component/spooky/proc/spectral_attack(datum/source, mob/living/carbon/C, mob/user)
- if(ishuman(user)) //this weapon wasn't meant for mortals.
- var/mob/living/carbon/human/U = user
- if(!istype(U.dna.species, /datum/species/skeleton))
- U.apply_damage(35, STAMINA) //Extra Damage
- U.Jitter(70 SECONDS)
- U.SetStuttering(40 SECONDS)
- if(U.getStaminaLoss() > 95)
- to_chat(U, "Your ears weren't meant for this spectral sound.")
- spectral_change(U)
- return
-
- if(ishuman(C))
- var/mob/living/carbon/human/H = C
- if(istype(H.dna.species, /datum/species/skeleton))
- return //undeads are unaffected by the spook-pocalypse.
- C.Jitter(70 SECONDS)
- C.SetStuttering(40 SECONDS)
- if(!istype(H.dna.species, /datum/species/diona) && !istype(H.dna.species, /datum/species/machine) && !istype(H.dna.species, /datum/species/slime) && !istype(H.dna.species, /datum/species/golem) && !istype(H.dna.species, /datum/species/plasmaman))
- C.apply_damage(25, STAMINA) //boneless humanoids don't lose the will to live
- to_chat(C, "DOOT")
- spectral_change(H)
-
- else //the sound will spook monkeys.
- C.Jitter(30 SECONDS)
- C.SetStuttering(40 SECONDS)
-
-/datum/component/spooky/proc/spectral_change(mob/living/carbon/human/H, mob/user)
- if((H.getStaminaLoss() > 95) && (!istype(H.dna.species, /datum/species/diona) && !istype(H.dna.species, /datum/species/machine) && !istype(H.dna.species, /datum/species/slime) && !istype(H.dna.species, /datum/species/golem) && !istype(H.dna.species, /datum/species/plasmaman) && !istype(H.dna.species, /datum/species/skeleton)))
- H.Stun(40 SECONDS)
- H.set_species(/datum/species/skeleton) // Makes the OP skelly
- H.visible_message("[H] has given up on life as a mortal.")
- var/T = get_turf(H)
- if(too_spooky)
- if(prob(30))
- new/obj/item/instrument/saxophone/spectral(T)
- else if(prob(30))
- new/obj/item/instrument/trumpet/spectral(T)
- else if(prob(30))
- new/obj/item/instrument/trombone/spectral(T)
- else
- to_chat(H, "The spooky gods forgot to ship your instrument. Better luck next unlife.")
- to_chat(H, "You are the spooky skeleton!")
- to_chat(H, "A new life and identity has begun. Help your fellow skeletons into bringing out the spooky-pocalypse. You haven't forgotten your past life, and are still beholden to past loyalties.")
- change_name(H) //time for a new name!
-
-/datum/component/spooky/proc/change_name(mob/living/carbon/human/H)
- var/t = tgui_input_text(H, "Enter your new skeleton name", H.real_name, max_length = MAX_NAME_LEN)
- if(!t)
- t = "spooky skeleton"
- H.real_name = t
- H.name = t
diff --git a/code/datums/components/squeak.dm b/code/datums/components/squeak.dm
deleted file mode 100644
index 0583c1f8a4f20..0000000000000
--- a/code/datums/components/squeak.dm
+++ /dev/null
@@ -1,129 +0,0 @@
-/datum/component/squeak
- var/static/list/default_squeak_sounds = list('sound/items/toysqueak1.ogg'=1, 'sound/items/toysqueak2.ogg'=1, 'sound/items/toysqueak3.ogg'=1)
- var/list/override_squeak_sounds
- var/squeak_chance = 100
- var/volume = 30
-
- // This is so shoes don't squeak every step
- var/steps = 0
- var/step_delay = 1
-
- // This is to stop squeak spam from inhand usage
- var/last_use = 0
- var/use_delay = 20
-
- ///extra-range for this component's sound
- var/sound_extra_range = -1
- ///when sounds start falling off for the squeak
- var/sound_falloff_distance = SOUND_DEFAULT_FALLOFF_DISTANCE
- ///sound exponent for squeak. Defaults to 10 as squeaking is loud and annoying enough.
- var/sound_falloff_exponent = 10
-
- ///what we set connect_loc to if parent is an item
- var/static/list/item_connections = list(
- COMSIG_ATOM_ENTERED = PROC_REF(on_atom_entered),
- )
-
-
-/datum/component/squeak/Initialize(custom_sounds, volume_override, chance_override, step_delay_override, use_delay_override, squeak_on_move, extrarange, falloff_exponent, fallof_distance)
- if(!isatom(parent))
- return COMPONENT_INCOMPATIBLE
- RegisterSignal(parent, list(COMSIG_ATOM_ENTERED, COMSIG_ATOM_BLOB_ACT, COMSIG_ATOM_HULK_ATTACK, COMSIG_ATTACK_BY), PROC_REF(play_squeak))
- if(ismovable(parent))
- RegisterSignal(parent, list(COMSIG_MOVABLE_BUMP, COMSIG_MOVABLE_IMPACT), PROC_REF(play_squeak))
-
- AddComponent(/datum/component/connect_loc_behalf, parent, item_connections)
- RegisterSignal(parent, COMSIG_MOVABLE_DISPOSING, PROC_REF(disposing_react))
- if(squeak_on_move)
- RegisterSignal(parent, COMSIG_MOVABLE_MOVED, PROC_REF(play_squeak))
- if(isitem(parent))
- RegisterSignal(parent, list(COMSIG_ATTACK, COMSIG_ATTACK_OBJ, COMSIG_ITEM_HIT_REACT), PROC_REF(play_squeak))
- RegisterSignal(parent, COMSIG_ACTIVATE_SELF, PROC_REF(use_squeak))
- RegisterSignal(parent, COMSIG_ITEM_EQUIPPED, PROC_REF(on_equip))
- RegisterSignal(parent, COMSIG_ITEM_DROPPED, PROC_REF(on_drop))
- if(istype(parent, /obj/item/clothing/shoes))
- RegisterSignal(parent, COMSIG_SHOES_STEP_ACTION, PROC_REF(step_squeak))
-
- override_squeak_sounds = custom_sounds
- if(chance_override)
- squeak_chance = chance_override
- if(volume_override)
- volume = volume_override
- if(isnum(step_delay_override))
- step_delay = step_delay_override
- if(isnum(use_delay_override))
- use_delay = use_delay_override
- if(isnum(extrarange))
- sound_extra_range = extrarange
- if(isnum(falloff_exponent))
- sound_falloff_exponent = falloff_exponent
- if(isnum(fallof_distance))
- sound_falloff_distance = fallof_distance
-
-/datum/component/squeak/proc/play_squeak()
- if(ismob(parent))
- var/mob/M = parent
- if(M.stat == DEAD)
- return
- if(prob(squeak_chance))
- if(!override_squeak_sounds)
- playsound(parent, pickweight(default_squeak_sounds), volume, TRUE, sound_extra_range, sound_falloff_exponent, falloff_distance = sound_falloff_distance)
- else
- playsound(parent, pickweight(override_squeak_sounds), volume, TRUE, sound_extra_range, sound_falloff_exponent, falloff_distance = sound_falloff_distance)
-
-/datum/component/squeak/proc/step_squeak()
- if(steps > step_delay)
- play_squeak()
- steps = 0
- else
- steps++
-
-/datum/component/squeak/proc/on_atom_entered(datum/source, atom/movable/entered)
- if(istype(entered, /obj/effect))
- return
- if(ismob(entered))
- var/mob/M = entered
- if(HAS_TRAIT(M, TRAIT_FLYING))
- return
- if(isliving(entered))
- var/mob/living/L = M
- if(L.floating)
- return
- else if(isitem(entered))
- var/obj/item/I = source
- if(I.flags & ABSTRACT)
- return
- if(isprojectile(entered))
- var/obj/item/projectile/P = entered
- if(P.original != parent)
- return
- if(ismob(source))
- var/mob/M = source
- if(HAS_TRAIT(M, TRAIT_FLYING))
- return
- if(isliving(source))
- var/mob/living/L = M
- if(L.floating)
- return
- play_squeak()
-
-/datum/component/squeak/proc/use_squeak()
- if(last_use + use_delay < world.time)
- last_use = world.time
- play_squeak()
-
-/datum/component/squeak/proc/on_equip(datum/source, mob/equipper, slot)
- RegisterSignal(equipper, COMSIG_MOVABLE_DISPOSING, PROC_REF(disposing_react), TRUE)
-
-/datum/component/squeak/proc/on_drop(datum/source, mob/user)
- UnregisterSignal(user, COMSIG_MOVABLE_DISPOSING)
-
-// Disposal pipes related shit
-/datum/component/squeak/proc/disposing_react(datum/source, obj/structure/disposalholder/disposal_holder, obj/machinery/disposal/disposal_source)
- //We don't need to worry about unregistering this signal as it will happen for us automaticaly when the holder is qdeleted
- RegisterSignal(disposal_holder, COMSIG_ATOM_DIR_CHANGE, PROC_REF(holder_dir_change))
-
-/datum/component/squeak/proc/holder_dir_change(datum/source, old_dir, new_dir)
- //If the dir changes it means we're going through a bend in the pipes, let's pretend we bumped the wall
- if(old_dir != new_dir)
- play_squeak()
diff --git a/code/datums/components/sticky.dm b/code/datums/components/sticky.dm
deleted file mode 100644
index dcfeed36aa35e..0000000000000
--- a/code/datums/components/sticky.dm
+++ /dev/null
@@ -1,153 +0,0 @@
-// kinda like duct tape but not shit (just kidding, its shit but in a different way)
-/datum/component/sticky
- /// The atom we are stickied to
- var/atom/attached_to
- /// Our priority overlay put on top of attached_to
- var/icon/overlay
- /// Do we drop on attached_to's destroy? If not, we qdel
- var/drop_on_attached_destroy = FALSE
-
-/datum/component/sticky/Initialize(_drop_on_attached_destroy = FALSE)
- if(!isitem(parent))
- return COMPONENT_INCOMPATIBLE
-
- drop_on_attached_destroy = _drop_on_attached_destroy
-
-/datum/component/sticky/Destroy(force, silent)
- // we dont want the falling off visible message if this component is getting destroyed because parent is getting destroyed
- if(attached_to)
- if(!QDELETED(parent) && isitem(parent))
- var/obj/item/I = parent
- I.visible_message("[parent] falls off of [attached_to].")
- pick_up(parent)
-
- move_to_the_thing(parent, get_turf(parent))
- return ..()
-
-/datum/component/sticky/RegisterWithParent()
- RegisterSignal(parent, COMSIG_PRE_ATTACK, PROC_REF(stick_to_it))
- RegisterSignal(parent, COMSIG_MOVABLE_IMPACT, PROC_REF(stick_to_it_throwing))
-
-/datum/component/sticky/UnregisterFromParent()
- UnregisterSignal(parent, COMSIG_PRE_ATTACK)
- UnregisterSignal(parent, COMSIG_MOVABLE_IMPACT)
-
-/datum/component/sticky/proc/stick_to_it(obj/item/I, atom/target, mob/user, params)
- SIGNAL_HANDLER
- if(!in_range(I, target))
- return
- if(isstorage(target))
- var/obj/item/storage/S = target
- if(S.can_be_inserted(parent))
- return
- if(target.GetComponent(/datum/component/sticky))
- return
- if(!user.canUnEquip(I))
- return
-
- INVOKE_ASYNC(user, TYPE_PROC_REF(/mob, drop_item_to_ground), I)
-
- var/list/click_params = params2list(params)
- //Center the icon where the user clicked.
- if(!click_params || !click_params["icon-x"] || !click_params["icon-y"])
- return
-
- attached_to = target
- move_to_the_thing(parent)
- if(user)
- to_chat(user, "You attach [parent] to [attached_to].")
-
- overlay = icon(I.icon, I.icon_state)
- //Clamp it so that the icon never moves more than 16 pixels in either direction
- overlay.Shift(EAST, clamp(text2num(click_params["icon-x"]) - 16, -(world.icon_size/2), world.icon_size/2))
- overlay.Shift(NORTH, clamp(text2num(click_params["icon-y"]) - 16, -(world.icon_size/2), world.icon_size/2))
-
- attach_signals(I)
- return COMPONENT_CANCEL_ATTACK_CHAIN
-
-/datum/component/sticky/proc/stick_to_it_throwing(obj/item/thrown_item, atom/hit_target, throwingdatum, params)
- SIGNAL_HANDLER
- if(hit_target.GetComponent(/datum/component/sticky))
- return
-
- attached_to = hit_target
- move_to_the_thing(parent)
-
- overlay = icon(thrown_item.icon, thrown_item.icon_state)
- attach_signals(thrown_item)
-
-/datum/component/sticky/proc/attach_signals(obj/item/attached)
- attached_to.add_overlay(overlay, priority = TRUE)
- attached.invisibility = INVISIBILITY_ABSTRACT
-
- RegisterSignal(attached_to, COMSIG_HUMAN_MELEE_UNARMED_ATTACKBY, PROC_REF(pick_up))
- RegisterSignal(attached_to, COMSIG_PARENT_EXAMINE, PROC_REF(add_sticky_text))
- RegisterSignal(attached_to, COMSIG_PARENT_QDELETING, PROC_REF(on_attached_destroy))
- if(ismovable(attached_to))
- RegisterSignal(attached_to, COMSIG_MOVABLE_MOVED, PROC_REF(on_move))
- START_PROCESSING(SSobj, src)
-
-/datum/component/sticky/proc/pick_up(atom/A, mob/living/carbon/human/user)
- SIGNAL_HANDLER
- if(!attached_to)
- CRASH("/datum/component/sticky/proc/pick_up was called, but without an attached atom")
- if(user && user.a_intent != INTENT_GRAB)
- return
- if(user && user.get_active_hand())
- return
- attached_to.cut_overlay(overlay, priority = TRUE)
-
- var/obj/item/I = parent
- I.pixel_x = initial(I.pixel_x)
- I.pixel_y = initial(I.pixel_y)
- move_to_the_thing(parent)
- if(user)
- INVOKE_ASYNC(user, TYPE_PROC_REF(/mob, put_in_hands), I)
- to_chat(user, "You take [parent] off of [attached_to].")
-
-
- I.invisibility = initial(I.invisibility)
- UnregisterSignal(attached_to, list(COMSIG_HUMAN_MELEE_UNARMED_ATTACKBY, COMSIG_PARENT_EXAMINE, COMSIG_PARENT_QDELETING, COMSIG_MOVABLE_MOVED))
- STOP_PROCESSING(SSobj, src)
- attached_to = null
- return COMPONENT_CANCEL_ATTACK_CHAIN
-
-/datum/component/sticky/proc/add_sticky_text(datum/source, mob/user, list/examine_list)
- SIGNAL_HANDLER
- if(!in_range(user, attached_to))
- return
- examine_list += "There is [parent] attached to [source], grab [attached_to.p_them()] to remove it."
-
-/datum/component/sticky/proc/on_move(datum/source, oldloc, move_dir)
- SIGNAL_HANDLER
- if(!attached_to)
- CRASH("/datum/component/sticky/proc/on_move was called, but without an attached atom")
- move_to_the_thing(parent)
-
-/datum/component/sticky/process() // because sometimes the item can move inside something, like a crate
- if(!attached_to)
- return PROCESS_KILL
- move_to_the_thing(parent)
-
-/datum/component/sticky/proc/on_attached_destroy(datum/source)
- SIGNAL_HANDLER
- if(!drop_on_attached_destroy)
- qdel(parent)
- return
-
- // Cancel out these signals, if they even still exist. Just to be safe
- UnregisterSignal(attached_to, list(COMSIG_HUMAN_MELEE_UNARMED_ATTACKBY, COMSIG_PARENT_EXAMINE, COMSIG_PARENT_QDELETING, COMSIG_MOVABLE_MOVED))
-
- var/turf/T = get_turf(source)
- if(!T)
- T = get_turf(parent)
- move_to_the_thing(parent, T)
-
-/datum/component/sticky/proc/move_to_the_thing(obj/item/I, turf/target)
- if(!istype(I))
- return // only items should be able to have the sticky component
- if(!target)
- target = get_turf(attached_to)
- if(!target)
- CRASH("/datum/component/sticky/proc/move_to_the_thing was called without a viable target")
- INVOKE_ASYNC(I, TYPE_PROC_REF(/atom/movable, forceMove), target)
diff --git a/code/datums/components/surgery_initiator.dm b/code/datums/components/surgery_initiator.dm
deleted file mode 100644
index 7a7f5966c8975..0000000000000
--- a/code/datums/components/surgery_initiator.dm
+++ /dev/null
@@ -1,396 +0,0 @@
-
-/**
- * # Surgery Initiator
- *
- * Allows an item to start (or prematurely stop) a surgical operation.
- */
-/datum/component/surgery_initiator
- /// If present, this surgery TYPE will be attempted when the item is used.
- /// Useful for things like limb reattachments that don't need a scalpel.
- var/datum/surgery/forced_surgery
-
- /// If true, the initial step will be cancellable by just using the tool again. Should be FALSE for any tool that actually has a first surgery step.
- var/can_cancel_before_first = FALSE
-
- /// If true, can be used with a cautery in the off-hand to cancel a surgery.
- var/can_cancel = TRUE
-
- /// If true, can start surgery on someone while they're standing up.
- /// Seeing as how we really don't support this (yet), it's much nicer to selectively enable this if we want it.
- var/can_start_on_stander = FALSE
-
- /// Bitfield for the types of surgeries that this can start.
- /// Note that in cases where organs are missing, this will be ignored.
- /// Also, note that for anything sharp, SURGERY_INITIATOR_ORGANIC should be set as well.
- var/valid_starting_types = SURGERY_INITIATOR_ORGANIC
-
- /// How effective this is at preventing infections.
- /// 0 = preventing nothing, 1 = preventing any infection
- var/germ_prevention_quality = 0
-
- /// The sound to play when starting surgeries
- var/surgery_start_sound = null
-
- // Replace any other surgery initiator
- dupe_type = /datum/component/surgery_initiator
-
-/**
- * Create a new surgery initiating component.
- *
- * Arguments:
- * * forced_surgery - (optional) the surgery that will be started when the parent is used on a mob.
- */
-/datum/component/surgery_initiator/Initialize(datum/surgery/forced_surgery)
- . = ..()
- if(!isitem(parent))
- return COMPONENT_INCOMPATIBLE
-
- src.forced_surgery = forced_surgery
-
-/datum/component/surgery_initiator/RegisterWithParent()
- RegisterSignal(parent, COMSIG_ATTACK, PROC_REF(initiate_surgery_moment))
- RegisterSignal(parent, COMSIG_ATOM_UPDATE_SHARPNESS, PROC_REF(on_parent_sharpness_change))
- RegisterSignal(parent, COMSIG_PARENT_EXAMINE_MORE, PROC_REF(on_parent_examine_more))
-
-/datum/component/surgery_initiator/UnregisterFromParent()
- UnregisterSignal(parent, COMSIG_ATTACK)
- UnregisterSignal(parent, COMSIG_ATOM_UPDATE_SHARPNESS)
- UnregisterSignal(parent, COMSIG_PARENT_EXAMINE_MORE)
-
-/datum/component/surgery_initiator/proc/on_parent_examine_more(datum/source, mob/user, list/examine_list)
- SIGNAL_HANDLER // COMSIG_PARENT_EXAMINE_MORE
- examine_list += "You can use this on someone who is laying down to begin surgery on them."
-
-/// Keep tabs on the attached item's sharpness.
-/// This component gets added in atoms when they're made sharp as well.
-/datum/component/surgery_initiator/proc/on_parent_sharpness_change()
- SIGNAL_HANDLER // COMSIG_ATOM_UPDATE_SHARPNESS
- var/obj/item/P = parent
- if(!P.sharp)
- UnlinkComponent()
- RemoveComponent()
-
-/// Does the surgery initiation.
-/datum/component/surgery_initiator/proc/initiate_surgery_moment(datum/source, atom/target, mob/user)
- SIGNAL_HANDLER // COMSIG_ATTACK
- if(!isliving(user))
- return
- var/mob/living/L = target
- if(!user.Adjacent(target))
- return
- if(user.a_intent != INTENT_HELP)
- return
- if(!IS_HORIZONTAL(L) && !can_start_on_stander)
- return
- if(IS_HORIZONTAL(L) && !on_operable_surface(L))
- return
- if(iscarbon(target))
- var/mob/living/carbon/C = target
- var/obj/item/organ/external/affected = C.get_organ(user.zone_selected)
- if(affected)
- if((affected.status & ORGAN_ROBOT) && !(valid_starting_types & SURGERY_INITIATOR_ROBOTIC))
- return
- if(!(affected.status & ORGAN_ROBOT) && !(valid_starting_types & SURGERY_INITIATOR_ORGANIC))
- return
-
- if(L.has_status_effect(STATUS_EFFECT_SUMMONEDGHOST))
- to_chat(user, "You realise that a ghost probably doesn't have any useful organs.")
- return //no cult ghost surgery please
- INVOKE_ASYNC(src, PROC_REF(do_initiate_surgery_moment), target, user)
- // This signal is actually part of the attack chain, so it needs to return true to stop it
- return TRUE
-
-/// Meat and potatoes of starting surgery.
-/datum/component/surgery_initiator/proc/do_initiate_surgery_moment(mob/living/target, mob/user)
- var/datum/surgery/current_surgery
-
- // Check if we've already got a surgery on our target zone
- for(var/i_one in target.surgeries)
- var/datum/surgery/surgeryloop = i_one
- if(surgeryloop.location == user.zone_selected)
- current_surgery = surgeryloop
- break
-
- if(!isnull(current_surgery) && !current_surgery.step_in_progress)
- var/datum/surgery_step/current_step = current_surgery.get_surgery_step()
- if(current_step.try_op(user, target, user.zone_selected, parent, current_surgery) == SURGERY_INITIATE_SUCCESS)
- return
- if(istype(parent, /obj/item/scalpel/laser/manager/debug))
- return
- if(attempt_cancel_surgery(current_surgery, target, user))
- return
-
- if(!isnull(current_surgery) && current_surgery.step_in_progress)
- return
-
- var/list/available_surgeries = get_available_surgeries(user, target)
-
- var/datum/surgery/procedure
-
- if(!length(available_surgeries))
- if(IS_HORIZONTAL(target))
- to_chat(user, "There aren't any surgeries you can perform there right now.")
- else
- to_chat(user, "You can't perform any surgeries there while [target] is standing.")
- return
-
- // if we have a surgery that should be performed regardless with this item,
- // make sure it's available to be done
- if(forced_surgery)
- for(var/datum/surgery/S in available_surgeries)
- if(istype(S, forced_surgery))
- procedure = S
- break
- else
- procedure = tgui_input_list(user, "Begin which procedure?", "Surgery", available_surgeries)
-
- if(!procedure)
- return
-
- if(!on_surgery_selection(user, target, procedure))
- return
-
- return try_choose_surgery(user, target, procedure)
-
-/datum/component/surgery_initiator/proc/on_surgery_selection(mob/user, mob/living/target, datum/surgery/target_surgery)
- return TRUE
-
-/datum/component/surgery_initiator/proc/get_available_surgeries(mob/user, mob/living/target)
- var/list/available_surgeries = list()
- for(var/datum/surgery/surgery in GLOB.surgeries_list)
- if(surgery.abstract && !istype(surgery, forced_surgery)) // no choosing abstract surgeries, though they can be forced
- continue
- if(!is_type_in_list(target, surgery.target_mobtypes))
- continue
- if(!target.can_run_surgery(surgery, user))
- continue
-
- available_surgeries |= surgery
-
- return available_surgeries
-
-/datum/component/surgery_initiator/proc/cancel_unstarted_surgery_fluff(datum/surgery/the_surgery, mob/living/patient, mob/user, selected_zone)
- user.visible_message(
- "[user] stops the surgery on [patient]'s [parse_zone(selected_zone)] with [parent].",
- "You stop the surgery on [patient]'s [parse_zone(selected_zone)] with [parent].",
- )
-
-/// Does the surgery de-initiation.
-/datum/component/surgery_initiator/proc/attempt_cancel_surgery(datum/surgery/the_surgery, mob/living/patient, mob/user)
- var/selected_zone = user.zone_selected
- /// We haven't even started yet. Any surgery can be cancelled at this point.
- if(the_surgery.step_number == 1)
- patient.surgeries -= the_surgery
- cancel_unstarted_surgery_fluff(the_surgery, patient, user, selected_zone)
-
- qdel(the_surgery)
- return TRUE
-
- if(!the_surgery.can_cancel)
- return
-
- // Don't make a forced surgery implement cancel a surgery.
- if(istype(the_surgery, forced_surgery))
- return
-
- var/obj/item/close_tool
- var/obj/item/other_hand = user.get_inactive_hand()
-
- var/is_robotic = !the_surgery.requires_organic_bodypart
- var/datum/surgery_step/chosen_close_step
- var/skip_surgery = FALSE // if true, don't even run an operation, just end the surgery.
-
- if(!the_surgery.requires_bodypart)
- // special behavior here; if it doesn't require a bodypart just check if there's a limb there or not.
- // this is a little bit gross and I do apologize
- if(iscarbon(patient))
- var/mob/living/carbon/C = patient
- var/obj/item/organ/external/affected = C.get_organ(user.zone_selected)
- if(!affected)
- skip_surgery = TRUE
-
- else
- // uh there's no reason this should be hit but let's be safe LOL
- skip_surgery = TRUE
-
- if(!skip_surgery)
- if(is_robotic)
- chosen_close_step = new /datum/surgery_step/robotics/external/close_hatch/premature()
- else
- chosen_close_step = new /datum/surgery_step/generic/cauterize/premature()
-
- if(skip_surgery)
- close_tool = user.get_active_hand() // sure, just something so that it isn't null
- else if(isrobot(user))
- if(!is_robotic)
- // borgs need to be able to finish surgeries with just the laser scalpel, no special checks here.
- close_tool = parent
- else
- close_tool = locate(/obj/item/crowbar) in user.get_all_slots()
- if(!close_tool)
- to_chat(user, "You need a prying tool in an inactive slot to stop the surgery!")
- return TRUE
-
- else if(other_hand)
- for(var/key in chosen_close_step.allowed_tools)
- if(ispath(key) && istype(other_hand, key) || other_hand.tool_behaviour == key)
- close_tool = other_hand
- break
-
- if(!close_tool)
- to_chat(user, "You need a [is_robotic ? "prying": "cauterizing"] tool in your inactive hand to stop the surgery!")
- return TRUE
-
- if(skip_surgery || chosen_close_step.try_op(user, patient, selected_zone, close_tool, the_surgery) == SURGERY_INITIATE_SUCCESS)
- // logging in case people wonder why they're cut up inside
- log_attack(user, patient, "Prematurely finished \a [the_surgery] surgery.")
- qdel(chosen_close_step)
- patient.surgeries -= the_surgery
- qdel(the_surgery)
-
- // always return TRUE here so we don't continue the surgery chain and try to start a new surgery with our tool.
- return TRUE
-
-/datum/component/surgery_initiator/proc/can_start_surgery(mob/user, mob/living/target)
- if(!user.Adjacent(target))
- return FALSE
-
- // The item was moved somewhere else
- if(!(parent in user))
- to_chat(user, "You cannot start an operation if you aren't holding the tool anymore.")
- return FALSE
-
- // While we were choosing, another surgery was started at the same location
- for(var/datum/surgery/surgery in target.surgeries)
- if(surgery.location == user.zone_selected)
- to_chat(user, "There's already another surgery in progress on their [parse_zone(surgery.location)].")
- return FALSE
-
- return TRUE
-
-/datum/component/surgery_initiator/proc/try_choose_surgery(mob/user, mob/living/target, datum/surgery/surgery)
- if(!can_start_surgery(user, target))
- return
-
- var/obj/item/organ/affecting_limb
-
- var/selected_zone = user.zone_selected
-
- if(iscarbon(target))
- var/mob/living/carbon/carbon_target = target
- affecting_limb = carbon_target.get_organ(check_zone(selected_zone))
-
- if(surgery.requires_bodypart == isnull(affecting_limb))
- if(surgery.requires_bodypart)
- to_chat(user, "The patient has no [parse_zone(selected_zone)]!")
- else
- to_chat(user, "The patient has \a [parse_zone(selected_zone)]!")
-
- return
-
- if(!isnull(affecting_limb) && (surgery.requires_organic_bodypart && affecting_limb.is_robotic()) || (!surgery.requires_organic_bodypart && !affecting_limb.is_robotic()))
- to_chat(user, "That's not the right type of limb for this operation!")
- return
-
- if(surgery.lying_required && !on_operable_surface(target))
- to_chat(user, "Patient must be lying down for this operation.")
- return
-
- if(target == user && !surgery.self_operable)
- to_chat(user, "You can't perform that operation on yourself!")
- return
-
- if(!surgery.can_start(user, target))
- to_chat(user, "Can't start the surgery!")
- return
-
- if(surgery_needs_exposure(surgery, target))
- to_chat(user, "You have to expose [target.p_their()] [parse_zone(selected_zone)] first!")
- return
-
- var/datum/surgery/procedure = new surgery.type(target, selected_zone, affecting_limb)
-
-
- RegisterSignal(procedure, COMSIG_SURGERY_BLOOD_SPLASH, PROC_REF(on_blood_splash))
-
- procedure.germ_prevention_quality = germ_prevention_quality
-
- show_starting_message(user, target, procedure)
-
- log_attack(user, target, "operated on (OPERATION TYPE: [procedure.name]) (TARGET AREA: [selected_zone])")
-
- return procedure
-
-/datum/component/surgery_initiator/proc/surgery_needs_exposure(datum/surgery/surgery, mob/living/target)
- return !surgery.ignore_clothes && !get_location_accessible(target, target.zone_selected)
-
-/// Handle to allow for easily overriding the message shown
-/datum/component/surgery_initiator/proc/show_starting_message(mob/user, mob/living/target, datum/surgery/procedure)
- user.visible_message(
- "[user] holds [parent] over [target]'s [parse_zone(user.zone_selected)] to prepare for surgery.",
- "You hold [parent] over [target]'s [parse_zone(user.zone_selected)] to prepare for \an [procedure.name].",
- )
-
-/datum/component/surgery_initiator/proc/on_prevent_germs()
- SIGNAL_HANDLER //
- return
-
-/datum/component/surgery_initiator/proc/on_blood_splash()
- SIGNAL_HANDLER // COMSIG_SURGERY_BLOOD_SPLASH
- return
-
-/datum/component/surgery_initiator/limb
- can_cancel = FALSE // don't let a leg cancel a surgery
-
-/datum/component/surgery_initiator/robo
- valid_starting_types = SURGERY_INITIATOR_ROBOTIC
-
-/datum/component/surgery_initiator/robo/sharp
- valid_starting_types = SURGERY_INITIATOR_ORGANIC | SURGERY_INITIATOR_ROBOTIC
-
-/datum/component/surgery_initiator/cloth
- can_cancel = FALSE
- surgery_start_sound = "rustle"
-
-/datum/component/surgery_initiator/cloth/show_starting_message(mob/user, mob/living/target, datum/surgery/procedure)
- user.visible_message(
- "[user] drapes [parent] over [target]'s [parse_zone(user.zone_selected)] to prepare for surgery.",
- "You drape [parent] over [target]'s [parse_zone(user.zone_selected)] to prepare for \an [procedure.name].",
- )
-
-/datum/component/surgery_initiator/cloth/try_choose_surgery(mob/user, mob/living/target, datum/surgery/surgery)
- var/datum/surgery/new_procedure = ..()
- if(!istype(new_procedure))
- return
-
- new_procedure.started_with_drapes = TRUE
-
-/datum/component/surgery_initiator/cloth/on_surgery_selection(mob/user, mob/living/target, datum/surgery/target_surgery)
- user.visible_message(
- "[user] starts to apply [parent] onto [target].",
- "You start to apply [parent] onto [target].",
- )
-
- if(!isnull(surgery_start_sound))
- playsound(src, surgery_start_sound, 50, TRUE)
-
- playsound(src, surgery_start_sound)
- if(!do_after_once(user, 3 SECONDS, TRUE, target))
- user.visible_message(
- "[user] stops applying [parent] onto [target].",
- "You stop applying [parent] onto [target]."
- )
- return
-
-
- if(!isnull(surgery_start_sound))
- playsound(src, surgery_start_sound, 50, TRUE)
-
- return TRUE
-
-/datum/component/surgery_initiator/cloth/on_blood_splash(datum/surgery, mob/user, mob/target, zone, obj/item/tool)
- if(prob(90 * germ_prevention_quality))
- target.visible_message("Blood splashes onto the dressing.")
- var/obj/item/I = parent // safety: this component can only go onto an item
- I.add_mob_blood(target)
- return COMPONENT_BLOOD_SPLASH_HANDLED
diff --git a/code/datums/components/swarming.dm b/code/datums/components/swarming.dm
deleted file mode 100644
index 608359d49089c..0000000000000
--- a/code/datums/components/swarming.dm
+++ /dev/null
@@ -1,59 +0,0 @@
-/datum/component/swarming
- var/offset_x = 0
- var/offset_y = 0
- var/is_swarming = FALSE
- var/list/swarm_members = list()
- var/static/list/swarming_loc_connections = list(
- COMSIG_ATOM_EXITED = PROC_REF(leave_swarm),
- COMSIG_ATOM_ENTERED = PROC_REF(join_swarm)
- )
-
-
-/datum/component/swarming/Initialize(max_x = 24, max_y = 24)
- if(!ismovable(parent))
- return COMPONENT_INCOMPATIBLE
- offset_x = rand(-max_x, max_x)
- offset_y = rand(-max_y, max_y)
-
- AddComponent(/datum/component/connect_loc_behalf, parent, swarming_loc_connections)
-
-/datum/component/swarming/Destroy()
- for(var/other in swarm_members)
- var/datum/component/swarming/other_swarm = other
- other_swarm.swarm_members -= src
- if(!length(other_swarm.swarm_members))
- other_swarm.unswarm()
- swarm_members = null
- return ..()
-
-/datum/component/swarming/proc/join_swarm(datum/source, atom/movable/arrived, atom/old_loc, list/atom/old_locs)
- var/datum/component/swarming/other_swarm = arrived.GetComponent(/datum/component/swarming)
- if(!other_swarm)
- return
- swarm()
- swarm_members |= other_swarm
- other_swarm.swarm()
- other_swarm.swarm_members |= src
-
-/datum/component/swarming/proc/leave_swarm(datum/source, atom/movable/gone, direction)
- var/datum/component/swarming/other_swarm = gone.GetComponent(/datum/component/swarming)
- if(!other_swarm || !(other_swarm in swarm_members))
- return
- swarm_members -= other_swarm
- if(!length(swarm_members))
- unswarm()
- other_swarm.swarm_members -= src
- if(!length(other_swarm.swarm_members))
- other_swarm.unswarm()
-
-/datum/component/swarming/proc/swarm()
- var/atom/movable/owner = parent
- if(!is_swarming)
- is_swarming = TRUE
- animate(owner, pixel_x = owner.pixel_x + offset_x, pixel_y = owner.pixel_y + offset_y, time = 2)
-
-/datum/component/swarming/proc/unswarm()
- var/atom/movable/owner = parent
- if(is_swarming)
- animate(owner, pixel_x = owner.pixel_x - offset_x, pixel_y = owner.pixel_y - offset_y, time = 2)
- is_swarming = FALSE
diff --git a/code/datums/datacore.dm b/code/datums/datacore.dm
deleted file mode 100644
index 06832bfd3a1f2..0000000000000
--- a/code/datums/datacore.dm
+++ /dev/null
@@ -1,596 +0,0 @@
-/datum/datacore
- var/list/medical = list()
- var/list/general = list()
- var/list/security = list()
- //This list tracks characters spawned in the world and cannot be modified in-game. Currently referenced by respawn_character().
- var/list/locked = list()
-
-/*
-We can't just insert in HTML into the TGUI so we need the raw data to play with.
-Instead of creating this list over and over when someone leaves their PDA open to the page
-we'll only update it when it changes. The PDA_Manifest global list is zeroed out upon any change
-using /datum/datacore/proc/manifest_inject(), or manifest_insert()
-*/
-
-GLOBAL_LIST_EMPTY(PDA_Manifest)
-
-/datum/datacore/proc/get_manifest_json()
- if(length(GLOB.PDA_Manifest))
- return
- var/heads[0]
- var/sec[0]
- var/eng[0]
- var/med[0]
- var/sci[0]
- var/ser[0]
- var/sup[0]
- var/bot[0]
- var/misc[0]
- for(var/datum/data/record/t in GLOB.data_core.general)
- var/name = sanitize(t.fields["name"])
- var/rank = sanitize(t.fields["rank"])
- var/real_rank = t.fields["real_rank"]
-
- var/isactive = t.fields["p_stat"]
- var/department = 0
- var/depthead = 0 // Department Heads will be placed at the top of their lists.
- if(real_rank in GLOB.command_positions)
- heads[++heads.len] = list("name" = name, "rank" = rank, "active" = isactive)
- department = 1
- depthead = 1
- if(rank == "Captain" && length(heads) != 1)
- heads.Swap(1, length(heads))
-
- if(real_rank in GLOB.active_security_positions)
- sec[++sec.len] = list("name" = name, "rank" = rank, "active" = isactive)
- department = 1
- if(depthead && length(sec) != 1)
- sec.Swap(1, length(sec))
-
- if(real_rank in GLOB.engineering_positions)
- eng[++eng.len] = list("name" = name, "rank" = rank, "active" = isactive)
- department = 1
- if(depthead && length(eng) != 1)
- eng.Swap(1, length(eng))
-
- if(real_rank in GLOB.medical_positions)
- med[++med.len] = list("name" = name, "rank" = rank, "active" = isactive)
- department = 1
- if(depthead && length(med) != 1)
- med.Swap(1, length(med))
-
- if(real_rank in GLOB.science_positions)
- sci[++sci.len] = list("name" = name, "rank" = rank, "active" = isactive)
- department = 1
- if(depthead && length(sci) != 1)
- sci.Swap(1, length(sci))
-
- if(real_rank in GLOB.service_positions)
- ser[++ser.len] = list("name" = name, "rank" = rank, "active" = isactive)
- department = 1
- if(depthead && length(ser) != 1)
- ser.Swap(1, length(ser))
-
- if(real_rank in GLOB.supply_positions)
- sup[++sup.len] = list("name" = name, "rank" = rank, "active" = isactive)
- department = 1
- if(depthead && length(sup) != 1)
- sup.Swap(1, length(sup))
-
- if(real_rank in GLOB.nonhuman_positions)
- bot[++bot.len] = list("name" = name, "rank" = rank, "active" = isactive)
- department = 1
-
- if(!department && !(name in heads))
- misc[++misc.len] = list("name" = name, "rank" = rank, "active" = isactive)
-
-
- GLOB.PDA_Manifest = list(\
- "heads" = heads,\
- "sec" = sec,\
- "eng" = eng,\
- "med" = med,\
- "sci" = sci,\
- "ser" = ser,\
- "sup" = sup,\
- "bot" = bot,\
- "misc" = misc\
- )
- return
-
-
-
-/datum/datacore/proc/manifest()
- for(var/mob/living/carbon/human/H in GLOB.player_list)
- manifest_inject(H)
-
-/datum/datacore/proc/manifest_modify(name, assignment)
- if(length(GLOB.PDA_Manifest))
- GLOB.PDA_Manifest.Cut()
- var/datum/data/record/foundrecord
- var/real_title = assignment
-
- for(var/datum/data/record/t in GLOB.data_core.general)
- if(t)
- if(t.fields["name"] == name)
- foundrecord = t
- break
-
- var/list/all_jobs = get_job_datums()
- var/is_custom_job = TRUE
-
- for(var/datum/job/J in all_jobs)
- var/list/alttitles = get_alternate_titles(J.title)
- if(J.title == real_title)
- is_custom_job = FALSE
- if(assignment in alttitles)
- real_title = J.title
- is_custom_job = FALSE
- break
-
- if(is_custom_job && foundrecord)
- real_title = foundrecord.fields["real_rank"]
-
- if(foundrecord)
- foundrecord.fields["rank"] = assignment
- foundrecord.fields["real_rank"] = real_title
-
-GLOBAL_VAR_INIT(record_id_num, 1001)
-/datum/datacore/proc/manifest_inject(mob/living/carbon/human/H)
- if(length(GLOB.PDA_Manifest))
- GLOB.PDA_Manifest.Cut()
-
- if(H.mind && (H.mind.assigned_role != H.mind.special_role))
- var/assignment
- if(H.mind.role_alt_title)
- assignment = H.mind.role_alt_title
- else if(H.mind.assigned_role)
- assignment = H.mind.assigned_role
- else if(H.job)
- assignment = H.job
- else
- assignment = "Unassigned"
- GLOB.crew_list[H.real_name] = assignment
-
- var/id = num2hex(GLOB.record_id_num++, 6)
-
-
- //General Record
- var/datum/data/record/G = new()
- G.fields["id"] = id
- G.fields["name"] = H.real_name
- G.fields["real_rank"] = H.mind.assigned_role
- G.fields["rank"] = assignment
- G.fields["age"] = H.age
- G.fields["fingerprint"] = md5(H.dna.uni_identity)
- G.fields["p_stat"] = "Active"
- G.fields["m_stat"] = "Stable"
- G.fields["sex"] = capitalize(H.gender)
- G.fields["species"] = H.dna.species.name
- // Do some ID card checking stuff here to save on resources
- var/card_photo
- if(istype(H.wear_id, /obj/item/card/id))
- var/obj/item/card/id/IDC = H.wear_id
- card_photo = IDC.photo
- else
- card_photo = get_id_photo(H)
-
- G.fields["photo"] = card_photo
- G.fields["photo-south"] = "data:image/png;base64,[icon2base64(icon(card_photo, dir = SOUTH))]"
- G.fields["photo-west"] = "data:image/png;base64,[icon2base64(icon(card_photo, dir = WEST))]"
- if(H.gen_record && !jobban_isbanned(H, ROLEBAN_RECORDS))
- G.fields["notes"] = H.gen_record
- else
- G.fields["notes"] = "No notes found."
- general += G
-
- //Medical Record
- var/datum/data/record/M = new()
- M.fields["id"] = id
- M.fields["name"] = H.real_name
- M.fields["blood_type"] = H.dna.blood_type
- M.fields["b_dna"] = H.dna.unique_enzymes
- M.fields["mi_dis"] = "None"
- M.fields["mi_dis_d"] = "No minor disabilities have been declared."
- M.fields["ma_dis"] = "None"
- M.fields["ma_dis_d"] = "No major disabilities have been diagnosed."
- M.fields["alg"] = "None"
- M.fields["alg_d"] = "No allergies have been detected in this patient."
- M.fields["cdi"] = "None"
- M.fields["cdi_d"] = "No diseases have been diagnosed at the moment."
- if(H.med_record && !jobban_isbanned(H, ROLEBAN_RECORDS))
- M.fields["notes"] = H.med_record
- else
- M.fields["notes"] = "No notes found."
- medical += M
-
- //Security Record
- var/datum/data/record/S = new()
- S.fields["id"] = id
- S.fields["name"] = H.real_name
- S.fields["criminal"] = "None"
- S.fields["mi_crim"] = "None"
- S.fields["mi_crim_d"] = "No minor crime convictions."
- S.fields["ma_crim"] = "None"
- S.fields["ma_crim_d"] = "No major crime convictions."
- S.fields["notes"] = "No notes."
- if(H.sec_record && !jobban_isbanned(H, ROLEBAN_RECORDS))
- S.fields["notes"] = H.sec_record
- else
- S.fields["notes"] = "No notes found."
- LAZYINITLIST(S.fields["comments"])
- security += S
-
- //Locked Record
- var/datum/data/record/L = new()
- L.fields["id"] = md5("[H.real_name][H.mind.assigned_role]")
- L.fields["name"] = H.real_name
- L.fields["rank"] = H.mind.assigned_role
- L.fields["age"] = H.age
- L.fields["sex"] = capitalize(H.gender)
- L.fields["blood_type"] = H.dna.blood_type
- L.fields["b_dna"] = H.dna.unique_enzymes
- L.fields["enzymes"] = H.dna.SE // Used in respawning
- L.fields["identity"] = H.dna.UI // "
- L.fields["image"] = getFlatIcon(H) //This is god-awful
- locked += L
- return
-
-/proc/get_id_photo(mob/living/carbon/human/H, custom_job = null)
- var/icon/preview_icon = null
- var/obj/item/organ/external/head/head_organ = H.get_organ("head")
- var/obj/item/organ/internal/eyes/eyes_organ = H.get_int_organ(/obj/item/organ/internal/eyes)
-
- var/g = "m"
- if(H.gender == FEMALE)
- g = "f"
-
- var/icon/icobase = head_organ.icobase //At this point all the organs would have the same icobase, so this is just recycling.
-
- preview_icon = new /icon(icobase, "torso_[g]")
- var/icon/temp
- temp = new /icon(icobase, "groin_[g]")
- preview_icon.Blend(temp, ICON_OVERLAY)
- var/head = "head"
- if(head_organ.alt_head && head_organ.dna.species.bodyflags & HAS_ALT_HEADS)
- var/datum/sprite_accessory/alt_heads/alternate_head = GLOB.alt_heads_list[head_organ.alt_head]
- if(alternate_head.icon_state)
- head = alternate_head.icon_state
- temp = new /icon(icobase, "[head]_[g]")
- preview_icon.Blend(temp, ICON_OVERLAY)
-
- //Tail
- if(H.body_accessory && (istype(H.body_accessory, /datum/body_accessory/tail) || istype(H.body_accessory, /datum/body_accessory/wing)))
- temp = new/icon("icon" = H.body_accessory.icon, "icon_state" = H.body_accessory.icon_state)
- preview_icon.Blend(temp, ICON_OVERLAY)
- else if(H.tail && H.dna.species.bodyflags & HAS_TAIL)
- temp = new/icon("icon" = 'icons/effects/species.dmi', "icon_state" = "[H.tail]_s")
- preview_icon.Blend(temp, ICON_OVERLAY)
- else if(H.wing && H.dna.species.bodyflags & HAS_WING)
- temp = new/icon("icon" = 'icons/effects/species.dmi', "icon_state" = "[H.wing]_s")
- preview_icon.Blend(temp, ICON_OVERLAY)
-
- for(var/obj/item/organ/external/E in H.bodyparts)
- preview_icon.Blend(E.get_icon(), ICON_OVERLAY)
-
- // Skin tone
- if(H.dna.species.bodyflags & HAS_SKIN_TONE)
- if(H.s_tone >= 0)
- preview_icon.Blend(rgb(H.s_tone, H.s_tone, H.s_tone), ICON_ADD)
- else
- preview_icon.Blend(rgb(-H.s_tone, -H.s_tone, -H.s_tone), ICON_SUBTRACT)
-
- // Proper Skin color - Fix, you can't have HAS_SKIN_TONE *and* HAS_SKIN_COLOR
- if(H.dna.species.bodyflags & HAS_SKIN_COLOR)
- preview_icon.Blend(H.skin_colour, ICON_ADD)
-
- //Tail Markings
- var/icon/t_marking_s
- if(H.dna.species.bodyflags & HAS_TAIL_MARKINGS)
- var/tail_marking = H.m_styles["tail"]
- var/datum/sprite_accessory/tail_marking_style = GLOB.marking_styles_list[tail_marking]
- if(tail_marking_style && tail_marking_style.species_allowed)
- t_marking_s = new/icon("icon" = tail_marking_style.icon, "icon_state" = "[tail_marking_style.icon_state]_s")
- t_marking_s.Blend(H.m_colours["tail"], ICON_ADD)
- if(!(H.body_accessory && istype(H.body_accessory, /datum/body_accessory/body)))
- preview_icon.Blend(t_marking_s, ICON_OVERLAY)
-
- var/icon/face_s = new/icon("icon" = 'icons/mob/human_face.dmi', "icon_state" = "bald_s")
- if(!(H.dna.species.bodyflags & NO_EYES))
- var/icon/eyes_s = new/icon("icon" = 'icons/mob/human_face.dmi', "icon_state" = H.dna.species ? H.dna.species.eyes : "eyes_s")
- if(!eyes_organ)
- return
- eyes_s.Blend(eyes_organ.eye_color, ICON_ADD)
- face_s.Blend(eyes_s, ICON_OVERLAY)
-
- var/datum/sprite_accessory/hair_style = GLOB.hair_styles_full_list[head_organ.h_style]
- if(hair_style)
- var/icon/hair_s = new/icon("icon" = hair_style.icon, "icon_state" = "[hair_style.icon_state]_s")
- // I'll want to make a species-specific proc for this sooner or later
- // But this'll do for now
- if(istype(head_organ.dna.species, /datum/species/slime))
- hair_s.Blend("[H.skin_colour]A0", ICON_AND) //A0 = 160 alpha.
- else
- hair_s.Blend(head_organ.hair_colour, ICON_ADD)
-
- if(hair_style.secondary_theme)
- var/icon/hair_secondary_s = new/icon("icon" = hair_style.icon, "icon_state" = "[hair_style.icon_state]_[hair_style.secondary_theme]_s")
- if(!hair_style.no_sec_colour)
- hair_secondary_s.Blend(head_organ.sec_hair_colour, ICON_ADD)
- hair_s.Blend(hair_secondary_s, ICON_OVERLAY)
-
- face_s.Blend(hair_s, ICON_OVERLAY)
-
- //Head Accessory
- if(head_organ.dna.species.bodyflags & HAS_HEAD_ACCESSORY)
- var/datum/sprite_accessory/head_accessory_style = GLOB.head_accessory_styles_list[head_organ.ha_style]
- if(head_accessory_style && head_accessory_style.species_allowed)
- var/icon/head_accessory_s = new/icon("icon" = head_accessory_style.icon, "icon_state" = "[head_accessory_style.icon_state]_s")
- head_accessory_s.Blend(head_organ.headacc_colour, ICON_ADD)
- face_s.Blend(head_accessory_s, ICON_OVERLAY)
-
- var/datum/sprite_accessory/facial_hair_style = GLOB.facial_hair_styles_list[head_organ.f_style]
- if(facial_hair_style && facial_hair_style.species_allowed)
- var/icon/facial_s = new/icon("icon" = facial_hair_style.icon, "icon_state" = "[facial_hair_style.icon_state]_s")
- if(istype(head_organ.dna.species, /datum/species/slime))
- facial_s.Blend("[H.skin_colour]A0", ICON_ADD) //A0 = 160 alpha.
- else
- facial_s.Blend(head_organ.facial_colour, ICON_ADD)
-
- if(facial_hair_style.secondary_theme)
- var/icon/facial_secondary_s = new/icon("icon" = facial_hair_style.icon, "icon_state" = "[facial_hair_style.icon_state]_[facial_hair_style.secondary_theme]_s")
- if(!facial_hair_style.no_sec_colour)
- facial_secondary_s.Blend(head_organ.sec_facial_colour, ICON_ADD)
- facial_s.Blend(facial_secondary_s, ICON_OVERLAY)
-
- face_s.Blend(facial_s, ICON_OVERLAY)
-
- //Markings
- if((H.dna.species.bodyflags & HAS_HEAD_MARKINGS) || (H.dna.species.bodyflags & HAS_BODY_MARKINGS))
- if(H.dna.species.bodyflags & HAS_BODY_MARKINGS) //Body markings.
- var/body_marking = H.m_styles["body"]
- var/datum/sprite_accessory/body_marking_style = GLOB.marking_styles_list[body_marking]
- if(body_marking_style && body_marking_style.species_allowed)
- var/icon/b_marking_s = new/icon("icon" = body_marking_style.icon, "icon_state" = "[body_marking_style.icon_state]_s")
- b_marking_s.Blend(H.m_colours["body"], ICON_ADD)
- face_s.Blend(b_marking_s, ICON_OVERLAY)
- if(H.dna.species.bodyflags & HAS_HEAD_MARKINGS) //Head markings.
- var/head_marking = H.m_styles["head"]
- var/datum/sprite_accessory/head_marking_style = GLOB.marking_styles_list[head_marking]
- if(head_marking_style && head_marking_style.species_allowed)
- var/icon/h_marking_s = new/icon("icon" = head_marking_style.icon, "icon_state" = "[head_marking_style.icon_state]_s")
- h_marking_s.Blend(H.m_colours["head"], ICON_ADD)
- face_s.Blend(h_marking_s, ICON_OVERLAY)
-
- preview_icon.Blend(face_s, ICON_OVERLAY)
- //Underwear
- var/icon/underwear_standing = new /icon('icons/mob/clothing/underwear.dmi', "nude")
- if(H.underwear && H.dna.species.clothing_flags & HAS_UNDERWEAR)
- var/datum/sprite_accessory/underwear/U = GLOB.underwear_list[H.underwear]
- if(U)
- var/u_icon = U.sprite_sheets && (H.dna.species.sprite_sheet_name in U.sprite_sheets) ? U.sprite_sheets[H.dna.species.sprite_sheet_name] : U.icon //Species-fit the undergarment.
- underwear_standing.Blend(new /icon(u_icon, "uw_[U.icon_state]_s"), ICON_OVERLAY)
-
- if(H.undershirt && H.dna.species.clothing_flags & HAS_UNDERSHIRT)
- var/datum/sprite_accessory/undershirt/U2 = GLOB.undershirt_list[H.undershirt]
- if(U2)
- var/u2_icon = U2.sprite_sheets && (H.dna.species.sprite_sheet_name in U2.sprite_sheets) ? U2.sprite_sheets[H.dna.species.sprite_sheet_name] : U2.icon
- underwear_standing.Blend(new /icon(u2_icon, "us_[U2.icon_state]_s"), ICON_OVERLAY)
-
- if(H.socks && H.dna.species.clothing_flags & HAS_SOCKS)
- var/datum/sprite_accessory/socks/U3 = GLOB.socks_list[H.socks]
- if(U3)
- var/u3_icon = U3.sprite_sheets && (H.dna.species.sprite_sheet_name in U3.sprite_sheets) ? U3.sprite_sheets[H.dna.species.sprite_sheet_name] : U3.icon
- underwear_standing.Blend(new /icon(u3_icon, "sk_[U3.icon_state]_s"), ICON_OVERLAY)
-
- if(underwear_standing)
- preview_icon.Blend(underwear_standing, ICON_OVERLAY)
-
- var/icon/hands_icon = icon(preview_icon)
- hands_icon.Blend(icon('icons/mob/clothing/masking_helpers.dmi', "l_hand_mask"), ICON_MULTIPLY)
-
- var/icon/clothes_s = null
- var/job_clothes = null
- if(custom_job)
- job_clothes = custom_job
- else if(H.mind)
- job_clothes = H.mind.assigned_role
- switch(job_clothes)
- if("Head of Personnel")
- clothes_s = new /icon('icons/mob/clothing/under/civilian.dmi', "hop_s")
- clothes_s.Blend(new /icon('icons/mob/clothing/feet.dmi', "brown"), ICON_UNDERLAY)
- if("Nanotrasen Representative")
- clothes_s = new /icon('icons/mob/clothing/under/centcom.dmi', "officer_s")
- clothes_s.Blend(new /icon('icons/mob/clothing/feet.dmi', "laceups"), ICON_UNDERLAY)
- if("Nanotrasen Career Trainer")
- clothes_s = new /icon('icons/mob/clothing/under/procedure.dmi', "trainer_s")
- clothes_s.Blend(new /icon('icons/mob/clothing/feet.dmi', "laceups"), ICON_UNDERLAY)
- clothes_s.Blend(new /icon('icons/mob/clothing/suit.dmi', "trainercoat"), ICON_OVERLAY)
- if("Blueshield")
- clothes_s = new /icon('icons/mob/clothing/under/centcom.dmi', "officer_s")
- clothes_s.Blend(new /icon('icons/mob/clothing/feet.dmi', "jackboots"), ICON_UNDERLAY)
- clothes_s.Blend(new /icon('icons/mob/clothing/suit.dmi', "blueshield"), ICON_OVERLAY)
- if("Magistrate")
- clothes_s = new /icon('icons/mob/clothing/under/suit.dmi', "really_black_suit_s")
- clothes_s.Blend(new /icon('icons/mob/clothing/feet.dmi', "laceups"), ICON_UNDERLAY)
- clothes_s.Blend(new /icon('icons/mob/clothing/suit.dmi', "judge"), ICON_OVERLAY)
- if("Bartender")
- clothes_s = new /icon('icons/mob/clothing/under/civilian.dmi', "ba_suit_s")
- clothes_s.Blend(new /icon('icons/mob/clothing/feet.dmi', "black"), ICON_UNDERLAY)
- if("Botanist")
- clothes_s = new /icon('icons/mob/clothing/under/civilian.dmi', "hydroponics_s")
- clothes_s.Blend(new /icon('icons/mob/clothing/feet.dmi', "black"), ICON_UNDERLAY)
- if("Chef")
- clothes_s = new /icon('icons/mob/clothing/under/civilian.dmi', "chef_s")
- clothes_s.Blend(new /icon('icons/mob/clothing/feet.dmi', "black"), ICON_UNDERLAY)
- if("Janitor")
- clothes_s = new /icon('icons/mob/clothing/under/civilian.dmi', "janitor_s")
- clothes_s.Blend(new /icon('icons/mob/clothing/feet.dmi', "black"), ICON_UNDERLAY)
- if("Librarian")
- clothes_s = new /icon('icons/mob/clothing/under/civilian.dmi', "red_suit_s")
- clothes_s.Blend(new /icon('icons/mob/clothing/feet.dmi', "black"), ICON_UNDERLAY)
- if("Clown")
- clothes_s = new /icon('icons/mob/clothing/under/civilian.dmi', "clown_s")
- clothes_s.Blend(new /icon('icons/mob/clothing/feet.dmi', "clown"), ICON_UNDERLAY)
- if("Mime")
- clothes_s = new /icon('icons/mob/clothing/under/civilian.dmi', "mime_s")
- clothes_s.Blend(new /icon('icons/mob/clothing/feet.dmi', "black"), ICON_UNDERLAY)
- if("Quartermaster")
- clothes_s = new /icon('icons/mob/clothing/under/cargo.dmi', "qm_s")
- clothes_s.Blend(new /icon('icons/mob/clothing/feet.dmi', "brown"), ICON_UNDERLAY)
- if("Cargo Technician")
- clothes_s = new /icon('icons/mob/clothing/under/cargo.dmi', "cargo_s")
- clothes_s.Blend(new /icon('icons/mob/clothing/feet.dmi', "black"), ICON_UNDERLAY)
- if("Shaft Miner")
- clothes_s = new /icon('icons/mob/clothing/under/cargo.dmi', "explorer_s")
- clothes_s.Blend(new /icon('icons/mob/clothing/feet.dmi', "explorer"), ICON_UNDERLAY)
- if("Internal Affairs Agent")
- clothes_s = new /icon('icons/mob/clothing/under/civilian.dmi', "internalaffairs_s")
- clothes_s.Blend(new /icon('icons/mob/clothing/feet.dmi', "brown"), ICON_UNDERLAY)
- if("Chaplain")
- clothes_s = new /icon('icons/mob/clothing/under/civilian.dmi', "chapblack_s")
- clothes_s.Blend(new /icon('icons/mob/clothing/feet.dmi', "black"), ICON_UNDERLAY)
- if("Research Director")
- clothes_s = new /icon('icons/mob/clothing/under/rnd.dmi', "director_s")
- clothes_s.Blend(new /icon('icons/mob/clothing/feet.dmi', "brown"), ICON_UNDERLAY)
- clothes_s.Blend(new /icon('icons/mob/clothing/suit.dmi', "labcoat_open"), ICON_OVERLAY)
- if("Scientist")
- clothes_s = new /icon('icons/mob/clothing/under/rnd.dmi', "science_s")
- clothes_s.Blend(new /icon('icons/mob/clothing/feet.dmi', "white"), ICON_UNDERLAY)
- clothes_s.Blend(new /icon('icons/mob/clothing/suit.dmi', "labcoat_tox_open"), ICON_OVERLAY)
- if("Chemist")
- clothes_s = new /icon('icons/mob/clothing/under/medical.dmi', "chemistry_s")
- clothes_s.Blend(new /icon('icons/mob/clothing/feet.dmi', "white"), ICON_UNDERLAY)
- clothes_s.Blend(new /icon('icons/mob/clothing/suit.dmi', "labcoat_chem_open"), ICON_OVERLAY)
- if("Chief Medical Officer")
- clothes_s = new /icon('icons/mob/clothing/under/medical.dmi', "cmo_s")
- clothes_s.Blend(new /icon('icons/mob/clothing/feet.dmi', "brown"), ICON_UNDERLAY)
- clothes_s.Blend(new /icon('icons/mob/clothing/suit.dmi', "labcoat_cmo_open"), ICON_OVERLAY)
- if("Medical Doctor")
- clothes_s = new /icon('icons/mob/clothing/under/medical.dmi', "medical_s")
- clothes_s.Blend(new /icon('icons/mob/clothing/feet.dmi', "white"), ICON_UNDERLAY)
- clothes_s.Blend(new /icon('icons/mob/clothing/suit.dmi', "labcoat_open"), ICON_OVERLAY)
- if("Coroner")
- clothes_s = new /icon('icons/mob/clothing/under/medical.dmi', "scrubsblack_s")
- clothes_s.Blend(new /icon('icons/mob/clothing/feet.dmi', "white"), ICON_UNDERLAY)
- clothes_s.Blend(new /icon('icons/mob/clothing/suit.dmi', "labcoat_mort_open"), ICON_OVERLAY)
- if("Geneticist")
- clothes_s = new /icon('icons/mob/clothing/under/rnd.dmi', "genetics_s")
- clothes_s.Blend(new /icon('icons/mob/clothing/feet.dmi', "white"), ICON_UNDERLAY)
- clothes_s.Blend(new /icon('icons/mob/clothing/suit.dmi', "labcoat_gen_open"), ICON_OVERLAY)
- if("Virologist")
- clothes_s = new /icon('icons/mob/clothing/under/medical.dmi', "virology_s")
- clothes_s.Blend(new /icon('icons/mob/clothing/feet.dmi', "white"), ICON_UNDERLAY)
- clothes_s.Blend(new /icon('icons/mob/clothing/suit.dmi', "labcoat_vir_open"), ICON_OVERLAY)
- if("Psychiatrist")
- clothes_s = new /icon('icons/mob/clothing/under/medical.dmi', "psych_s")
- clothes_s.Blend(new /icon('icons/mob/clothing/feet.dmi', "laceups"), ICON_UNDERLAY)
- clothes_s.Blend(new /icon('icons/mob/clothing/suit.dmi', "labcoat_open"), ICON_UNDERLAY)
- if("Paramedic")
- clothes_s = new /icon('icons/mob/clothing/under/medical.dmi', "paramedic_s")
- clothes_s.Blend(new /icon('icons/mob/clothing/feet.dmi', "black"), ICON_UNDERLAY)
- if("Captain")
- clothes_s = new /icon('icons/mob/clothing/under/captain.dmi', "captain_s")
- clothes_s.Blend(new /icon('icons/mob/clothing/feet.dmi', "brown"), ICON_UNDERLAY)
- if("Head of Security")
- clothes_s = new /icon('icons/mob/clothing/under/security.dmi', "hos_s")
- clothes_s.Blend(new /icon('icons/mob/clothing/feet.dmi', "jackboots"), ICON_UNDERLAY)
- if("Warden")
- clothes_s = new /icon('icons/mob/clothing/under/security.dmi', "warden_s")
- clothes_s.Blend(new /icon('icons/mob/clothing/feet.dmi', "jackboots"), ICON_UNDERLAY)
- if("Detective")
- clothes_s = new /icon('icons/mob/clothing/under/security.dmi', "detective_s")
- clothes_s.Blend(new /icon('icons/mob/clothing/feet.dmi', "brown"), ICON_UNDERLAY)
- clothes_s.Blend(new /icon('icons/mob/clothing/suit.dmi', "detective"), ICON_OVERLAY)
- if("Security Officer")
- clothes_s = new /icon('icons/mob/clothing/under/security.dmi', "security_s")
- clothes_s.Blend(new /icon('icons/mob/clothing/feet.dmi', "jackboots"), ICON_UNDERLAY)
- if("Chief Engineer")
- clothes_s = new /icon('icons/mob/clothing/under/engineering.dmi', "chief_engineer_s")
- clothes_s.Blend(new /icon('icons/mob/clothing/feet.dmi', "brown"), ICON_UNDERLAY)
- clothes_s.Blend(new /icon('icons/mob/clothing/belt.dmi', "utility"), ICON_OVERLAY)
- if("Station Engineer")
- clothes_s = new /icon('icons/mob/clothing/under/engineering.dmi', "engineer_s")
- clothes_s.Blend(new /icon('icons/mob/clothing/feet.dmi', "orange"), ICON_UNDERLAY)
- clothes_s.Blend(new /icon('icons/mob/clothing/belt.dmi', "utility"), ICON_OVERLAY)
- if("Life Support Specialist")
- clothes_s = new /icon('icons/mob/clothing/under/engineering.dmi', "atmos_s")
- clothes_s.Blend(new /icon('icons/mob/clothing/feet.dmi', "black"), ICON_UNDERLAY)
- clothes_s.Blend(new /icon('icons/mob/clothing/belt.dmi', "utility"), ICON_OVERLAY)
- if("Roboticist")
- clothes_s = new /icon('icons/mob/clothing/under/rnd.dmi', "robotics_s")
- clothes_s.Blend(new /icon('icons/mob/clothing/feet.dmi', "black"), ICON_UNDERLAY)
- clothes_s.Blend(new /icon('icons/mob/clothing/suit.dmi', "labcoat_open"), ICON_OVERLAY)
- if("Syndicate Agent")
- clothes_s = new /icon('icons/mob/clothing/under/syndicate.dmi', "syndicate_s")
- clothes_s.Blend(new /icon('icons/mob/clothing/feet.dmi', "black"), ICON_UNDERLAY)
- if("Syndicate Officer")
- clothes_s = new /icon('icons/mob/clothing/under/syndicate.dmi', "syndicate_s")
- clothes_s.Blend(new /icon('icons/mob/clothing/feet.dmi', "jackboots"), ICON_UNDERLAY)
- if("Syndicate Nuclear Operative")
- clothes_s = new /icon('icons/mob/clothing/under/syndicate.dmi', "syndicate_s")
- clothes_s.Blend(new /icon('icons/mob/clothing/feet.dmi', "jackboots"), ICON_UNDERLAY)
- if("Emergency Response Team Officer")
- clothes_s = new /icon('icons/mob/clothing/under/centcom.dmi', "officer_s")
- clothes_s.Blend(new /icon('icons/mob/clothing/feet.dmi', "jackboots"), ICON_UNDERLAY)
- clothes_s.Blend(new /icon('icons/mob/clothing/hands.dmi', "swat_gl"), ICON_UNDERLAY)
- clothes_s.Blend(new /icon('icons/mob/clothing/belt.dmi', "security"), ICON_OVERLAY)
- if("Emergency Response Team Engineer")
- clothes_s = new /icon('icons/mob/clothing/under/centcom.dmi', "officer_s")
- clothes_s.Blend(new /icon('icons/mob/clothing/feet.dmi', "workboots"), ICON_UNDERLAY)
- clothes_s.Blend(new /icon('icons/mob/clothing/hands.dmi', "swat_gl"), ICON_UNDERLAY)
- clothes_s.Blend(new /icon('icons/mob/clothing/belt.dmi', "utility"), ICON_OVERLAY)
- if("Emergency Response Team Medic")
- clothes_s = new /icon('icons/mob/clothing/under/centcom.dmi', "officer_s")
- clothes_s.Blend(new /icon('icons/mob/clothing/feet.dmi', "white"), ICON_UNDERLAY)
- clothes_s.Blend(new /icon('icons/mob/clothing/hands.dmi', "swat_gl"), ICON_UNDERLAY)
- clothes_s.Blend(new /icon('icons/mob/clothing/belt.dmi', "medical"), ICON_OVERLAY)
- if("Emergency Response Team Inquisitor")
- clothes_s = new /icon('icons/mob/clothing/under/centcom.dmi', "officer_s")
- clothes_s.Blend(new /icon('icons/mob/clothing/feet.dmi', "jackboots"), ICON_UNDERLAY)
- clothes_s.Blend(new /icon('icons/mob/clothing/hands.dmi', "swat_gl"), ICON_UNDERLAY)
- clothes_s.Blend(new /icon('icons/mob/clothing/belt.dmi', "claymore"), ICON_OVERLAY)
- if("Emergency Response Team Janitor")
- clothes_s = new /icon('icons/mob/clothing/under/centcom.dmi', "officer_s")
- clothes_s.Blend(new /icon('icons/mob/clothing/feet.dmi', "galoshes"), ICON_UNDERLAY)
- clothes_s.Blend(new /icon('icons/mob/clothing/hands.dmi', "swat_gl"), ICON_UNDERLAY)
- clothes_s.Blend(new /icon('icons/mob/clothing/belt.dmi', "janibelt"), ICON_OVERLAY)
- if("Emergency Response Team Leader")
- clothes_s = new /icon('icons/mob/clothing/under/centcom.dmi', "officer_s")
- clothes_s.Blend(new /icon('icons/mob/clothing/feet.dmi', "laceups"), ICON_UNDERLAY)
- clothes_s.Blend(new /icon('icons/mob/clothing/hands.dmi', "wgloves"), ICON_UNDERLAY)
- if("Emergency Response Team Member")
- clothes_s = new /icon('icons/mob/clothing/under/centcom.dmi', "officer_s")
- clothes_s.Blend(new /icon('icons/mob/clothing/feet.dmi', "jackboots"), ICON_UNDERLAY)
- clothes_s.Blend(new /icon('icons/mob/clothing/hands.dmi', "swat_gl"), ICON_UNDERLAY)
- if("Naked")
- clothes_s = null
- else
- if(H.mind && (H.mind.assigned_role in get_all_centcom_jobs()))
- clothes_s = new /icon('icons/mob/clothing/under/centcom.dmi', "officer_s")
- clothes_s.Blend(new /icon('icons/mob/clothing/feet.dmi', "laceups"), ICON_UNDERLAY)
- else
- clothes_s = new /icon('icons/mob/clothing/under/color.dmi', "grey_s")
- clothes_s.Blend(new /icon('icons/mob/clothing/feet.dmi', "black"), ICON_UNDERLAY)
-
- preview_icon.Blend(face_s, ICON_OVERLAY) // Why do we do this twice
- if(clothes_s)
- preview_icon.Blend(clothes_s, ICON_OVERLAY)
- //Bus body accessories that go over clothes.
- if(H.body_accessory && istype(H.body_accessory, /datum/body_accessory/body))
- temp = new/icon("icon" = H.body_accessory.icon, "icon_state" = H.body_accessory.icon_state)
- if(H.body_accessory.pixel_x_offset)
- temp.Shift(EAST, H.body_accessory.pixel_x_offset)
- if(H.body_accessory.pixel_y_offset)
- temp.Shift(NORTH, H.body_accessory.pixel_y_offset)
- if(H.dna.species.bodyflags & HAS_SKIN_COLOR)
- temp.Blend(H.skin_colour, H.body_accessory.blend_mode)
- if(t_marking_s)
- temp.Blend(t_marking_s, ICON_OVERLAY)
- preview_icon.Blend(temp, ICON_OVERLAY)
-
- preview_icon.Blend(hands_icon, ICON_OVERLAY)
- qdel(face_s)
- qdel(clothes_s)
-
- return preview_icon
diff --git a/code/datums/datum.dm b/code/datums/datum.dm
deleted file mode 100644
index e09769af7552d..0000000000000
--- a/code/datums/datum.dm
+++ /dev/null
@@ -1,86 +0,0 @@
-/datum
- var/gc_destroyed //Time when this object was destroyed.
- var/list/active_timers //for SStimer
- var/list/datum_components //for /datum/components
- /// Status traits attached to this datum
- var/list/status_traits
- var/list/comp_lookup
- var/list/list/datum/callback/signal_procs
- var/var_edited = FALSE //Warranty void if seal is broken
- var/tmp/unique_datum_id = null
- /// MD5'd version of the UID. Used for instances where we dont want to make clients aware of UIDs.
- VAR_PRIVATE/tmp/md5_unique_datum_id = null // using VAR_PRIVATE means it cant be accessed outside of the MD5_UID() proc
-
- /// Used by SSprocessing
- var/isprocessing = FALSE
-
-/**
- * A cached version of our \ref
- * The brunt of \ref costs are in creating entries in the string tree (a tree of immutable strings)
- * This avoids doing that more then once per datum by ensuring ref strings always have a reference to them after they're first pulled
- */
- var/cached_ref
-
-#ifdef REFERENCE_TRACKING
- var/running_find_references
- var/last_find_references = 0
-#endif
-
-// Default implementation of clean-up code.
-// This should be overridden to remove all references pointing to the object being destroyed.
-// Return the appropriate QDEL_HINT; in most cases this is QDEL_HINT_QUEUE.
-/datum/proc/Destroy(force = FALSE, ...)
- SHOULD_CALL_PARENT(TRUE)
- tag = null
-
- // Close our open TGUIs
- SStgui.close_uis(src)
-
- var/list/timers = active_timers
- active_timers = null
- for(var/thing in timers)
- var/datum/timedevent/timer = thing
- if(timer.spent && !(timer.flags & TIMER_DELETE_ME))
- continue
- qdel(timer)
-
- //BEGIN: ECS SHIT
- var/list/dc = datum_components
- if(dc)
- var/all_components = dc[/datum/component]
- if(length(all_components))
- for(var/I in all_components)
- var/datum/component/C = I
- qdel(C, FALSE, TRUE)
- else
- var/datum/component/C = all_components
- qdel(C, FALSE, TRUE)
- dc.Cut()
-
- _clear_signal_refs()
- //END: ECS SHIT
-
- return QDEL_HINT_QUEUE
-
-/// Do not override this. This proc exists solely to be overriden by /turf. This
-/// allows it to ignore clearing out signals which refer to it, in order to keep
-/// those signals valid after the turf has been changed.
-/datum/proc/_clear_signal_refs()
- var/list/lookup = comp_lookup
- if(lookup)
- for(var/sig in lookup)
- var/list/comps = lookup[sig]
- if(length(comps))
- for(var/i in comps)
- var/datum/component/comp = i
- comp.UnregisterSignal(src, sig)
- else
- var/datum/component/comp = comps
- comp.UnregisterSignal(src, sig)
- comp_lookup = lookup = null
-
- for(var/target in signal_procs)
- UnregisterSignal(target, signal_procs[target])
-
-/datum/nothing
- // Placeholder object, used for ispath checks. Has to be defined to prevent errors, but shouldn't ever be created.
diff --git a/code/datums/diseases/_MobProcs.dm b/code/datums/diseases/_MobProcs.dm
deleted file mode 100644
index e833f1715fa51..0000000000000
--- a/code/datums/diseases/_MobProcs.dm
+++ /dev/null
@@ -1,172 +0,0 @@
-
-/mob/proc/HasDisease(datum/disease/D)
- for(var/thing in viruses)
- var/datum/disease/DD = thing
- if(DD.IsSame(D))
- return TRUE
- return FALSE
-
-
-/mob/proc/CanContractDisease(datum/disease/D)
- if(stat == DEAD && !D.allow_dead)
- return FALSE
-
- if(D.GetDiseaseID() in resistances)
- return FALSE
-
- if(HasDisease(D))
- return FALSE
-
- if(istype(D, /datum/disease/advance) && count_by_type(viruses, /datum/disease/advance) > 0)
- return FALSE
-
- if(!(type in D.viable_mobtypes))
- return -1 //for stupid fucking monkies
-
- return TRUE
-
-
-/mob/proc/ContractDisease(datum/disease/D)
- if(!CanContractDisease(D))
- return 0
- AddDisease(D)
- return TRUE
-
-
-/mob/proc/AddDisease(datum/disease/D, respect_carrier = FALSE)
- var/datum/disease/DD = new D.type(1, D, 0)
- viruses += DD
- DD.affected_mob = src
- GLOB.active_diseases += DD //Add it to the active diseases list, now that it's actually in a mob and being processed.
-
- //Copy properties over. This is so edited diseases persist.
- var/list/skipped = list("affected_mob","holder","carrier","stage","type","parent_type","vars","transformed")
- if(respect_carrier)
- skipped -= "carrier"
- for(var/V in DD.vars)
- if(V in skipped)
- continue
- if(istype(DD.vars[V],/list))
- var/list/L = D.vars[V]
- DD.vars[V] = L.Copy()
- else
- DD.vars[V] = D.vars[V]
-
- create_log(MISC_LOG, "has contacted the virus \"[DD]\"")
- DD.affected_mob.med_hud_set_status()
-
-
-/mob/living/carbon/ContractDisease(datum/disease/D)
- if(!CanContractDisease(D))
- return 0
-
- var/obj/item/clothing/Cl = null
- var/passed = 1
-
- var/head_ch = 100
- var/body_ch = 100
- var/hands_ch = 25
- var/feet_ch = 25
-
- if(D.spread_flags & CONTACT_HANDS)
- head_ch = 0
- body_ch = 0
- hands_ch = 100
- feet_ch = 0
- if(D.spread_flags & CONTACT_FEET)
- head_ch = 0
- body_ch = 0
- hands_ch = 0
- feet_ch = 100
-
- if(prob(15/D.permeability_mod))
- return
-
- if(satiety > 0 && prob(satiety/10)) // positive satiety makes it harder to contract the disease.
- return
-
- var/target_zone = pick(head_ch;1,body_ch;2,hands_ch;3,feet_ch;4)
-
- if(ishuman(src))
- var/mob/living/carbon/human/H = src
-
- switch(target_zone)
- if(1)
- if(isobj(H.head) && !istype(H.head, /obj/item/paper))
- Cl = H.head
- passed = prob((Cl.permeability_coefficient*100) - 1)
- if(passed && isobj(H.wear_mask))
- Cl = H.wear_mask
- passed = prob((Cl.permeability_coefficient*100) - 1)
- if(2)
- if(isobj(H.wear_suit))
- Cl = H.wear_suit
- passed = prob((Cl.permeability_coefficient*100) - 1)
- if(passed && isobj(H.w_uniform))
- Cl = H.w_uniform
- passed = prob((Cl.permeability_coefficient*100) - 1)
- if(3)
- if(isobj(H.wear_suit) && H.wear_suit.body_parts_covered&HANDS)
- Cl = H.wear_suit
- passed = prob((Cl.permeability_coefficient*100) - 1)
-
- if(passed && isobj(H.gloves))
- Cl = H.gloves
- passed = prob((Cl.permeability_coefficient*100) - 1)
- if(4)
- if(isobj(H.wear_suit) && H.wear_suit.body_parts_covered&FEET)
- Cl = H.wear_suit
- passed = prob((Cl.permeability_coefficient*100) - 1)
-
- if(passed && isobj(H.shoes))
- Cl = H.shoes
- passed = prob((Cl.permeability_coefficient*100) - 1)
-
-
- if(!passed && (D.spread_flags & AIRBORNE) && !internal)
- passed = (prob((50*D.permeability_mod) - 1))
-
- if(passed)
- AddDisease(D)
- return passed
-
-
-/**
- * Forces the mob to contract a virus. If the mob can have viruses. Ignores clothing and other protection
- * Returns TRUE if it succeeds. False if it doesn't
- *
- * Arguments:
- * * D - the disease the mob will try to contract
- * * respect_carrier - if set to TRUE will not ignore the disease carrier flag
- * * notify_ghosts - will notify ghosts of infection if set to TRUE
- */
-//Same as ContractDisease, except never overidden clothes checks
-/mob/proc/ForceContractDisease(datum/disease/D, respect_carrier, notify_ghosts = FALSE)
- if(!CanContractDisease(D))
- return FALSE
- if(notify_ghosts)
- for(var/mob/ghost as anything in GLOB.dead_mob_list) //Announce outbreak to dchat
- to_chat(ghost, "Disease outbreak: [src] ([ghost_follow_link(src, ghost)]) [D.carrier ? "is now a carrier of" : "has contracted"] [D]!")
- AddDisease(D, respect_carrier)
- return TRUE
-
-
-/mob/living/carbon/human/CanContractDisease(datum/disease/D)
- if(HAS_TRAIT(src, TRAIT_VIRUSIMMUNE) && !D.bypasses_immunity)
- return FALSE
-
- for(var/organ in D.required_organs)
- if(istext(organ) && get_int_organ_datum(organ))
- continue
- if(locate(organ) in internal_organs)
- continue
- if(locate(organ) in bodyparts)
- continue
- return FALSE
- return ..()
-
-/mob/living/carbon/human/monkey/CanContractDisease(datum/disease/D)
- . = ..()
- if(. == -1)
- if(D.viable_mobtypes.Find(/mob/living/carbon/human))
- return 1 //this is stupid as fuck but because monkeys are only half the time actually subtypes of humans they need this
diff --git a/code/datums/diseases/pierrot_throat.dm b/code/datums/diseases/pierrot_throat.dm
deleted file mode 100644
index dd2c758092429..0000000000000
--- a/code/datums/diseases/pierrot_throat.dm
+++ /dev/null
@@ -1,60 +0,0 @@
-/datum/disease/pierrot_throat
- name = "Pierrot's Throat"
- max_stages = 4
- spread_text = "Airborne"
- cure_text = "Banana products, especially banana bread."
- cures = list("banana")
- cure_chance = 75
- agent = "H0NI<42.B4n4 Virus"
- viable_mobtypes = list(/mob/living/carbon/human)
- permeability_mod = 0.75
- desc = "If left untreated the subject will probably drive others to insanity and go insane themselves."
- severity = MINOR
-
-/datum/disease/pierrot_throat/stage_act()
- if(!..())
- return FALSE
- if(stage < 2)
- return
-
- var/mob/living/carbon/human/H = affected_mob
-
- var/static/list/message_chances = list(null, 4, 2, 1)
- if(prob(message_chances[stage]))
- to_chat(H, "You feel [pick("a little silly", "like making a joke", "in the mood for giggling", "like the world is a little more vibrant")].")
- if(prob(message_chances[stage]))
- to_chat(H, "You see [pick("rainbows", "puppies", "banana pies")] for a moment.")
-
- if(stage < 3)
- return
-
- var/static/list/honk_chances = list(null, null, 4, 0.66)
- if(prob(honk_chances[stage]))
- to_chat(H, "Your thoughts are interrupted by a loud HONK!")
- SEND_SOUND(H, sound(pick(18; 'sound/items/bikehorn.ogg', 1; 'sound/items/airhorn.ogg', 1; 'sound/items/airhorn2.ogg'))) // 10% chance total for an airhorn
-
- if(stage < 4)
- return
-
- if(prob(5))
- H.say(pick("HONK!", "Honk!", "Honk.", "Honk?", "Honk!!", "Honk?!", "Honk..."))
-
- // Semi-permanent clown mask while in last stage of infection
- if(locate(/obj/item/clothing/mask/gas/clown_hat) in H)
- return
- if(!istype(H)) // Xenos don't have masks. They can still feel silly though
- return
-
- if(!H.has_organ_for_slot(ITEM_SLOT_MASK) || !H.canUnEquip(H.get_item_by_slot(ITEM_SLOT_MASK)))
- return
-
- var/saved_internals = H.internal
-
- H.drop_item_to_ground(H.get_item_by_slot(ITEM_SLOT_MASK))
- var/obj/item/clothing/mask/gas/clown_hat/peak_comedy = new
- peak_comedy.flags |= DROPDEL
- H.equip_to_slot_or_del(peak_comedy, ITEM_SLOT_MASK)
-
- if(saved_internals) // Let's not stealthily suffocate Vox/Plasmamen, this isn't a murder virus
- H.internal = saved_internals
- H.update_action_buttons_icon()
diff --git a/code/datums/diseases/wizarditis.dm b/code/datums/diseases/wizarditis.dm
deleted file mode 100644
index 2dd23b8734aab..0000000000000
--- a/code/datums/diseases/wizarditis.dm
+++ /dev/null
@@ -1,122 +0,0 @@
-/datum/disease/wizarditis
- name = "Wizarditis"
- desc = "Some speculate that this virus is the cause of Wizard Federation existence. Subjects affected show signs of dementia, yelling obscure sentences or total gibberish. In late stages, subjects sometime express feelings of inner power, and cite 'the ability to control the forces of cosmos themselves!' A gulp of strong, manly spirits usually reverts them to normal, humanlike condition."
- max_stages = 4
- spread_text = "Airborne"
- cure_text = "The Manly Dorf"
- cures = list("manlydorf")
- cure_chance = 100
- agent = "Rincewindus Vulgaris"
- viable_mobtypes = list(/mob/living/carbon/human)
- permeability_mod = 0.75
- severity = MINOR
- /// A mapping of `num2text(ITEM_SLOT_XYZ)` -> item path
- var/list/magic_fashion = list()
-
-
-/datum/disease/wizarditis/New()
- . = ..()
-
- var/list/magic_fashion_slot_IDs = list(
- ITEM_SLOT_LEFT_HAND,
- ITEM_SLOT_RIGHT_HAND,
- ITEM_SLOT_HEAD,
- ITEM_SLOT_OUTER_SUIT,
- ITEM_SLOT_SHOES
- )
- var/list/magic_fashion_items = list(
- /obj/item/staff,
- /obj/item/staff,
- /obj/item/clothing/head/wizard,
- /obj/item/clothing/suit/wizrobe,
- /obj/item/clothing/shoes/sandal
- )
- for(var/i in 1 to length(magic_fashion_slot_IDs))
- var/slot = num2text(magic_fashion_slot_IDs[i])
- var/item = magic_fashion_items[i]
- magic_fashion[slot] = item
-
-/datum/disease/wizarditis/stage_act()
- if(!..())
- return FALSE
-
- switch(stage)
- if(2, 3)
- if(prob(2)) // Low prob. since everyone else will also be spouting this
- affected_mob.say(pick("You shall not pass!", "Expeliarmus!", "By Merlin's beard!", "Feel the power of the Dark Side!", "A wizard is never late!", "50 points for Security!", "NEC CANTIO!", "STI KALY!", "AULIE OXIN FIERA!", "GAR YOK!", "DIRI CEL!"))
- if(prob(8)) // Double the stage advancement prob. so each player has a chance to catch a couple
- to_chat(affected_mob, "You feel [pick("that you don't have enough mana", "that the winds of magic are gone", "that this location gives you a +1 to INT", "an urge to summon familiar")].")
- if(4)
- if(prob(1))
- affected_mob.say(pick("FORTI GY AMA!", "GITTAH WEIGH!", "TOKI WO TOMARE!", "TARCOL MINTI ZHERI!", "ONI SOMA!", "EI NATH!", "BIRUZ BENNAR!", "NWOLC EGNEVER!"))
- if(prob(3)) // Last stage, so we'll have plenty of time to show these off even with a lower prob.
- to_chat(affected_mob, "You feel [pick("the tidal wave of raw power building inside", "that this location gives you a +2 to INT and +1 to WIS", "an urge to teleport", "the magic bubbling in your veins", "an urge to summon familiar")].")
- if(prob(3)) // About 1 minute per item on average
- spawn_wizard_clothes()
- if(prob(0.033)) // Assuming 50 infected, someone should teleport every ~2 minutes on average
- teleport()
-
-/datum/disease/wizarditis/proc/spawn_wizard_clothes()
- var/mob/living/carbon/human/H = affected_mob
- if(!istype(H))
- return // Woe, wizard xeno upon ye
-
- // Which slots can we replace?
- var/list/eligible_slot_IDs = list()
- for(var/slot in magic_fashion)
- var/slot_ID = text2num(slot) // Convert back to numeric defines
-
- if((locate(magic_fashion[slot]) in H) || !H.has_organ_for_slot(slot_ID) || !H.canUnEquip(H.get_item_by_slot(slot_ID)))
- continue
-
- switch(slot_ID) // Extra filtering for specific slots
- if(ITEM_SLOT_HEAD)
- if(isplasmaman(H))
- continue // We want them to spread the magical joy, not burn to death in agony
-
- eligible_slot_IDs.Add(slot_ID)
- if(!length(eligible_slot_IDs))
- return
-
- // Pick the magical winner and apply
- var/chosen_slot_ID = pick(eligible_slot_IDs)
- var/chosen_fashion = magic_fashion[num2text(chosen_slot_ID)]
-
- H.drop_item_to_ground(H.get_item_by_slot(chosen_slot_ID))
- var/obj/item/magic_attire = new chosen_fashion
- magic_attire.flags |= DROPDEL
- H.equip_to_slot_or_del(magic_attire, chosen_slot_ID)
-
-/datum/disease/wizarditis/proc/teleport()
- if(!is_teleport_allowed(affected_mob.z))
- return
- if(SEND_SIGNAL(affected_mob, COMSIG_MOVABLE_TELEPORTING, get_turf(affected_mob)) & COMPONENT_BLOCK_TELEPORT)
- return FALSE
-
- var/list/possible_areas = get_areas_in_range(80, affected_mob)
- for(var/area/space/S in possible_areas)
- possible_areas -= S
-
- if(!length(possible_areas))
- return
-
- var/area/chosen_area = pick(possible_areas)
-
- var/list/teleport_turfs = list()
- for(var/turf/T in get_area_turfs(chosen_area.type))
- if(isspaceturf(T))
- continue
- if(!T.density)
- var/clear = TRUE
- for(var/obj/O in T)
- if(O.density)
- clear = FALSE
- break
- if(clear)
- teleport_turfs += T
-
- if(!length(teleport_turfs))
- return
-
- affected_mob.say("SCYAR NILA [uppertext(chosen_area.name)]!")
- affected_mob.forceMove(pick(teleport_turfs))
diff --git a/code/datums/dog_fashion.dm b/code/datums/dog_fashion.dm
deleted file mode 100644
index 2c3832190bb8b..0000000000000
--- a/code/datums/dog_fashion.dm
+++ /dev/null
@@ -1,306 +0,0 @@
-/datum/dog_fashion
- var/name
- var/desc
- var/emote_see
- var/emote_hear
- var/speak
- var/speak_emote
-
- // This isn't applied to the dog, but stores the icon_state of the
- // sprite that the associated item uses
- var/icon_file
- var/obj_icon_state
- var/obj_alpha
- var/obj_color
-
-/datum/dog_fashion/New(mob/M)
- name = replacetext(name, "REAL_NAME", M.real_name)
- desc = replacetext(desc, "NAME", name)
-
-/datum/dog_fashion/proc/apply(mob/living/simple_animal/pet/dog/D)
- if(name)
- D.name = name
- if(desc)
- D.desc = desc
- if(emote_see)
- D.emote_see = emote_see
- if(emote_hear)
- D.emote_hear = emote_hear
- if(speak)
- D.speak = speak
- if(speak_emote)
- D.speak_emote = speak_emote
-
-/datum/dog_fashion/proc/get_overlay(dir)
- if(icon_file && obj_icon_state)
- var/image/corgI = image(icon_file, obj_icon_state, dir = dir)
- corgI.alpha = obj_alpha
- corgI.color = obj_color
- return corgI
-
-
-/datum/dog_fashion/head
- icon_file = 'icons/mob/corgi_head.dmi'
-
-/datum/dog_fashion/back
- icon_file = 'icons/mob/corgi_back.dmi'
-
-/datum/dog_fashion/head/hardhat/apply(mob/living/simple_animal/pet/dog/D)
- ..()
- D.set_light(4)
-
-/datum/dog_fashion/head/hardhat
- name = "Engineer REAL_NAME"
- desc = "Trust him, he's an engineer."
-
-/datum/dog_fashion/head/hardhat/white
- name = "Chief engineer REAL_NAME"
- desc = "Hasn't delamed the engine once."
-
-/datum/dog_fashion/head/hardhat/red
- name = "Fire chief REAL_NAME"
- desc = "Some days you're the dog, some days you're the hydrant."
-
-/datum/dog_fashion/head/helmet
- name = "Sergeant REAL_NAME"
- desc = "The ever-loyal, the ever-vigilant."
-
-/datum/dog_fashion/head/chef
- name = "Sous chef REAL_NAME"
- desc = "Your food will be taste-tested. All of it."
-
-
-/datum/dog_fashion/head/captain
- name = "Captain REAL_NAME"
- desc = "Probably better than the last captain."
-
-/datum/dog_fashion/head/kitty
- name = "Runtime"
- emote_see = list("coughs up a furball", "stretches")
- emote_hear = list("purrs")
- speak = list("Purrr", "Meow!", "MAOOOOOW!", "HISSSSS", "MEEEEEEW")
- desc = "It's a cute little kitty-cat! ... wait ... what the hell?"
-
-/datum/dog_fashion/head/rabbit
- name = "Hoppy"
- emote_see = list("twitches its nose", "hops around a bit")
- desc = "This is Hoppy. It's a corgi-...urmm... bunny rabbit."
-
-/datum/dog_fashion/head/beret
- name = "Yann"
- desc = "Mon dieu! C'est un chien!"
- speak = list("le woof!", "le bark!", "JAPPE!!")
- emote_see = list("cowers in fear.", "surrenders.", "plays dead.","looks as though there is a wall in front of him.")
-
-
-/datum/dog_fashion/head/detective
- name = "Detective REAL_NAME"
- desc = "NAME sees through your lies..."
- emote_see = list("investigates the area.","sniffs around for clues.","searches for scooby snacks.","takes a candycorn from the hat.")
-
-
-/datum/dog_fashion/head/nurse
- name = "Nurse REAL_NAME"
- desc = "NAME needs 100cc of beef jerky... STAT!"
-
-/datum/dog_fashion/head/pirate
- name = "Pirate-title Pirate-name"
- desc = "Yaarghh!! Thar' be a scurvy dog!"
- emote_see = list("hunts for treasure.","stares coldly...","gnashes his tiny corgi teeth!")
- emote_hear = list("growls ferociously!", "snarls.")
- speak = list("Arrrrgh!!","Grrrrrr!")
-
-/datum/dog_fashion/head/pirate/New(mob/M)
- ..()
- name = "[pick("Ol'","Scurvy","Black","Rum","Gammy","Bloody","Gangrene","Death","Long-John")] [pick("kibble","leg","beard","tooth","poop-deck","Threepwood","Le Chuck","corsair","Silver","Crusoe")]"
-
-/datum/dog_fashion/head/ushanka
- name = "Communist-title Realname"
- desc = "A follower of Karl Barx."
- emote_see = list("contemplates the failings of the capitalist economic model.", "ponders the pros and cons of vanguardism.")
-
-/datum/dog_fashion/head/ushanka/New(mob/M)
- ..()
- name = "[pick("Comrade","Commissar","Glorious Leader")] [M.real_name]"
-
-/datum/dog_fashion/head/warden
- name = "Officer REAL_NAME"
- emote_see = list("drools.","looks for donuts.")
- desc = "Stop right there criminal scum!"
-
-/datum/dog_fashion/head/blue_wizard
- name = "Grandwizard REAL_NAME"
- speak = list("YAP", "Woof!", "Bark!", "AUUUUUU", "EI NATH!")
-
-/datum/dog_fashion/head/red_wizard
- name = "Pyromancer REAL_NAME"
- speak = list("YAP", "Woof!", "Bark!", "AUUUUUU", "ONI SOMA!")
-
-/datum/dog_fashion/head/black_wizard
- name = "Necromancer REAL_NAME"
- speak = list("YAP", "Woof!", "Bark!", "AUUUUUU")
-
-// CARDBORG OUTFITS
-/datum/dog_fashion/head/cardborg
- name = "BORGI"
- speak = list("Ping!","Beep!","Woof!")
- emote_see = list("goes rogue.", "sniffs out non-humans.")
- desc = "Result of robotics budget cuts."
-
-/datum/dog_fashion/head/cardborg/security
- name = "SECBORGI"
- speak = list("Ping!", "Beep!", "Woof!", "HALT!", "HALT! HALT! HALT!")
- emote_see = list("goes rogue.", "sniffs out criminals.")
- desc = "Result of robotics budget cuts and a ban on the station having real security cyborgs."
-
-/datum/dog_fashion/head/cardborg/engineering
- name = "ENGI-IAN"
- speak = list("Ping!", "Beep!", "Woof!")
- emote_see = list("goes rogue.", "sniffs for wires.", "looks for an autolathe board.")
- desc = "Result of robotics budget cuts. Knows as much about atmospherics as the average engineer."
-
-/datum/dog_fashion/head/cardborg/mining
- name = "DIGDOG"
- speak = list("Ping!", "Beep!", "Woof!")
- emote_see = list("goes rogue.", "sniffs for ores.", "digs into the floor.")
- desc = "Result of robotics budget cuts. Has dug up more bones than any other miner!"
-
-/datum/dog_fashion/head/cardborg/service
- name = "Service dogbot"
- speak = list("Ping!", "Beep!", "Woof!")
- emote_see = list("goes rogue.")
- desc = "Result of robotics budget cuts. Still about as useful as a real service cyborg..."
-
-/datum/dog_fashion/head/cardborg/medical
- name = "M3D1CAL_IANTERN"
- speak = list("Ping!", "Beep!", "Woof!")
- emote_see = list("goes rogue.", "sniffs out the injured.", "analyses your vitals.")
- desc = "Result of robotics budget cuts. Hopefully medical is more useful."
-
-/datum/dog_fashion/head/cardborg/janitor
- name = "CLE-IAN-G"
- speak = list("Ping!", "Beep!", "Woof!")
- emote_see = list("goes rogue.", "sniffs for messes.", "licks the floor clean.")
- desc = "Result of robotics budget cuts. More pettable than 9 out of 10 janitors."
-
-/datum/dog_fashion/head/cardborg/xeno
- name = "BORGIMORPH"
- speak = list("Ping!", "Beep!", "Woof!", "HISS!", "HISSHISSHISS!")
- emote_see = list("goes rogue.", "hisses.")
- desc = "Result of robotics budget cuts. If this is your last line of defence against a xenomorph outbreak, god help you."
-
-/datum/dog_fashion/head/cardborg/deathbot
- name = "Epsilon-D0G1"
- speak = list("Ping!", "Beep!", "Woof!")
- emote_see = list("goes rogue.", "sniffs out survivors.", "prepares to destroy the station.")
- desc = "Result of robotics budget cuts. Looks just like the cyborg from the Deathsquad TV show!"
-
-/datum/dog_fashion/head/ghost
- name = "\improper Ghost"
- speak = list("WoooOOOooo~","AUUUUUUUUUUUUUUUUUU")
- emote_see = list("stumbles around.", "shivers.")
- emote_hear = list("howls!","groans.")
- desc = "Spooky!"
- obj_icon_state = "sheet"
-
-/datum/dog_fashion/head/santa
- name = "Santa's Corgi Helper"
- emote_hear = list("barks Christmas songs.", "yaps merrily!")
- emote_see = list("looks for presents.", "checks his list.")
- desc = "He's very fond of milk and cookies."
-
-/datum/dog_fashion/head/cargo_tech
- name = "Corgi Tech REAL_NAME"
- desc = "The reason your yellow gloves have chew-marks."
-
-/datum/dog_fashion/head/softcap
-
-/datum/dog_fashion/head/reindeer
- name = "REAL_NAME the red-nosed Corgi"
- emote_hear = list("lights the way!", "illuminates.", "yaps!")
- desc = "He has a very shiny nose."
-
-/datum/dog_fashion/head/reindeer/apply(mob/living/simple_animal/pet/dog/D)
- ..()
- D.set_light(2, 2, LIGHT_COLOR_RED)
-
-/datum/dog_fashion/head/sombrero
- name = "Segnor REAL_NAME"
- desc = "You must respect Elder Dogname."
-
-/datum/dog_fashion/head/sombrero/New(mob/M)
- ..()
- desc = "You must respect Elder [M.real_name]."
-
-/datum/dog_fashion/head/hop
- name = "Lieutenant REAL_NAME"
- desc = "Can actually be trusted to not run off on his own."
-
-/datum/dog_fashion/head/deathsquad
- name = "Trooper REAL_NAME"
- desc = "That's not red paint. That's real corgi blood."
-
-/datum/dog_fashion/head/clown
- name = "REAL_NAME the Clown"
- desc = "Honkman's best friend."
- speak = list("HONK!", "Honk!")
- emote_see = list("plays tricks.", "slips.")
-
-/datum/dog_fashion/back/deathsquad
- name = "Trooper REAL_NAME"
- desc = "That's not red paint. That's real corgi blood."
-
-/datum/dog_fashion/head/not_ian
- name = "Definitely Not REAL_NAME"
- desc = "That's Definitely Not Dogname."
-
-/datum/dog_fashion/head/not_ian/New(mob/M)
- ..()
- desc = "That's Definitely Not [M.real_name]."
-
-/datum/dog_fashion/head/cone
- name = "REAL_NAME"
- desc = "Omnicone's Chosen Champion."
-
-/datum/dog_fashion/head/fried_vox_empty
- name = "Colonel REAL_NAME"
- desc = "Keep away from live vox."
-
-/datum/dog_fashion/head/hos
- name = "Head of Security REAL_NAME"
- desc = "Probably better than the last HoS."
-
-/datum/dog_fashion/head/beret/sec
- name = "Officer REAL_NAME"
- desc = "Ever-loyal, ever-vigilant."
-
-/datum/dog_fashion/head/bowlerhat
- name = "REAL_NAME"
- desc = "A sophisticated city gent."
-
-/datum/dog_fashion/head/surgery
- name = "Nurse-in-Training REAL_NAME"
- desc = "The most adorable bed-side manner ever."
-
-/datum/dog_fashion/head/bucket
- name = "REAL_NAME"
- desc = "A janitor's best friend."
-
-/datum/dog_fashion/head/justice_wig
- name = "Arbiter REAL_NAME"
- desc = "Head of the High Court of Cute."
-
-/datum/dog_fashion/head/wizard/magus
- name = "Battlemage REAL_NAME"
-
-/datum/dog_fashion/head/wizard/marisa
- name = "Witch REAL_NAME"
- desc = "Flying broom not included."
-
-/datum/dog_fashion/head/roman
- name = "Imperator REAL_NAME"
- desc = "For the Senate and the people of Rome!"
-
-/datum/dog_fashion/head/qm
- name = "Supplymaster REAL_NAME"
- desc = "A loyal watchdog for the most secure transportation."
diff --git a/code/datums/elements/connect_loc.dm b/code/datums/elements/connect_loc.dm
deleted file mode 100644
index 6fcc1474679c3..0000000000000
--- a/code/datums/elements/connect_loc.dm
+++ /dev/null
@@ -1,43 +0,0 @@
-/// This element hooks a signal onto the loc the current object is on.
-/// When the object moves, it will unhook the signal and rehook it to the new object.
-/datum/element/connect_loc
- element_flags = ELEMENT_BESPOKE
- argument_hash_start_idx = 2
-
- /// An assoc list of signal -> procpath to register to the loc this object is on.
- var/list/connections
-
-/datum/element/connect_loc/Attach(atom/movable/listener, list/connections)
- . = ..()
- if(!istype(listener))
- return ELEMENT_INCOMPATIBLE
-
- src.connections = connections
-
- RegisterSignal(listener, COMSIG_MOVABLE_MOVED, PROC_REF(on_moved), override = TRUE)
- update_signals(listener)
-
-/datum/element/connect_loc/Detach(atom/movable/listener)
- . = ..()
- unregister_signals(listener, listener.loc)
- UnregisterSignal(listener, COMSIG_MOVABLE_MOVED)
-
-/datum/element/connect_loc/proc/update_signals(atom/movable/listener)
- var/atom/listener_loc = listener.loc
- if(QDELETED(listener) || QDELETED(listener_loc))
- return
-
- for(var/signal in connections)
- //override=TRUE because more than one connect_loc element instance tracked object can be on the same loc
- listener.RegisterSignal(listener_loc, signal, connections[signal], override=TRUE)
-
-/datum/element/connect_loc/proc/unregister_signals(datum/listener, atom/old_loc)
- if(isnull(old_loc))
- return
-
- listener.UnregisterSignal(old_loc, connections)
-
-/datum/element/connect_loc/proc/on_moved(atom/movable/listener, atom/old_loc)
- SIGNAL_HANDLER // COMSIG_MOVABLE_MOVED
- unregister_signals(listener, old_loc)
- update_signals(listener)
diff --git a/code/datums/elements/decal_element.dm b/code/datums/elements/decal_element.dm
deleted file mode 100644
index 1d9cbb15da8c2..0000000000000
--- a/code/datums/elements/decal_element.dm
+++ /dev/null
@@ -1,177 +0,0 @@
-// NOTE:
-// This is an incredibly piecemeal port of /tg/'s decal element.
-// It does not include several pieces of functionality that exist in /tg/.
-//
-// Namely:
-// - It does not support smoothing decals
-// - It does not send a signal when a decal is detached (used for trapdoors on /tg/)
-// - It does not support custom plane configuration as this behavior seems primarily concerned with multi-z
-
-/datum/element/decal
- element_flags = ELEMENT_BESPOKE|ELEMENT_DETACH_ON_HOST_DESTROY
- argument_hash_start_idx = 2
- /// Whether this decal can be cleaned.
- var/cleanable
- /// A description this decal appends to the target's examine message.
- var/description
- /// If true this was initialized with no set direction - will follow the parent dir.
- var/directional
- /// The base icon state that this decal was initialized with.
- var/base_icon_state
- /// The overlay applied by this decal to the target.
- var/mutable_appearance/pic
-
-/datum/element/decal/Attach(atom/target, _icon, _icon_state, _dir, _layer=TURF_LAYER, _alpha=255, _color, _cleanable=FALSE, _description, mutable_appearance/_pic)
- . = ..()
- if(!isatom(target))
- return ELEMENT_INCOMPATIBLE
- if(_pic)
- pic = _pic
- else if(!generate_appearance(_icon, _icon_state, _dir, _layer, _color, _alpha, target))
- return ELEMENT_INCOMPATIBLE
- description = _description
- cleanable = _cleanable
- directional = _dir
- base_icon_state = _icon_state
-
- RegisterSignal(target, COMSIG_ATOM_UPDATE_OVERLAYS, PROC_REF(apply_overlay), TRUE)
- if(target.initialized)
- target.update_appearance(UPDATE_OVERLAYS) //could use some queuing here now maybe.
- else
- RegisterSignal(target, COMSIG_ATOM_AFTER_SUCCESSFUL_INITIALIZE, PROC_REF(late_update_icon), TRUE)
- if(isitem(target))
- INVOKE_ASYNC(target, TYPE_PROC_REF(/obj/item/, update_slot_icon), TRUE)
- if(_dir)
- RegisterSignal(target, list(COMSIG_ATOM_DECALS_ROTATING, COMSIG_ATOM_GET_DECALS), PROC_REF(get_decals), TRUE)
- SSdcs.RegisterSignal(target, COMSIG_ATOM_DIR_CHANGE, TYPE_PROC_REF(/datum/controller/subsystem/processing/dcs, rotate_decals), override=TRUE)
- if(_cleanable)
- RegisterSignal(target, COMSIG_COMPONENT_CLEAN_ACT, PROC_REF(clean_react), TRUE)
- if(_description)
- RegisterSignal(target, COMSIG_PARENT_EXAMINE, PROC_REF(examine), TRUE)
-
- RegisterSignal(target, COMSIG_TURF_ON_SHUTTLE_MOVE, PROC_REF(shuttle_move_react), TRUE)
-
-/// Remove old decals and apply new decals after rotation as necessary
-/datum/controller/subsystem/processing/dcs/proc/rotate_decals(datum/source, old_dir, new_dir)
- SIGNAL_HANDLER // COMSIG_ATOM_DIR_CHANGE
-
- if(old_dir == new_dir)
- return
-
- var/list/datum/element/decal/old_decals = list() //instances
- SEND_SIGNAL(source, COMSIG_ATOM_DECALS_ROTATING, old_decals)
-
- if(!length(old_decals))
- UnregisterSignal(source, COMSIG_ATOM_DIR_CHANGE)
- return
-
- var/list/resulting_decals_params = list() // param lists
- for(var/datum/element/decal/rotating as anything in old_decals)
- resulting_decals_params += list(rotating.get_rotated_parameters(old_dir,new_dir))
-
- //Instead we could generate ids and only remove duplicates to save on churn on four-corners symmetry ?
- for(var/datum/element/decal/decal in old_decals)
- decal.Detach(source)
-
- for(var/result in resulting_decals_params)
- source.AddElement(/datum/element/decal, result["icon"], result["icon_state"], result["dir"], result["layer"], result["alpha"], result["color"], result["cleanable"], result["desc"])
-
-/datum/element/decal/proc/get_rotated_parameters(old_dir,new_dir)
- var/rotation = 0
- if(directional) //Even when the dirs are the same rotation is coming out as not 0 for some reason
- rotation = SIMPLIFY_DEGREES(dir2angle(new_dir)-dir2angle(old_dir))
- new_dir = turn(pic.dir,-rotation)
- return list(
- "icon" = pic.icon,
- "icon_state" = base_icon_state,
- "dir" = new_dir,
- "plane" = pic.plane,
- "layer" = pic.layer,
- "alpha" = pic.alpha,
- "color" = pic.color,
- "cleanable" = cleanable,
- "desc" = description
- )
-
-/datum/element/decal/proc/late_update_icon(atom/source)
- SIGNAL_HANDLER // COMSIG_ATOM_AFTER_SUCCESSFUL_INITIALIZE
-
- if(istype(source) && !(source.flags_2 & DECAL_INIT_UPDATE_EXPERIENCED_2))
- source.flags_2 |= DECAL_INIT_UPDATE_EXPERIENCED_2
- source.update_appearance(UPDATE_OVERLAYS)
- UnregisterSignal(source, COMSIG_ATOM_AFTER_SUCCESSFUL_INITIALIZE)
-
-/**
- * If the decal was not given an appearance, it will generate one based on the other given arguments.
- * element won't be compatible if it cannot do either
- * all args are fed into creating an image, they are byond vars for images you'll recognize in the byond docs
- * (except source, source is the object whose appearance we're copying.)
- */
-/datum/element/decal/proc/generate_appearance(_icon, _icon_state, _dir, _layer, _color, _alpha, source)
- if(!_icon || !_icon_state)
- return FALSE
- var/temp_image = image(_icon, null, _icon_state, _layer, _dir)
- pic = new(temp_image)
- pic.color = _color
- pic.alpha = _alpha
- return TRUE
-
-/datum/element/decal/Detach(atom/source)
- UnregisterSignal(source, list(
- COMSIG_ATOM_DIR_CHANGE,
- COMSIG_COMPONENT_CLEAN_ACT,
- COMSIG_PARENT_EXAMINE,
- COMSIG_ATOM_UPDATE_OVERLAYS,
- COMSIG_TURF_ON_SHUTTLE_MOVE,
- COMSIG_ATOM_DECALS_ROTATING
- ))
- SSdcs.UnregisterSignal(source, COMSIG_ATOM_DIR_CHANGE)
- source.update_appearance(UPDATE_OVERLAYS)
- if(isitem(source))
- INVOKE_ASYNC(source, TYPE_PROC_REF(/obj/item/, update_slot_icon))
- return ..()
-
-/datum/element/decal/proc/apply_overlay(atom/source)
- SIGNAL_HANDLER // COMSIG_ATOM_UPDATE_OVERLAYS
-
- source.add_overlay(pic)
- // TODO: Fix this disgusting hack
- //
- // `COMSIG_ATOM_UPDATE_OVERLAYS` is sent at the end of
- // /atom/proc/update_icon's stanza for updating overlays, instead
- // somewhere useful, like, during it. /tg/ handles this by sending
- // a list of overlays with the signal, allowing receivers to add to
- // the list, instead of returning their own.
- //
- // This is much saner and more flexible, but would require refactoring
- // many many uses of update_overlay() across the code base, which is left
- // as an exercise for the next poor sap to touch this code (probably me).
- if(source.managed_overlays && !islist(source.managed_overlays))
- source.managed_overlays = list(source.managed_overlays, pic)
- else
- LAZYDISTINCTADD(source.managed_overlays, pic)
-
-/datum/element/decal/proc/clean_react(datum/source, clean_types)
- SIGNAL_HANDLER // COMSIG_COMPONENT_CLEAN_ACT
-
- if(clean_types & cleanable)
- Detach(source)
- return COMPONENT_CLEANED
- return NONE
-
-/datum/element/decal/proc/examine(datum/source, mob/user, list/examine_list)
- SIGNAL_HANDLER // COMSIG_PARENT_EXAMINE
-
- examine_list += description
-
-/datum/element/decal/proc/shuttle_move_react(datum/source, turf/new_turf)
- SIGNAL_HANDLER // COMSIG_TURF_ON_SHUTTLE_MOVE
-
- if(new_turf == source)
- return
- Detach(source)
- new_turf.AddElement(type, pic.icon, base_icon_state, directional, pic.layer, pic.alpha, pic.color, cleanable, description)
-
-/datum/element/decal/proc/get_decals(datum/source, list/datum/element/decal/rotating)
- SIGNAL_HANDLER // COMSIG_ATOM_DECALS_ROTATING
- rotating += src
diff --git a/code/datums/elements/high_value_item.dm b/code/datums/elements/high_value_item.dm
deleted file mode 100644
index dd34bafa096e0..0000000000000
--- a/code/datums/elements/high_value_item.dm
+++ /dev/null
@@ -1,22 +0,0 @@
-GLOBAL_LIST_EMPTY(high_value_items)
-
-/datum/element/high_value_item
- element_flags = ELEMENT_DETACH_ON_HOST_DESTROY
-
-/datum/element/high_value_item/Attach(datum/target)
- . = ..()
- if(!isatom(target))
- return ELEMENT_INCOMPATIBLE
- GLOB.high_value_items |= target
-
-/datum/element/high_value_item/Detach(datum/source, force)
- . = ..()
- var/turf/turf_loc = get_turf(source)
- if(turf_loc)
- message_admins("[source] has been destroyed in [get_area(turf_loc)] at [ADMIN_COORDJMP(turf_loc)].")
- log_game("[source] has been destroyed at ([turf_loc.x],[turf_loc.y],[turf_loc.z]) in the location [turf_loc.loc].")
- else
- message_admins("[source] has been destroyed in nullspace.")
- log_game("[source] has been destroyed in nullspace.")
- GLOB.high_value_items -= source
-
diff --git a/code/datums/elements/rust_element.dm b/code/datums/elements/rust_element.dm
deleted file mode 100644
index d2464e016df77..0000000000000
--- a/code/datums/elements/rust_element.dm
+++ /dev/null
@@ -1,123 +0,0 @@
-/**
- * Adding this element to an atom will have it automatically render an overlay.
- */
-/datum/element/rust
- element_flags = ELEMENT_BESPOKE | ELEMENT_DETACH_ON_HOST_DESTROY // Detach for turfs
- argument_hash_start_idx = 2
- /// The rust image itself, since the icon and icon state are only used as an argument
- var/image/rust_overlay
-
-/datum/element/rust/Attach(atom/target, rust_icon = 'icons/effects/rust_overlay.dmi', rust_icon_state = "rust_default")
- . = ..()
- if(!isatom(target))
- return ELEMENT_INCOMPATIBLE
-
- rust_overlay = image(rust_icon, "rust[rand(1, 6)]")
- ADD_TRAIT(target, TRAIT_RUSTY, "rusted_turf")
- RegisterSignal(target, COMSIG_ATOM_UPDATE_OVERLAYS, PROC_REF(apply_rust_overlay))
- RegisterSignal(target, COMSIG_PARENT_EXAMINE, PROC_REF(handle_examine))
- RegisterSignal(target, COMSIG_INTERACT_TARGET, PROC_REF(on_interaction))
- RegisterSignal(target, COMSIG_TOOL_ATTACK, PROC_REF(welder_tool_act))
- // Unfortunately registering with parent sometimes doesn't cause an overlay update
- target.update_appearance()
-
-/datum/element/rust/Detach(atom/source)
- . = ..()
- UnregisterSignal(source, COMSIG_ATOM_UPDATE_OVERLAYS)
- UnregisterSignal(source, COMSIG_PARENT_EXAMINE)
- UnregisterSignal(source, COMSIG_TOOL_ATTACK)
- UnregisterSignal(source, COMSIG_INTERACT_TARGET)
- REMOVE_TRAIT(source, TRAIT_RUSTY, "rusted_turf")
- source.cut_overlays()
- source.update_appearance()
-
-/datum/element/rust/proc/handle_examine(datum/source, mob/user, list/examine_list)
- SIGNAL_HANDLER //COMSIG_PARENT_EXAMINE
-
- examine_list += "[source] is very rusty, you could probably burn it off."
-
-/datum/element/rust/proc/apply_rust_overlay(atom/parent_atom, list/overlays)
- SIGNAL_HANDLER //COMSIG_ATOM_UPDATE_OVERLAYS
-
- if(rust_overlay)
- parent_atom.add_overlay(rust_overlay)
-
-/// Because do_after sleeps we register the signal here and defer via an async call
-/datum/element/rust/proc/welder_tool_act(atom/source, obj/item/item, mob/user)
- SIGNAL_HANDLER // COMSIG_TOOL_ATTACK
-
- INVOKE_ASYNC(src, PROC_REF(handle_tool_use), source, item, user)
- return COMPONENT_CANCEL_TOOLACT
-
-/// We call this from secondary_tool_act because we sleep with do_after
-/datum/element/rust/proc/handle_tool_use(atom/source, obj/item/item, mob/user)
- switch(item.tool_behaviour)
- if(TOOL_WELDER)
- if(!item.tool_start_check(source, user, amount=1))
- return
- to_chat(user, "You start burning off the rust...")
-
- if(!item.use_tool(source, user, 5 SECONDS, volume = item.tool_volume))
- return
- to_chat(user, "You burn off the rust!")
- Detach(source)
- return
-
-/// Prevents placing floor tiles on rusted turf
-/datum/element/rust/proc/on_interaction(datum/source, mob/living/user, obj/item/tool, list/modifiers)
- SIGNAL_HANDLER // COMSIG_INTERACT_TARGET
- if(istype(tool, /obj/item/stack/tile) || istype(tool, /obj/item/stack/rods) || istype(tool, /obj/item/rcd))
- to_chat(user, "[source] is too rusted to build on!")
- return ITEM_INTERACT_COMPLETE
-
-/// For rust applied by heretics (if that ever happens) / revenants
-/datum/element/rust/heretic
-
-/datum/element/rust/heretic/Attach(atom/target, rust_icon, rust_icon_state)
- . = ..()
- if(. == ELEMENT_INCOMPATIBLE)
- return .
- RegisterSignal(target, COMSIG_ATOM_ENTERED, PROC_REF(on_entered))
- RegisterSignal(target, COMSIG_ATOM_EXITED, PROC_REF(on_exited))
-
-/datum/element/rust/heretic/Detach(atom/source)
- . = ..()
- UnregisterSignal(source, COMSIG_ATOM_ENTERED)
- UnregisterSignal(source, COMSIG_ATOM_EXITED)
- for(var/obj/effect/glowing_rune/rune_to_remove in source)
- qdel(rune_to_remove)
- for(var/mob/living/victim in source)
- victim.remove_status_effect(STATUS_EFFECT_RUST_CORRUPTION)
-
-/datum/element/rust/heretic/proc/on_entered(turf/source, atom/movable/entered, ...)
- SIGNAL_HANDLER
-
- if(!isliving(entered))
- return
- var/mob/living/victim = entered
- if(istype(victim, /mob/living/simple_animal/revenant))
- return
- victim.apply_status_effect(STATUS_EFFECT_RUST_CORRUPTION)
-
-/datum/element/rust/heretic/proc/on_exited(turf/source, atom/movable/gone)
- SIGNAL_HANDLER
- if(!isliving(gone))
- return
- var/mob/living/leaver = gone
- leaver.remove_status_effect(STATUS_EFFECT_RUST_CORRUPTION)
-
-// Small visual effect imparted onto rusted things by revenants.
-/obj/effect/glowing_rune
- icon = 'icons/effects/eldritch.dmi'
- icon_state = "small_rune_1"
- anchored = TRUE
- plane = FLOOR_PLANE
- layer = SIGIL_LAYER
- mouse_opacity = MOUSE_OPACITY_TRANSPARENT
-
-/obj/effect/glowing_rune/Initialize(mapload)
- . = ..()
- pixel_y = rand(-6, 6)
- pixel_x = rand(-6, 6)
- icon_state = "small_rune_[rand(1, 12)]"
- update_appearance()
diff --git a/code/datums/elements/shatters_when_thrown.dm b/code/datums/elements/shatters_when_thrown.dm
deleted file mode 100644
index 06cdc23dd65b8..0000000000000
--- a/code/datums/elements/shatters_when_thrown.dm
+++ /dev/null
@@ -1,55 +0,0 @@
-/**
- * When attached to something, will make that thing shatter into shards on throw impact or z level falling
- */
-/datum/element/shatters_when_thrown
- element_flags = ELEMENT_BESPOKE
- argument_hash_start_idx = 2
- /// What type of item is spawned as a 'shard' once the shattering happens
- var/obj/item/shard_type
- /// How many shards total are made when the thing we're attached to shatters
- var/number_of_shards
- /// What sound plays when the thing we're attached to shatters
- var/shattering_sound
-
-/datum/element/shatters_when_thrown/Attach(datum/target, shard_type = /obj/item/shard, number_of_shards = 5, shattering_sound = "shatter")
- . = ..()
-
- if(!ismovable(target))
- return ELEMENT_INCOMPATIBLE
-
- src.shard_type = shard_type
- src.number_of_shards = number_of_shards
- src.shattering_sound = shattering_sound
-
- RegisterSignal(target, COMSIG_MOVABLE_IMPACT, PROC_REF(on_throw_impact))
-
-/datum/element/shatters_when_thrown/Detach(datum/target)
- . = ..()
-
- UnregisterSignal(target,COMSIG_MOVABLE_IMPACT)
-
-/// Tells the parent to shatter if we are thrown and impact something
-/datum/element/shatters_when_thrown/proc/on_throw_impact(datum/source, atom/hit_atom)
- SIGNAL_HANDLER
- INVOKE_ASYNC(src, PROC_REF(shatter), source, hit_atom)
-
-/// Handles the actual shattering part, throwing shards of whatever is defined on the component everywhere
-/datum/element/shatters_when_thrown/proc/shatter(atom/movable/source, atom/hit_atom)
- var/generator/scatter_gen = generator(GEN_CIRCLE, 0, 48, NORMAL_RAND)
- var/scatter_turf = get_turf(hit_atom)
-
- for(var/obj/item/scattered_item as anything in source.contents)
- scattered_item.forceMove(scatter_turf)
- var/list/scatter_vector = scatter_gen.Rand()
- scattered_item.pixel_x = scatter_vector[1]
- scattered_item.pixel_y = scatter_vector[2]
-
- for(var/iteration in 1 to number_of_shards)
- var/obj/item/shard = new shard_type(scatter_turf)
- shard.scatter_atom()
- playsound(scatter_turf, shattering_sound, 60, TRUE)
- if(isobj(source))
- var/obj/obj_source = source
- obj_source.deconstruct(FALSE)
- return
- qdel(source)
diff --git a/code/datums/elements/strippable.dm b/code/datums/elements/strippable.dm
deleted file mode 100644
index d30135dcf2aff..0000000000000
--- a/code/datums/elements/strippable.dm
+++ /dev/null
@@ -1,502 +0,0 @@
-#define SHOW_MINIATURE_MENU 0
-#define SHOW_FULLSIZE_MENU 1
-/// An element for atoms that, when dragged and dropped onto a mob, opens a strip panel.
-/datum/element/strippable
- element_flags = ELEMENT_BESPOKE | ELEMENT_DETACH_ON_HOST_DESTROY
- argument_hash_start_idx = 2
-
- /// An assoc list of keys to /datum/strippable_item
- var/list/items
-
- /// An existing strip menus
- var/list/strip_menus
-
-/datum/element/strippable/Attach(datum/target, list/items = list())
- . = ..()
- if(!isatom(target))
- return ELEMENT_INCOMPATIBLE
-
- RegisterSignal(target, COMSIG_DO_MOB_STRIP, PROC_REF(mouse_drop_onto))
-
- src.items = items
-
-/datum/element/strippable/Detach(datum/source)
- . = ..()
-
- UnregisterSignal(source, COMSIG_DO_MOB_STRIP)
-
- if(!isnull(strip_menus))
- qdel(strip_menus[source])
- strip_menus -= source
-
-/datum/element/strippable/proc/mouse_drop_onto(datum/source, atom/over, mob/user)
- SIGNAL_HANDLER
-
- if(user == source)
- return
-
- if(over != user)
- return
-
- var/datum/strip_menu/strip_menu = LAZYACCESS(strip_menus, source)
-
- if(isnull(strip_menu))
- strip_menu = new(source, src)
- LAZYSET(strip_menus, source, strip_menu)
-
- INVOKE_ASYNC(strip_menu, TYPE_PROC_REF(/datum, ui_interact), user)
-
-/// A representation of an item that can be stripped down
-/datum/strippable_item
- /// The STRIPPABLE_ITEM_* key
- var/key
-
-
-/// Gets the item from the given source.
-/datum/strippable_item/proc/get_item(atom/source)
- return
-
-/// Tries to equip the item onto the given source.
-/// Returns TRUE/FALSE depending on if it is allowed.
-/// This should be used for checking if an item CAN be equipped.
-/// It should not perform the equipping itself.
-/datum/strippable_item/proc/try_equip(atom/source, obj/item/equipping, mob/user)
- if(equipping.flags & NODROP)
- to_chat(user, "You can't put [equipping] on [source], it's stuck to your hand!")
- return FALSE
-
- if(equipping.flags & ABSTRACT)
- return FALSE //I don't know a sane-sounding feedback message for trying to put a slap into someone's hand
-
- return TRUE
-
-/// Start the equipping process. This is the proc you should yield in.
-/// Returns TRUE/FALSE depending on if it is allowed.
-/datum/strippable_item/proc/start_equip(atom/source, obj/item/equipping, mob/user)
- if(!in_thief_mode(user))
- source.visible_message(
- "[user] tries to put [equipping] on [source].",
- "[user] tries to put [equipping] on you.",
- )
- if(ishuman(source))
- var/mob/living/carbon/human/victim_human = source
- if(!victim_human.has_vision())
- to_chat(victim_human, "You feel someone trying to put something on you.")
-
- if(!do_mob(user, source, equipping.put_on_delay))
- return FALSE
-
- if(QDELETED(equipping) || !user.Adjacent(source) || (equipping.flags & NODROP))
- return FALSE
-
- return TRUE
-
-/// The proc that places the item on the source. This should not yield.
-/datum/strippable_item/proc/finish_equip(atom/source, obj/item/equipping, mob/user)
- SHOULD_NOT_SLEEP(TRUE)
- return
-
-/// Tries to unequip the item from the given source.
-/// Returns TRUE/FALSE depending on if it is allowed.
-/// This should be used for checking if it CAN be unequipped.
-/// It should not perform the unequipping itself.
-/datum/strippable_item/proc/try_unequip(atom/source, mob/user)
- SHOULD_NOT_SLEEP(TRUE)
-
- var/obj/item/item = get_item(source)
- if(isnull(item))
- return FALSE
-
- if(ismob(source))
- var/mob/mob_source = source
- if(!item.canStrip(user, mob_source))
- return FALSE
-
- return TRUE
-
-/// Start the unequipping process. This is the proc you should yield in.
-/// Returns TRUE/FALSE depending on if it is allowed.
-/datum/strippable_item/proc/start_unequip(atom/source, mob/user)
- var/obj/item/item = get_item(source)
- if(isnull(item))
- return FALSE
-
- to_chat(user, "You try to remove [source]'s [item.name]...")
- add_attack_logs(user, source, "Attempting stripping of [item]")
- item.add_fingerprint(user)
-
- if(!in_thief_mode(user))
- source.visible_message(
- "[user] tries to remove [source]'s [item.name].",
- "[user] tries to remove your [item.name].",
- "You hear rustling."
- )
- if(ishuman(source))
- var/mob/living/carbon/human/victim_human = source
- if(!victim_human.has_vision())
- to_chat(source, "You feel someone fumble with your belongings.")
-
- return start_unequip_mob(get_item(source), source, user)
-
-/// The proc that unequips the item from the source. This should not yield.
-/datum/strippable_item/proc/finish_unequip(atom/source, mob/user)
- SHOULD_NOT_SLEEP(TRUE)
- return
-
-/// Returns a STRIPPABLE_OBSCURING_* define to report on whether or not this is obscured.
-/datum/strippable_item/proc/get_obscuring(atom/source)
- SHOULD_NOT_SLEEP(TRUE)
- return STRIPPABLE_OBSCURING_NONE
-
-/// Returns the ID of this item's strippable action.
-/// Return `null` if there is no alternate action.
-/// Any return value of this must be in StripMenu.
-/datum/strippable_item/proc/get_alternate_actions(atom/source, mob/user)
- return null
-
-/**
- * Actions that can happen to that body part, regardless if there is an item or not. As long as it is not obscured
- */
-/datum/strippable_item/proc/get_body_action(atom/source, mob/user)
- return
-
-/// Performs an alternative action on this strippable_item.
-/// `has_alternate_action` needs to be TRUE.
-/// Returns FALSE if blocked by signal, TRUE otherwise.
-/datum/strippable_item/proc/alternate_action(atom/source, mob/user, action_key)
- SHOULD_CALL_PARENT(TRUE)
- return TRUE
-
-/// Returns whether or not this item should show.
-/datum/strippable_item/proc/should_show(atom/source, mob/user)
- return TRUE
-
-/// Returns whether the user is in "thief mode" where stripping/equipping is silent and stealing from pockets moves stuff to your hands
-/datum/strippable_item/proc/in_thief_mode(mob/user)
- if(!ishuman(user))
- return FALSE
- var/mob/living/carbon/human/H = usr
- var/obj/item/clothing/gloves/G = H.gloves
- return G?.pickpocket
-
-/// A preset for equipping items onto mob slots
-/datum/strippable_item/mob_item_slot
- /// The ITEM_SLOT_* to equip to.
- var/item_slot
-
-/datum/strippable_item/mob_item_slot/get_item(atom/source)
- if(!ismob(source))
- return null
-
- var/mob/mob_source = source
- return mob_source.get_item_by_slot(item_slot)
-
-/datum/strippable_item/mob_item_slot/try_equip(atom/source, obj/item/equipping, mob/user)
- . = ..()
- if(!.)
- return
-
- if(!ismob(source))
- return FALSE
-
- if(!equipping.mob_can_equip(source, item_slot, disable_warning = TRUE))
- to_chat(user, "\The [equipping] doesn't fit in that place!")
- return FALSE
-
- return TRUE
-
-/datum/strippable_item/mob_item_slot/start_equip(atom/source, obj/item/equipping, mob/user)
- . = ..()
- if(!.)
- return
-
- if(!ismob(source))
- return FALSE
-
- if(!equipping.mob_can_equip(source, item_slot, disable_warning = TRUE))
- return FALSE
-
- return TRUE
-
-/datum/strippable_item/mob_item_slot/finish_equip(atom/source, obj/item/equipping, mob/user)
- if(!ismob(source))
- return FALSE
-
- var/mob/mob_source = source
- mob_source.equip_to_slot(equipping, item_slot)
-
- add_attack_logs(user, source, "Strip equipped [equipping]")
-
-/datum/strippable_item/mob_item_slot/get_obscuring(atom/source)
- if(ishuman(source))
- var/mob/living/carbon/human/human_source = source
- if(item_slot & human_source.check_obscured_slots())
- return STRIPPABLE_OBSCURING_COMPLETELY
- return STRIPPABLE_OBSCURING_NONE
-
- return FALSE
-
-/datum/strippable_item/mob_item_slot/finish_unequip(atom/source, mob/user)
- var/obj/item/item = get_item(source)
- if(isnull(item))
- return FALSE
-
- if(!ismob(source))
- return FALSE
-
- INVOKE_ASYNC(GLOBAL_PROC, GLOBAL_PROC_REF(finish_unequip_mob), item, source, user)
- if(in_thief_mode(user))
- INVOKE_ASYNC(user, TYPE_PROC_REF(/mob, put_in_hands), item)
-
-/// Returns the delay of equipping this item to a mob
-/datum/strippable_item/mob_item_slot/proc/get_equip_delay(obj/item/equipping)
- return equipping.put_on_delay
-
-/// A utility function for `/datum/strippable_item`s to start unequipping an item from a mob.
-/proc/start_unequip_mob(obj/item/item, mob/source, mob/user, strip_delay)
- if(!strip_delay)
- strip_delay = item.strip_delay
- if(!do_mob(user, source, strip_delay))
- return FALSE
-
- return TRUE
-
-/// A utility function for `/datum/strippable_item`s to finish unequipping an item from a mob.
-/proc/finish_unequip_mob(obj/item/item, mob/source, mob/user)
- if(!source.drop_item_to_ground(item))
- return
-
- add_attack_logs(user, source, "Stripping of [item]")
-
-/// A representation of the stripping UI
-/datum/strip_menu
- /// The owner who has the element /datum/element/strippable
- var/atom/movable/owner
-
- /// The strippable element itself
- var/datum/element/strippable/strippable
-
- /// A lazy list of user mobs to a list of strip menu keys that they're interacting with
- var/list/interactions
-
- /// Associated list of "[icon][icon_state]" = base64 representation of icon. Used for PERFORMANCE.
- var/static/list/base64_cache = list()
-
-/datum/strip_menu/New(atom/movable/owner, datum/element/strippable/strippable)
- . = ..()
- src.owner = owner
- src.strippable = strippable
-
-/datum/strip_menu/Destroy()
- owner = null
- strippable = null
-
- return ..()
-
-/datum/strip_menu/ui_interact(mob/user, datum/tgui/ui)
- ui = SStgui.try_update_ui(user, src, ui)
- if(!ui)
- ui = new(user, src, "StripMenu")
- ui.open()
-
-/datum/strip_menu/ui_assets(mob/user)
- return list(
- get_asset_datum(/datum/asset/simple/inventory),
- )
-
-/datum/strip_menu/ui_data(mob/user)
- var/list/data = list()
-
- var/list/items = list()
-
- for(var/strippable_key in strippable.items)
- var/datum/strippable_item/item_data = strippable.items[strippable_key]
-
- if(!item_data.should_show(owner, user))
- continue
-
- var/list/result
-
- var/obj/item/item = item_data.get_item(owner)
- if(item && (item.flags & ABSTRACT || HAS_TRAIT(item, TRAIT_NO_STRIP) || HAS_TRAIT(item, TRAIT_SKIP_EXAMINE)))
- items[strippable_key] = result
- continue
-
- if(strippable_key in LAZYACCESS(interactions, user))
- LAZYSET(result, "interacting", TRUE)
-
- var/obscuring = item_data.get_obscuring(owner)
- if(obscuring == STRIPPABLE_OBSCURING_COMPLETELY || (item && !item.canStrip(user)))
- LAZYSET(result, "cantstrip", TRUE)
-
- if(obscuring != STRIPPABLE_OBSCURING_NONE)
- LAZYSET(result, "obscured", obscuring)
- items[strippable_key] = result
- continue
-
- var/alternates = item_data.get_body_action(owner, user)
- if(!islist(alternates) && !isnull(alternates))
- alternates = list(alternates)
-
- if(isnull(item))
- if(length(alternates))
- LAZYSET(result, "alternates", alternates)
- items[strippable_key] = result
- continue
-
- LAZYINITLIST(result)
-
- var/key = "[item.icon],[item.icon_state]"
- if(!(key in base64_cache))
- base64_cache[key] = icon2base64(icon(item.icon, item.icon_state, dir = SOUTH, frame = 1, moving = FALSE))
- result["icon"] = base64_cache[key]
- result["name"] = item.name
-
- var/real_alts = item_data.get_alternate_actions(owner, user)
- if(!isnull(real_alts))
- if(islist(alternates))
- alternates += real_alts
- else
- alternates = real_alts
- if(!islist(alternates) && !isnull(alternates))
- alternates = list(alternates)
- result["alternates"] = alternates
-
- items[strippable_key] = result
-
- data["items"] = items
-
- // While most `\the`s are implicit, this one is not.
- // In this case, `\The` would otherwise be used.
- // This doesn't match with what it's used for, which is to say "Stripping the alien drone",
- // as opposed to "Stripping The alien drone".
- // Human names will still show without "the", as they are proper nouns.
- data["name"] = "\the [owner]"
- data["show_mode"] = user.client.prefs.toggles2 & PREFTOGGLE_2_BIG_STRIP_MENU ? SHOW_FULLSIZE_MENU : SHOW_MINIATURE_MENU
-
- return data
-
-/datum/strip_menu/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
- . = ..()
- if(.)
- return
-
- var/mob/living/user = ui.user
- if(!isliving(ui.user) || !HAS_TRAIT(user, TRAIT_CAN_STRIP))
- return
-
- . = TRUE
- switch(action)
- if("use")
- var/key = params["key"]
- var/datum/strippable_item/strippable_item = strippable.items[key]
-
- if(isnull(strippable_item))
- return
-
- if(!strippable_item.should_show(owner, user))
- return
-
- if(strippable_item.get_obscuring(owner) == STRIPPABLE_OBSCURING_COMPLETELY)
- return
-
- var/item = strippable_item.get_item(owner)
- if(isnull(item))
- var/obj/item/held_item = user.get_active_hand()
- if(isnull(held_item))
- return
-
- if(strippable_item.try_equip(owner, held_item, user))
- LAZYORASSOCLIST(interactions, user, key)
-
- // Update just before the delay starts
- SStgui.update_uis(src)
- // Yielding call
- var/should_finish = strippable_item.start_equip(owner, held_item, user)
-
- LAZYREMOVEASSOC(interactions, user, key)
-
- if(!should_finish)
- return
-
- if(QDELETED(src) || QDELETED(owner))
- return
-
- // They equipped an item in the meantime, or they're no longer adjacent
- if(!isnull(strippable_item.get_item(owner)) || !user.Adjacent(owner))
- return
-
- // make sure to drop the item
- if(!user.drop_item_to_ground(held_item))
- return
-
- strippable_item.finish_equip(owner, held_item, user)
- else if(strippable_item.try_unequip(owner, user))
- LAZYORASSOCLIST(interactions, user, key)
-
- // Update just before the delay starts
- SStgui.update_uis(src)
- var/should_unequip = strippable_item.start_unequip(owner, user)
-
- LAZYREMOVEASSOC(interactions, user, key)
-
- // Yielding call
- if(!should_unequip)
- return
-
- if(QDELETED(src) || QDELETED(owner))
- return
-
- // They changed the item in the meantime
- if(strippable_item.get_item(owner) != item)
- return
-
- if(!user.Adjacent(owner))
- return
-
- strippable_item.finish_unequip(owner, user)
- if("alt")
- var/key = params["key"]
- var/datum/strippable_item/strippable_item = strippable.items[key]
-
- if(isnull(strippable_item))
- return
-
- if(!strippable_item.should_show(owner, user))
- return
-
- if(strippable_item.get_obscuring(owner) == STRIPPABLE_OBSCURING_COMPLETELY)
- return
-
- if(isnull(strippable_item.get_body_action(owner, user)))
- var/item = strippable_item.get_item(owner)
- if(isnull(item) || isnull(strippable_item.get_alternate_actions(owner, user)))
- return
-
- LAZYORASSOCLIST(interactions, user, key)
-
- // Update just before the delay starts
- SStgui.update_uis(src)
- // Potentially yielding
- strippable_item.alternate_action(owner, user, params["action_key"])
-
- LAZYREMOVEASSOC(interactions, user, key)
-
-/datum/strip_menu/ui_host(mob/user)
- return owner
-
-/datum/strip_menu/ui_state(mob/user)
- return GLOB.strippable_state
-
-/// Creates an assoc list of keys to /datum/strippable_item
-/proc/create_strippable_list(types)
- var/list/strippable_items = list()
-
- for(var/strippable_type in types)
- var/datum/strippable_item/strippable_item = new strippable_type
- strippable_items[strippable_item.key] = strippable_item
-
- return strippable_items
-
-#undef SHOW_MINIATURE_MENU
-#undef SHOW_FULLSIZE_MENU
diff --git a/code/datums/helper_datums/map_template.dm b/code/datums/helper_datums/map_template.dm
deleted file mode 100644
index 08437daa6097c..0000000000000
--- a/code/datums/helper_datums/map_template.dm
+++ /dev/null
@@ -1,188 +0,0 @@
-/datum/map_template
- var/name = "Default Template Name"
- var/width = 0
- var/height = 0
- var/mappath = null
- var/mapfile = null
- var/loaded = 0 // Times loaded this round
- /// Do we exclude this from CI checks? If so, set this to the templates pathtype itself to avoid it getting passed down
- var/ci_exclude = null // DO NOT SET THIS IF YOU DO NOT KNOW WHAT YOU ARE DOING
-
-/datum/map_template/New(path = null, map = null, rename = null)
- if(path)
- mappath = path
- if(mappath)
- preload_size(mappath)
- if(map)
- mapfile = map
- if(rename)
- name = rename
-
-/datum/map_template/proc/preload_size(path)
- var/bounds = GLOB.maploader.load_map(file(path), 1, 1, 1, shouldCropMap = FALSE, measureOnly = TRUE)
- if(bounds)
- width = bounds[MAP_MAXX] // Assumes all templates are rectangular, have a single Z level, and begin at 1,1,1
- height = bounds[MAP_MAXY]
- return bounds
-
-/datum/map_template/proc/load(turf/T, centered = 0)
- var/turf/placement = T
- var/min_x = placement.x
- var/min_y = placement.y
- if(centered)
- min_x -= round(width/2)
- min_y -= round(height/2)
-
- var/max_x = min_x + width - 1
- var/max_y = min_y + height - 1
-
- if(!T)
- return 0
-
- var/turf/bot_left = locate(max(1, min_x), max(1, min_y), placement.z)
- var/turf/top_right = locate(min(world.maxx, max_x), min(world.maxy, max_y), placement.z)
-
- // 1 bigger, to update the turf smoothing
- var/turf/ST_bot_left = locate(max(1, min_x-1), max(1, min_y-1), placement.z)
- var/turf/ST_top_right = locate(min(world.maxx, max_x+1), min(world.maxy, max_y+1), placement.z)
- // This is to place a freeze on initialization until the map's done loading
- // otherwise atmos and stuff will start running mid-load
- // This system will metaphorically snap in half (not postpone init everywhere)
- // if given a multi-z template
- // it might need to be adapted for that when that time comes
- GLOB.space_manager.add_dirt(placement.z)
- try
- var/list/bounds = GLOB.maploader.load_map(get_file(), min_x, min_y, placement.z, shouldCropMap = TRUE)
- if(!bounds)
- return 0
- if(bot_left == null || top_right == null)
- stack_trace("One of the late setup corners is bust")
-
- if(ST_bot_left == null || ST_top_right == null)
- stack_trace("One of the smoothing corners is bust")
- catch(var/exception/e)
- GLOB.space_manager.remove_dirt(placement.z)
- var/datum/milla_safe_must_sleep/late_setup_level/milla = new()
- milla.invoke_async(bot_left, top_right, block(ST_bot_left, ST_top_right))
- message_admins("Map template [name] threw an error while loading. Safe exit attempted, but check for errors at [ADMIN_COORDJMP(placement)].")
- log_admin("Map template [name] threw an error while loading. Safe exit attempted.")
- throw e
- GLOB.space_manager.remove_dirt(placement.z)
- var/datum/milla_safe_must_sleep/late_setup_level/milla = new()
- milla.invoke_async(bot_left, top_right, block(ST_bot_left, ST_top_right))
-
- log_game("[name] loaded at [min_x],[min_y],[placement.z]")
- return 1
-
-/datum/map_template/proc/get_file()
- if(mapfile)
- . = mapfile
- else if(mappath)
- . = wrap_file(mappath)
-
- if(!.)
- stack_trace(" The file of [src] appears to be empty/non-existent.")
-
-/datum/map_template/proc/get_affected_turfs(turf/T, centered = 0)
- var/list/coordinate_bounds = get_coordinate_bounds(T, centered)
- var/datum/coords/bottom_left = coordinate_bounds["bottom_left"]
- var/datum/coords/top_right = coordinate_bounds["top_right"]
- return block(max(bottom_left.x_pos, 1), max(bottom_left.y_pos, 1), T.z, min(top_right.x_pos, world.maxx), min(top_right.y_pos, world.maxy), T.z)
-
-/datum/map_template/proc/get_coordinate_bounds(turf/T, centered = FALSE)
- var/turf/placement = T
- var/min_x = placement.x
- var/min_y = placement.y
- if(centered)
- min_x -= round(width/2)
- min_y -= round(height/2)
-
- var/max_x = min_x + width-1
- var/max_y = min_y + height-1
-
- var/datum/coords/bottom_left = new(min_x, min_y, 1)
- var/datum/coords/top_right = new(max_x, max_y, 1)
- return list("bottom_left" = bottom_left, "top_right" = top_right)
-
-/datum/map_template/proc/fits_in_map_bounds(turf/T, centered = 0)
- var/turf/placement = T
- var/min_x = placement.x
- var/min_y = placement.y
- if(centered)
- min_x -= round(width/2)
- min_y -= round(height/2)
-
- var/max_x = min_x + width-1
- var/max_y = min_y + height-1
- if(min_x < 1 || min_y < 1 || max_x > world.maxx || max_y > world.maxy)
- return FALSE
- else
- return TRUE
-
-
-/proc/preloadTemplates(path = "_maps/map_files/templates/") //see master controller setup
- for(var/map in flist(path))
- if(cmptext(copytext(map, length(map) - 3), ".dmm"))
- var/datum/map_template/T = new(path = "[path][map]", rename = "[map]")
- GLOB.map_templates[T.name] = T
-
- if(GLOB.configuration.ruins.enable_space_ruins) // so we don't unnecessarily clutter start-up
- preloadRuinTemplates()
- preloadShelterTemplates()
- preloadShuttleTemplates()
- preloadEventTemplates()
-
-/proc/preloadRuinTemplates()
- // Merge the active lists together
- var/list/space_ruins = GLOB.configuration.ruins.active_space_ruins.Copy()
- var/list/lava_ruins = GLOB.configuration.ruins.active_lava_ruins.Copy()
- var/list/all_ruins = space_ruins | lava_ruins
-
- for(var/item in subtypesof(/datum/map_template/ruin))
- var/datum/map_template/ruin/ruin_type = item
- // screen out the abstract subtypes
- if(!initial(ruin_type.id))
- continue
- var/datum/map_template/ruin/R = new ruin_type()
-
- // If not in the active list, skip it
- if(!(R.mappath in all_ruins))
- continue
-
- GLOB.map_templates[R.name] = R
-
- if(istype(R, /datum/map_template/ruin/lavaland))
- GLOB.lava_ruins_templates[R.name] = R
- if(istype(R, /datum/map_template/ruin/space))
- GLOB.space_ruins_templates[R.name] = R
-
-/proc/preloadShelterTemplates()
- for(var/item in subtypesof(/datum/map_template/shelter))
- var/datum/map_template/shelter/shelter_type = item
- if(!(initial(shelter_type.mappath)))
- continue
- var/datum/map_template/shelter/S = new shelter_type()
-
- GLOB.shelter_templates[S.shelter_id] = S
- GLOB.map_templates[S.shelter_id] = S
-
-/proc/preloadShuttleTemplates()
- for(var/item in subtypesof(/datum/map_template/shuttle))
- var/datum/map_template/shuttle/shuttle_type = item
- if(!initial(shuttle_type.suffix))
- continue
-
- var/datum/map_template/shuttle/S = new shuttle_type()
-
- GLOB.shuttle_templates[S.shuttle_id] = S
- GLOB.map_templates[S.shuttle_id] = S
-
-/proc/preloadEventTemplates()
- for(var/item in subtypesof(/datum/map_template/event))
- var/datum/map_template/event/event_type = item
- if(!initial(event_type.mappath))
- continue
-
- var/datum/map_template/event/E = new event_type()
-
- GLOB.map_templates[E.event_id] = E
diff --git a/code/datums/keybindings/emote_keybinds.dm b/code/datums/keybindings/emote_keybinds.dm
deleted file mode 100644
index a021b95995fb5..0000000000000
--- a/code/datums/keybindings/emote_keybinds.dm
+++ /dev/null
@@ -1,732 +0,0 @@
-/datum/keybinding/emote
- category = KB_CATEGORY_EMOTE_GENERIC
- var/datum/emote/linked_emote
-
-/datum/keybinding/emote/can_use(client/C, mob/M)
- return ..() //We don't need custom logic here as emotes handle their own useability, see USABLE_DEAD_EMOTES
-
-/datum/keybinding/emote/down(client/user)
- . = ..()
- user.mob.emote(initial(linked_emote.key), intentional = TRUE)
-
-/datum/keybinding/emote/flip
- linked_emote = /datum/emote/flip
- name = "Flip"
-
-/datum/keybinding/emote/spin
- linked_emote = /datum/emote/spin
- name = "Spin"
-
-/datum/keybinding/emote/blush
- linked_emote = /datum/emote/living/blush
- name = "Blush"
-
-/datum/keybinding/emote/bow
- linked_emote = /datum/emote/living/bow
- name = "Bow"
-
-/datum/keybinding/emote/burp
- linked_emote = /datum/emote/living/burp
- name = "Burp"
-
-/datum/keybinding/emote/choke
- linked_emote = /datum/emote/living/choke
- name = "Choke"
-
-/datum/keybinding/emote/collapse
- linked_emote = /datum/emote/living/collapse
- name = "Collapse"
-
-/datum/keybinding/emote/dance
- linked_emote = /datum/emote/living/dance
- name = "Dance"
-
-/datum/keybinding/emote/jump
- linked_emote = /datum/emote/living/jump
- name = "Jump"
-
-/datum/keybinding/emote/deathgasp
- linked_emote = /datum/emote/living/deathgasp
- name = "Deathgasp"
-
-/datum/keybinding/emote/drool
- linked_emote = /datum/emote/living/drool
- name = "Drool"
-
-/datum/keybinding/emote/quiver
- linked_emote = /datum/emote/living/quiver
- name = "Quiver"
-
-/datum/keybinding/emote/frown
- linked_emote = /datum/emote/living/frown
- name = "Frown"
-
-/datum/keybinding/emote/gag
- linked_emote = /datum/emote/living/gag
- name = "Gag"
-
-/datum/keybinding/emote/glare
- linked_emote = /datum/emote/living/glare
- name = "Glare"
-
-/datum/keybinding/emote/grin
- linked_emote = /datum/emote/living/grin
- name = "Grin"
-
-/datum/keybinding/emote/grimace
- linked_emote = /datum/emote/living/grimace
- name = "Grimace"
-
-/datum/keybinding/emote/groan
- linked_emote = /datum/emote/living/groan
- name = "Groan"
-
-/datum/keybinding/emote/look
- linked_emote = /datum/emote/living/look
- name = "Look"
-
-/datum/keybinding/emote/bshake
- linked_emote = /datum/emote/living/bshake
- name = "Shake"
-
-/datum/keybinding/emote/shudder
- linked_emote = /datum/emote/living/shudder
- name = "Shudder"
-
-/datum/keybinding/emote/point
- linked_emote = /datum/emote/living/point
- name = "Point"
-
-/datum/keybinding/emote/pout
- linked_emote = /datum/emote/living/pout
- name = "Pout"
-
-/datum/keybinding/emote/scream
- linked_emote = /datum/emote/living/scream
- name = "Scream"
-
-/datum/keybinding/emote/shake
- linked_emote = /datum/emote/living/shake
- name = "Head Shake"
-
-/datum/keybinding/emote/shiver
- linked_emote = /datum/emote/living/shiver
- name = "Shiver"
-
-/datum/keybinding/emote/sigh
- linked_emote = /datum/emote/living/sigh
- name = "Sigh"
-
-/datum/keybinding/emote/happy
- linked_emote = /datum/emote/living/sigh/happy
- name = "Sigh (Happy)"
-
-/datum/keybinding/emote/sit
- linked_emote = /datum/emote/living/sit
- name = "Sit"
-
-/datum/keybinding/emote/smile
- linked_emote = /datum/emote/living/smile
- name = "Smile"
-
-/datum/keybinding/emote/smug
- linked_emote = /datum/emote/living/smug
- name = "Smug"
-
-/datum/keybinding/emote/sniff
- linked_emote = /datum/emote/living/sniff
- name = "Sniff"
-
-/datum/keybinding/emote/snore
- linked_emote = /datum/emote/living/snore
- name = "Snore"
-
-/datum/keybinding/emote/nightmare
- linked_emote = /datum/emote/living/nightmare
- name = "Nightmare"
-
-/datum/keybinding/emote/stare
- linked_emote = /datum/emote/living/stare
- name = "Stare"
-
-/datum/keybinding/emote/stretch
- linked_emote = /datum/emote/living/strech
- name = "Stretch"
-
-/datum/keybinding/emote/sulk
- linked_emote = /datum/emote/living/sulk
- name = "Sulk"
-
-/datum/keybinding/emote/sway
- linked_emote = /datum/emote/living/sway
- name = "Sway"
-
-/datum/keybinding/emote/swear
- linked_emote = /datum/emote/living/swear
- name = "Swear"
-
-/datum/keybinding/emote/tilt
- linked_emote = /datum/emote/living/tilt
- name = "Tilt"
-
-/datum/keybinding/emote/tremble
- linked_emote = /datum/emote/living/tremble
- name = "Tremble"
-
-/datum/keybinding/emote/twitch
- linked_emote = /datum/emote/living/twitch
- name = "Twitch (Violent)"
-
-/datum/keybinding/emote/twitch_s
- linked_emote = /datum/emote/living/twitch_s
- name = "Twitch"
-
-/datum/keybinding/emote/whimper
- linked_emote = /datum/emote/living/whimper
- name = "Whimper"
-
-/datum/keybinding/emote/wsmile
- linked_emote = /datum/emote/living/wsmile
- name = "Smile (Weak)"
-
-/datum/keybinding/emote/carbon
- category = KB_CATEGORY_EMOTE_CARBON
-
-/datum/keybinding/emote/carbon/can_use(client/C, mob/M)
- return iscarbon(M) && ..()
-
-/datum/keybinding/emote/carbon/blink
- linked_emote = /datum/emote/living/carbon/blink
- name = "Blink"
-
-/datum/keybinding/emote/carbon/blink_r
- linked_emote = /datum/emote/living/carbon/blink_r
- name = "Blink (Rapid)"
-
-/datum/keybinding/emote/carbon/cross
- linked_emote = /datum/emote/living/carbon/cross
- name = "Cross Arms"
-
-/datum/keybinding/emote/carbon/chuckle
- linked_emote = /datum/emote/living/carbon/chuckle
- name = "Chuckle"
-
-/datum/keybinding/emote/carbon/cough
- linked_emote = /datum/emote/living/carbon/cough
- name = "Cough"
-
-/datum/keybinding/emote/carbon/moan
- linked_emote = /datum/emote/living/carbon/moan
- name = "Moan"
-
-/datum/keybinding/emote/carbon/giggle
- linked_emote = /datum/emote/living/carbon/giggle
- name = "Giggle"
-
-/datum/keybinding/emote/carbon/gurgle
- linked_emote = /datum/emote/living/carbon/gurgle
- name = "Gurgle"
-
-/datum/keybinding/emote/carbon/inhale
- linked_emote = /datum/emote/living/carbon/inhale
- name = "Inhale"
-
-/datum/keybinding/emote/carbon/inhale/sharp
- linked_emote = /datum/emote/living/carbon/inhale/sharp
- name = "Inhale (Sharp)"
-
-/datum/keybinding/emote/carbon/kiss
- linked_emote = /datum/emote/living/carbon/kiss
- name = "Kiss" //PG13
-
-/datum/keybinding/emote/carbon/wave
- linked_emote = /datum/emote/living/carbon/wave
- name = "Wave"
-
-/datum/keybinding/emote/carbon/yawn
- linked_emote = /datum/emote/living/carbon/yawn
- name = "Yawn"
-
-/datum/keybinding/emote/carbon/exhale
- linked_emote = /datum/emote/living/carbon/exhale
- name = "Exhale"
-
-/datum/keybinding/emote/carbon/laugh
- linked_emote = /datum/emote/living/carbon/laugh
- name = "Laugh"
-
-/datum/keybinding/emote/carbon/scowl
- linked_emote = /datum/emote/living/carbon/scowl
- name = "Scowl"
-
-/datum/keybinding/emote/carbon/twirl
- linked_emote = /datum/emote/living/carbon/twirl
- name = "Twirl"
-
-/datum/keybinding/emote/carbon/faint
- linked_emote = /datum/emote/living/carbon/faint
- name = "Faint"
-
-/datum/keybinding/emote/carbon/sign
- linked_emote = /datum/emote/living/carbon/sign
- name = "Sign"
-
-/datum/keybinding/emote/carbon/alien
- category = KB_CATEGORY_EMOTE_ALIEN
-
-/datum/keybinding/emote/carbon/alien/can_use(client/C, mob/M)
- return isalien(M) && ..()
-
-/datum/keybinding/emote/carbon/alien/humanoid/roar
- linked_emote = /datum/emote/living/carbon/alien_humanoid/roar
- name = "Roar"
-
-/datum/keybinding/emote/carbon/alien/humanoid/hiss
- linked_emote = /datum/emote/living/carbon/alien_humanoid/hiss
- name = "Hiss"
-
-/datum/keybinding/emote/carbon/alien/humanoid/gnarl
- linked_emote = /datum/emote/living/carbon/alien_humanoid/gnarl
- name = "Gnarl"
-
-/datum/keybinding/emote/carbon/brain
- category = KB_CATEGORY_EMOTE_BRAIN
-
-/datum/keybinding/emote/carbon/brain/can_use(client/C, mob/M)
- return isbrain(M) && ..()
-
-/datum/keybinding/emote/carbon/brain/alarm
- linked_emote = /datum/emote/living/brain/alarm
- name = "Alarm"
-
-/datum/keybinding/emote/carbon/brain/alert
- linked_emote = /datum/emote/living/brain/alert
- name = "Alert"
-
-/datum/keybinding/emote/carbon/brain/notice
- linked_emote = /datum/emote/living/brain/notice
- name = "Notice"
-
-/datum/keybinding/emote/carbon/brain/flash
- linked_emote = /datum/emote/living/brain/flash
- name = "Flash"
-
-/datum/keybinding/emote/carbon/brain/whistle
- linked_emote = /datum/emote/living/brain/whistle
- name = "Whistle"
-
-/datum/keybinding/emote/carbon/brain/beep
- linked_emote = /datum/emote/living/brain/beep
- name = "Beep"
-
-/datum/keybinding/emote/carbon/brain/boop
- linked_emote = /datum/emote/living/brain/boop
- name = "Boop"
-
-/datum/keybinding/emote/carbon/human
- category = KB_CATEGORY_EMOTE_HUMAN
-
-/datum/keybinding/emote/carbon/human/can_use(client/C, mob/M)
- return ishuman(M) && ..()
-
-/datum/keybinding/emote/carbon/human/airguitar
- linked_emote = /datum/emote/living/carbon/human/airguitar
- name = "Airguitar"
-
-/datum/keybinding/emote/carbon/human/clap
- linked_emote = /datum/emote/living/carbon/human/clap
- name = "Clap"
-
-/datum/keybinding/emote/carbon/human/cry
- linked_emote = /datum/emote/living/carbon/human/cry
- name = "Cry"
-
-/datum/keybinding/emote/carbon/human/dap
- linked_emote = /datum/emote/living/carbon/human/highfive/dap
- name = "Dap"
-
-/datum/keybinding/emote/carbon/human/eyebrow
- linked_emote = /datum/emote/living/carbon/human/eyebrow
- name = "Eyebrow"
-
-/datum/keybinding/emote/carbon/human/facepalm
- linked_emote = /datum/emote/living/carbon/human/facepalm
- name = "Facepalm"
-
-/datum/keybinding/emote/carbon/human/grumble
- linked_emote = /datum/emote/living/carbon/human/grumble
- name = "Grumble"
-
-/datum/keybinding/emote/carbon/human/hug
- linked_emote = /datum/emote/living/carbon/human/hug
- name = "Hug"
-
-/datum/keybinding/emote/carbon/human/mumble
- linked_emote = /datum/emote/living/carbon/human/mumble
- name = "Mumble"
-
-/datum/keybinding/emote/carbon/human/nod
- linked_emote = /datum/emote/living/carbon/human/nod
- name = "Nod"
-
-/datum/keybinding/emote/carbon/human/palm
- linked_emote = /datum/emote/living/carbon/human/palm
- name = "Extend palm"
-
-/datum/keybinding/emote/carbon/human/scream
- linked_emote = /datum/emote/living/carbon/human/scream
- name = "Scream"
-
-/datum/keybinding/emote/carbon/human/gasp
- linked_emote = /datum/emote/living/carbon/human/gasp
- name = "Gasp"
-
-/datum/keybinding/emote/carbon/human/shake
- linked_emote = /datum/emote/living/carbon/human/shake
- name = "Shake Head"
-
-/datum/keybinding/emote/carbon/human/pale
- linked_emote = /datum/emote/living/carbon/human/pale
- name = "Pale"
-
-/datum/keybinding/emote/carbon/human/raise
- linked_emote = /datum/emote/living/carbon/human/raise
- name = "Raise"
-
-/datum/keybinding/emote/carbon/human/salute
- linked_emote = /datum/emote/living/carbon/human/salute
- name = "Salute"
-
-/datum/keybinding/emote/carbon/human/signal
- linked_emote = /datum/emote/living/carbon/sign/signal
- name = "Signal"
-
-/datum/keybinding/emote/carbon/human/shrug
- linked_emote = /datum/emote/living/carbon/human/shrug
- name = "Shrug"
-
-/datum/keybinding/emote/carbon/human/sniff
- linked_emote = /datum/emote/living/carbon/human/sniff
- name = "Sniff"
-
-/datum/keybinding/emote/carbon/human/johnny
- linked_emote = /datum/emote/living/carbon/human/johnny
- name = "Johnny"
-
-/datum/keybinding/emote/carbon/human/sneeze
- linked_emote = /datum/emote/living/carbon/human/sneeze
- name = "Sneeze"
-
-/datum/keybinding/emote/carbon/human/slap
- linked_emote = /datum/emote/living/carbon/human/slap
- name = "Slap"
-
-/datum/keybinding/emote/carbon/human/wince
- linked_emote = /datum/emote/living/carbon/human/wince
- name = "Wince"
-
-/datum/keybinding/emote/carbon/human/squint
- linked_emote = /datum/emote/living/carbon/human/squint
- name = "Squint"
-
-/datum/keybinding/emote/carbon/human/wink
- linked_emote = /datum/emote/living/carbon/human/wink
- name = "Wink"
-
-/datum/keybinding/emote/carbon/human/highfive
- linked_emote = /datum/emote/living/carbon/human/highfive
- name = "High Five"
-
-/datum/keybinding/emote/carbon/human/handshake
- linked_emote = /datum/emote/living/carbon/human/highfive/handshake
- name = "Handshake"
-
-/datum/keybinding/emote/carbon/human/snap
- linked_emote = /datum/emote/living/carbon/human/snap
- name = "Snap"
-
-/datum/keybinding/emote/carbon/human/crack
- linked_emote = /datum/emote/living/carbon/human/crack
- name = "Crack"
-
-/datum/keybinding/emote/carbon/human/fart
- linked_emote = /datum/emote/living/carbon/human/fart
- name = "Fart"
-
-/datum/keybinding/emote/carbon/human/wag
- linked_emote = /datum/emote/living/carbon/human/wag
- name = "Wag"
-
-/datum/keybinding/emote/carbon/human/wag/stop
- linked_emote = /datum/emote/living/carbon/human/wag/stop
- name = "Stop Wag"
-
-/datum/keybinding/emote/carbon/human/flap
- linked_emote = /datum/emote/living/carbon/human/flap
- name = "Flap"
-
-/datum/keybinding/emote/carbon/human/flap/angry
- linked_emote = /datum/emote/living/carbon/human/flap/angry
- name = "Angry Flap"
-
-/datum/keybinding/emote/carbon/human/flutter
- linked_emote = /datum/emote/living/carbon/human/flutter
- name = "Flutter"
-
-/datum/keybinding/emote/carbon/human/quill
- linked_emote = /datum/emote/living/carbon/human/quill
- name = "Quill"
-
-/datum/keybinding/emote/carbon/human/warble
- linked_emote = /datum/emote/living/carbon/human/warble
- name = "Warble"
-
-/datum/keybinding/emote/carbon/human/clack
- linked_emote = /datum/emote/living/carbon/human/clack
- name = "Clack"
-
-/datum/keybinding/emote/carbon/human/clack/click
- linked_emote = /datum/emote/living/carbon/human/clack/click
- name = "Click"
-
-/datum/keybinding/emote/carbon/human/drask_talk/drone
- linked_emote = /datum/emote/living/carbon/human/drask_talk/drone
- name = "Drone"
-
-/datum/keybinding/emote/carbon/human/drask_talk/hum
- linked_emote = /datum/emote/living/carbon/human/drask_talk/hum
- name = "Hum"
-
-/datum/keybinding/emote/carbon/human/drask_talk/rumble
- linked_emote = /datum/emote/living/carbon/human/drask_talk/rumble
- name = "Rumble"
-
-/datum/keybinding/emote/carbon/human/hiss
- linked_emote = /datum/emote/living/carbon/human/hiss
- name = "Hiss (Unathi)"
-
-/datum/keybinding/emote/carbon/human/creak
- linked_emote = /datum/emote/living/carbon/human/creak
- name = "Creak"
-
-/datum/keybinding/emote/carbon/human/squish
- linked_emote = /datum/emote/living/carbon/human/slime/squish
- name = "Squish"
-
-/datum/keybinding/emote/carbon/human/howl
- linked_emote = /datum/emote/living/carbon/human/howl
- name = "Howl"
-
-/datum/keybinding/emote/carbon/human/growl
- linked_emote = /datum/emote/living/carbon/human/growl
- name = "Growl"
-
-/datum/keybinding/emote/carbon/human/hiss/tajaran
- linked_emote = /datum/emote/living/carbon/human/hiss/tajaran
- name = "Hiss (Tajaran)"
-
-/datum/keybinding/emote/carbon/human/rattle
- linked_emote = /datum/emote/living/carbon/human/rattle
- name = "Rattle"
-
-/datum/keybinding/emote/carbon/human/bubble
- linked_emote = /datum/emote/living/carbon/human/slime/bubble
- name = "Bubble"
-
-/datum/keybinding/emote/carbon/human/pop
- linked_emote = /datum/emote/living/carbon/human/slime/pop
- name = "Pop"
-
-/datum/keybinding/emote/carbon/human/monkey/can_use(client/C, mob/M)
- return ismonkeybasic(M) && ..()
-
-/datum/keybinding/emote/carbon/human/monkey/gnarl
- linked_emote = /datum/emote/living/carbon/human/monkey/gnarl
- name = "Gnarl (Monkey)"
-
-/datum/keybinding/emote/carbon/human/monkey/roll
- linked_emote = /datum/emote/living/carbon/human/monkey/roll
- name = "Roll (Monkey)"
-
-/datum/keybinding/emote/carbon/human/monkey/scratch
- linked_emote = /datum/emote/living/carbon/human/monkey/scratch
- name = "Scratch (Monkey)"
-
-/datum/keybinding/emote/carbon/human/monkey/tail
- linked_emote = /datum/emote/living/carbon/human/monkey/tail
- name = "Tail (Monkey)"
-
-/datum/keybinding/emote/carbon/human/monkey/screech
- linked_emote = /datum/emote/living/carbon/human/scream/screech
- name = "Screech (Monkey)"
-
-/datum/keybinding/emote/carbon/human/monkey/screech/roar
- linked_emote = /datum/emote/living/carbon/human/scream/screech/roar
- name = "Roar (Monkey)"
-
-/datum/keybinding/emote/silicon
- category = KB_CATEGORY_EMOTE_SILICON
-
-/datum/keybinding/emote/silicon/can_use(client/C, mob/M)
- return (issilicon(M) || ismachineperson(M)) && ..()
-
-/datum/keybinding/emote/silicon/scream
- linked_emote = /datum/emote/living/silicon/scream
- name = "Scream"
-
-/datum/keybinding/emote/silicon/ping
- linked_emote = /datum/emote/living/silicon/ping
- name = "Ping"
-
-/datum/keybinding/emote/silicon/buzz
- linked_emote = /datum/emote/living/silicon/buzz
- name = "Buzz"
-
-/datum/keybinding/emote/silicon/buzz2
- linked_emote = /datum/emote/living/silicon/buzz2
- name = "Buzzz"
-
-/datum/keybinding/emote/silicon/beep
- linked_emote = /datum/emote/living/silicon/beep
- name = "Beep"
-
-/datum/keybinding/emote/silicon/boop
- linked_emote = /datum/emote/living/silicon/boop
- name = "Boop"
-
-/datum/keybinding/emote/silicon/yes
- linked_emote = /datum/emote/living/silicon/yes
- name = "Yes"
-
-/datum/keybinding/emote/silicon/no
- linked_emote = /datum/emote/living/silicon/no
- name = "No"
-
-/datum/keybinding/emote/silicon/law
- linked_emote = /datum/emote/living/silicon/law
- name = "Law"
-
-/datum/keybinding/emote/silicon/halt
- linked_emote = /datum/emote/living/silicon/halt
- name = "Halt"
-
-/datum/keybinding/emote/simple_animal
- category = KB_CATEGORY_EMOTE_ANIMAL
-
-/datum/keybinding/emote/simple_animal/can_use(client/C, mob/M)
- return isanimal(M) && ..()
-
-/datum/keybinding/emote/simple_animal/diona_chirp
- linked_emote = /datum/emote/living/simple_animal/diona_chirp
- name = "Chirp (Nymph)"
-
-/datum/keybinding/emote/simple_animal/diona_chirp/can_use(client/C, mob/M)
- return isnymph(M) && ..()
-
-/datum/keybinding/emote/simple_animal/gorilla_ooga
- linked_emote = /datum/emote/living/simple_animal/gorilla/ooga
- name = "Ooga (Gorilla)"
-
-/datum/keybinding/emote/simple_animal/gorilla_ooga/can_use(client/C, mob/M)
- return isgorilla(M) && ..()
-
-/datum/keybinding/emote/simple_animal/pet/dog/bark
- linked_emote = /datum/emote/living/simple_animal/pet/dog/bark
- name = "Bark (Dog)"
-
-/datum/keybinding/emote/simple_animal/pet/dog/yelp
- linked_emote = /datum/emote/living/simple_animal/pet/dog/yelp
- name = "Yelp (Dog)"
-
-/datum/keybinding/emote/simple_animal/pet/dog/growl
- linked_emote = /datum/emote/living/simple_animal/pet/dog/growl
- name = "Growl (Dog)"
-
-/datum/keybinding/emote/simple_animal/pet/dog/can_use(client/C, mob/M)
- return isdog(M) && ..()
-
-/datum/keybinding/emote/simple_animal/mouse/squeak
- linked_emote = /datum/emote/living/simple_animal/mouse/squeak
- name = "Squeak (Mouse)"
-
-/datum/keybinding/emote/simple_animal/mouse/can_use(client/C, mob/M)
- return ismouse(M) && ..()
-
-/datum/keybinding/emote/simple_animal/pet/cat/meow
- linked_emote = /datum/emote/living/simple_animal/pet/cat/meow
- name = "Meow (Cat)"
-
-/datum/keybinding/emote/simple_animal/pet/cat/hiss
- linked_emote = /datum/emote/living/simple_animal/pet/cat/hiss
- name = "Hiss (Cat)"
-
-/datum/keybinding/emote/simple_animal/pet/cat/purr
- linked_emote = /datum/emote/living/simple_animal/pet/cat/purr
- name = "Purr (Cat)"
-
-/datum/keybinding/emote/simple_animal/pet/cat/sit
- linked_emote = /datum/emote/living/sit/cat
- name = "Sit/Stand (Cat)"
-
-/datum/keybinding/emote/simple_animal/pet/cat/can_use(client/C, mob/M)
- return iscat(M) && ..()
-
-/datum/keybinding/custom
- category = KB_CATEGORY_EMOTE_CUSTOM
- var/default_emote_text = "Insert custom me emote text."
- var/donor_exclusive = FALSE
-
-/datum/keybinding/custom/down(client/C)
- . = ..()
- if(!C.prefs?.active_character?.custom_emotes) //Checks the current character save for any custom emotes
- return
-
- var/desired_emote = C.prefs.active_character.custom_emotes[name] //check the custom emotes list for this keybind name
-
- if(!desired_emote)
- return
-
- C.mob.me_verb(html_decode(desired_emote)) //do the thing!
-
-/datum/keybinding/custom/can_use(client/C, mob/M)
- if(donor_exclusive && !(C.donator_level || C.holder || C.prefs.unlock_content)) //is this keybind restricted to donors/byond members/admins, and are you one or not?
- return
-
- return isliving(M) && ..()
-
-/datum/keybinding/custom/one
- name = "Custom Emote 1"
-
-/datum/keybinding/custom/two
- name = "Custom Emote 2"
-
-/datum/keybinding/custom/three
- name = "Custom Emote 3"
-
-/datum/keybinding/custom/four
- name = "Custom Emote 4"
- donor_exclusive = TRUE
-
-/datum/keybinding/custom/five
- name = "Custom Emote 5"
- donor_exclusive = TRUE
-
-/datum/keybinding/custom/six
- name = "Custom Emote 6"
- donor_exclusive = TRUE
-
-/datum/keybinding/custom/seven
- name = "Custom Emote 7"
- donor_exclusive = TRUE
-
-/datum/keybinding/custom/eight
- name = "Custom Emote 8"
- donor_exclusive = TRUE
-
-/datum/keybinding/custom/nine
- name = "Custom Emote 9"
- donor_exclusive = TRUE
-
-/datum/keybinding/custom/ten
- name = "Custom Emote 10"
- donor_exclusive = TRUE
diff --git a/code/datums/log_record.dm b/code/datums/log_record.dm
deleted file mode 100644
index 2b9aef01cc24b..0000000000000
--- a/code/datums/log_record.dm
+++ /dev/null
@@ -1,79 +0,0 @@
-/datum/log_record
- var/log_type // Type of log
- var/raw_time // When did this happen?
- var/what // What happened
- var/who // Who did it
- var/who_usr // The current usr, if not who.
- var/target // Who/what was targeted
- var/where // Where did it happen
-
-/datum/log_record/New(_log_type, _who, _what, _target, _where, _raw_time, force_no_usr_check, automatic)
- log_type = _log_type
-
- who = get_subject_text(_who, _log_type)
- who_usr = ""
- if(!isnull(usr) && !force_no_usr_check)
- if(automatic)
- who_usr = " Automatic for [get_subject_text(usr, _log_type)]"
- else if(log_type == DEFENSE_LOG)
- if(usr != _target)
- who_usr = " FORCED by [get_subject_text(usr, _log_type)]"
- else if(usr != _who)
- who_usr = " FORCED by [get_subject_text(usr, _log_type)]"
- what = _what
- target = get_subject_text(_target, _log_type)
- if(!istext(_where) && !isturf(_where))
- _where = get_turf(_who)
- if(isturf(_where))
- var/turf/T = _where
- where = ADMIN_COORDJMP(T)
- else
- where = _where
- if(!_raw_time)
- _raw_time = world.time
- raw_time = _raw_time
-
-/datum/log_record/proc/get_subject_text(subject, log_type)
- if(ismob(subject) || isclient(subject) || istype(subject, /datum/mind))
- . = key_name_admin(subject)
- if(should_log_health(log_type) && isliving(subject))
- . += get_health_string(subject)
- else if(isatom(subject))
- var/atom/A = subject
- . = A.name
- else if(istype(subject, /datum))
- var/datum/D = subject
- return D.type
- else
- . = subject
-
-/datum/log_record/proc/get_health_string(mob/living/L)
- var/OX = L.getOxyLoss() > 50 ? "[L.getOxyLoss()]" : L.getOxyLoss()
- var/TX = L.getToxLoss() > 50 ? "[L.getToxLoss()]" : L.getToxLoss()
- var/BU = L.getFireLoss() > 50 ? "[L.getFireLoss()]" : L.getFireLoss()
- var/BR = L.getBruteLoss() > 50 ? "[L.getBruteLoss()]" : L.getBruteLoss()
- var/ST = L.getStaminaLoss() > 50 ? "[L.getStaminaLoss()]" : L.getStaminaLoss()
- return " ([L.health]: [OX] - [TX] - [BU] - [BR] - [ST])"
-
-/datum/log_record/proc/should_log_health(log_type)
- if(log_type == ATTACK_LOG || log_type == DEFENSE_LOG)
- return TRUE
- return FALSE
-
-/proc/compare_log_record(datum/log_record/A, datum/log_record/B)
- var/time_diff = A.raw_time - B.raw_time
- if(!time_diff) // Same time
- return cmp_text_asc(A.log_type, B.log_type)
- return time_diff
-
-/datum/log_record/vv_edit_var(var_name, var_value)
- message_admins("[key_name_admin(src)] attempted to VV edit a logging object. Inform the host at once.")
- log_admin("[key_name(src)] attempted to VV edit a logging object. Inform the host at once.")
- GLOB.discord_manager.send2discord_simple(DISCORD_WEBHOOK_ADMIN, "[key_name(src)] attempted to VV edit a logging object. Inform the host at once.")
- return FALSE
-
-/datum/log_record/can_vv_delete()
- message_admins("[key_name_admin(src)] attempted to VV edit a logging object. Inform the host at once.")
- log_admin("[key_name(src)] attempted to VV edit a logging object. Inform the host at once.")
- GLOB.discord_manager.send2discord_simple(DISCORD_WEBHOOK_ADMIN, "[key_name(src)] attempted to VV edit a logging object. Inform the host at once.")
- return FALSE
diff --git a/code/datums/log_viewer.dm b/code/datums/log_viewer.dm
deleted file mode 100644
index 843eb32bf0680..0000000000000
--- a/code/datums/log_viewer.dm
+++ /dev/null
@@ -1,319 +0,0 @@
-#define UPDATE_CKEY_MOB(__ckey) var/mob/result = selected_ckeys_mobs[__ckey];\
-if(!result || result.ckey != __ckey){\
- result = get_mob_by_ckey(__ckey);\
- selected_ckeys_mobs[__ckey] = result;\
-}
-
-#define RECORD_WARN_LIMIT 1000
-#define RECORD_HARD_LIMIT 2500
-
-/datum/log_viewer
- var/time_from = 0
- var/time_to = 4 HOURS // 4 Hours should be enough. INFINITY would screw the UI up
- var/list/selected_mobs = list() // The mobs in question.
- var/list/selected_ckeys = list() // The ckeys selected to search for. Will show all mobs the ckey is attached to
- var/list/mob/selected_ckeys_mobs = list()
- var/list/selected_log_types = ALL_LOGS // The log types being searched for
- var/list/log_records = list() // Found and sorted records
-
-/datum/log_viewer/proc/clear_all()
- selected_mobs.Cut()
- selected_log_types = ALL_LOGS
- selected_ckeys.Cut()
- selected_ckeys_mobs.Cut()
- time_from = initial(time_from)
- time_to = initial(time_to)
- log_records.Cut()
- return
-
-/datum/log_viewer/proc/search(user)
- log_records.Cut() // Empty the old results
- var/list/invalid_mobs = list()
- var/list/ckeys = selected_ckeys.Copy()
- for(var/i in selected_mobs)
- var/mob/M = i
- if(!M || QDELETED(M) || !M.last_known_ckey)
- invalid_mobs |= M
- continue
- ckeys |= M.last_known_ckey
-
- for(var/ckey in ckeys)
- for(var/log_type in selected_log_types)
- var/list/logs = GLOB.logging.get_logs_by_type(ckey, log_type)
- var/len_logs = length(logs)
- if(len_logs)
- var/start_index = get_earliest_log_index(logs)
- if(!start_index) // No log found that matches the starting time criteria
- continue
- var/end_index = get_latest_log_index(logs)
- if(!end_index) // No log found that matches the end time criteria
- continue
- log_records.Add(logs.Copy(start_index, end_index + 1))
-
- if(length(invalid_mobs))
- to_chat(user, "The search criteria contained invalid mobs. They have been removed from the criteria.")
- for(var/i in invalid_mobs)
- selected_mobs -= i // Cleanup
-
- log_records = sortTim(log_records, GLOBAL_PROC_REF(compare_log_record))
-
-/** Binary search like implementation to find the earliest log
- * Returns the index of the earliest log using the time_from value for the given list of logs.
- * It will return 0 if no log after time_from is found
-*/
-/datum/log_viewer/proc/get_earliest_log_index(list/logs)
- if(!time_from)
- return 1
- var/start = 1
- var/end = length(logs)
- var/mid
- do
- mid = round_down((end + start) / 2)
- var/datum/log_record/L = logs[mid]
- if(L.raw_time >= time_from)
- end = mid
- else
- start = mid
- while(end - start > 1)
- var/datum/log_record/L = logs[end]
- if(L.raw_time >= time_from) // Check if there is atleast one valid log
- return end
- return 0
-
-/** Binary search like implementation to find the latest log
- * Returns the index of the latest log using the time_to value (1 second is added to prevent rounding weirdness) for the given list of logs.
- * It will return 0 if no log before time_to + 10 is found
-*/
-/datum/log_viewer/proc/get_latest_log_index(list/logs)
- if(world.time < time_to)
- return length(logs)
-
- var/end = length(logs)
- var/start = 1
- var/mid
- var/max_time = time_to + 10
- do
- mid = round((end + start) / 2 + 0.5)
- var/datum/log_record/L = logs[mid]
- if(L.raw_time >= max_time)
- end = mid
- else
- start = mid
- while(end - start > 1)
- var/datum/log_record/L = logs[start]
- if(L.raw_time < max_time) // Check if there is atleast one valid log
- return start
- return 0
-
-/datum/log_viewer/proc/add_mobs(list/mob/mobs)
- if(!length(mobs))
- return
- for(var/i in mobs)
- add_mob(usr, i, FALSE)
-
-/datum/log_viewer/proc/add_ckey(mob/user, ckey)
- if(!user || !ckey)
- return
- selected_ckeys |= ckey
- UPDATE_CKEY_MOB(ckey)
- show_ui(user)
-
-/datum/log_viewer/proc/add_mob(mob/user, mob/M, show_the_ui = TRUE)
- if(!M || !user)
- return
-
- selected_mobs |= M
-
- show_ui(user)
-
-/datum/log_viewer/proc/show_ui(mob/user)
- var/all_log_types = ALL_LOGS
- var/trStyleTop = "border-top:2px solid; border-bottom:2px solid; padding-top: 5px; padding-bottom: 5px;"
- var/trStyle = "border-top:1px solid; border-bottom:1px solid; padding-top: 5px; padding-bottom: 5px;"
- var/list/dat = list()
- dat += ""
- dat += ""
-
- // Search results
- var/tdStyleTime = "width:80px; text-align:center;"
- var/tdStyleType = "width:80px; text-align:center;"
- var/tdStyleWho = "width:400px; text-align:center;"
- var/tdStyleWhere = "width:150px; text-align:center;"
- dat += ""
- dat += " "
- dat += "When | Type | Who | What | Target | Where | "
- for(var/i in log_records)
- var/datum/log_record/L = i
- var/time = gameTimestamp(wtime = L.raw_time - 9.99) // The time rounds up for some reason. Will result in weird filtering results
-
- dat +="[time] | [L.log_type] | \
- [L.who][L.who_usr] | [L.what] | \
- [L.target] | [L.where] | "
- dat += " "
- dat += " "
-
- var/datum/browser/popup = new(user, "Log Viewer", "Log Viewer", 1500, 600)
- popup.set_content(dat.Join())
- popup.open()
-
-/datum/log_viewer/Topic(href, href_list)
- if(href_list["start_time"])
- var/input = input(usr, "hh:mm:ss", "Start time", "00:00:00") as text|null
- if(!input)
- return
- var/res = timeStampToNum(input)
- if(res < 0)
- to_chat(usr, "'[input]' is an invalid input value.")
- return
- time_from = res
- show_ui(usr)
- return
- if(href_list["end_time"])
- var/input = input(usr, "hh:mm:ss", "End time", "04:00:00") as text|null
- if(!input)
- return
- var/res = timeStampToNum(input)
- if(res < 0)
- to_chat(usr, "'[input]' is an invalid input value.")
- return
- time_to = res
-
- show_ui(usr)
- return
- if(href_list["search"])
- search(usr)
- var/records_len = length(log_records)
- if(records_len > RECORD_WARN_LIMIT)
- var/datum/log_record/last_record = log_records[RECORD_WARN_LIMIT]
- var/last_time = gameTimestamp(wtime = last_record.raw_time - 9.99)
- var/answer = alert(usr, "More than [RECORD_WARN_LIMIT] records were found. continuing will take a long time. This won't cause much lag for the server. Time at the [RECORD_WARN_LIMIT]th record '[last_time]'", "Warning", "Continue", "Limit to [RECORD_WARN_LIMIT]", "Cancel")
- if(answer == "Limit to [RECORD_WARN_LIMIT]")
- log_records.Cut(RECORD_WARN_LIMIT)
- else if(answer == "Cancel")
- log_records.Cut()
- else
- if(records_len > RECORD_HARD_LIMIT)
- to_chat(usr, "Record limit reached. Limiting to [RECORD_HARD_LIMIT].")
- log_records.Cut(RECORD_HARD_LIMIT)
- show_ui(usr)
- return
- if(href_list["clear_all"])
- clear_all(usr)
- show_ui(usr)
- return
- if(href_list["clear_mobs"])
- selected_mobs.Cut()
- show_ui(usr)
- return
- if(href_list["clear_ckeys"])
- selected_ckeys.Cut()
- selected_ckeys_mobs.Cut()
- show_ui(usr)
- return
- if(href_list["add_mob"])
- var/list/mobs = getpois(TRUE, TRUE)
- var/mob_choice = tgui_input_list(usr, "Please, select a mob: ", "Mob selector", mobs)
- add_mob(usr, mobs[mob_choice])
- return
- if(href_list["add_ckey"])
- var/list/ckeys = GLOB.logging.get_ckeys_logged()
- var/ckey_choice = tgui_input_list(usr, "Please, select a ckey: ", "Ckey selector", ckeys)
- add_ckey(usr, ckey_choice)
- return
- if(href_list["remove_mob"])
- var/mob/M = locate(href_list["remove_mob"])
- if(M)
- selected_mobs -= M
- show_ui(usr)
- return
- if(href_list["remove_ckey"])
- selected_ckeys -= href_list["remove_ckey"]
- show_ui(usr)
- return
- if(href_list["toggle_log_type"])
- var/log_type = href_list["toggle_log_type"]
- if(log_type in selected_log_types)
- selected_log_types -= log_type
- else
- selected_log_types += log_type
- show_ui(usr)
- return
-
-/datum/log_viewer/proc/get_logtype_color(log_type)
- switch(log_type)
- if(ATTACK_LOG)
- return "darkred"
- if(DEFENSE_LOG)
- return "chocolate"
- if(CONVERSION_LOG)
- return "indigo"
- if(SAY_LOG)
- return "teal"
- if(EMOTE_LOG)
- return "deepskyblue"
- if(MISC_LOG)
- return "gray"
- if(DEADCHAT_LOG)
- return "#cc00c6"
- if(OOC_LOG)
- return "#002eb8"
- if(LOOC_LOG)
- return "#6699CC"
- return "slategray"
-
-/datum/log_viewer/proc/get_display_name(mob/M)
- var/name = M.name
- if(M.name != M.real_name)
- name = "[name] ([M.real_name])"
- if(isobserver(M))
- name = "[name] (DEAD)"
- return "\[[M.last_known_ckey]\] [name]"
-
-/datum/log_viewer/proc/get_ckey_name(ckey)
- UPDATE_CKEY_MOB(ckey)
- var/mob/M = selected_ckeys_mobs[ckey]
-
- return get_display_name(M)
-
-#undef UPDATE_CKEY_MOB
-#undef RECORD_WARN_LIMIT
-#undef RECORD_HARD_LIMIT
diff --git a/code/datums/logging.dm b/code/datums/logging.dm
deleted file mode 100644
index 9cf2fb84f3cb6..0000000000000
--- a/code/datums/logging.dm
+++ /dev/null
@@ -1,59 +0,0 @@
-/datum/logging
- var/list/datum/log_record/logs = list() // Assoc list of assoc lists (ckey, (log_type, list/logs))
-
-/datum/logging/proc/add_log(ckey, datum/log_record/log)
- if(!ckey)
- log_debug("GLOB.logging.add_log called with an invalid ckey")
- return
-
- if(!logs[ckey])
- logs[ckey] = list()
-
- var/list/log_types_list = logs[ckey]
-
- if(!log_types_list[log.log_type])
- log_types_list[log.log_type] = list()
-
- var/list/datum/log_record/log_records = log_types_list[log.log_type]
- log_records.Add(log)
-
-/datum/logging/proc/get_ckeys_logged()
- var/list/ckeys = list()
- for(var/ckey in logs)
- ckeys.Add(ckey)
- return ckeys
-
-/* Returns the logs of a given ckey and log_type
- * If no logs exist it will return an empty list
-*/
-/datum/logging/proc/get_logs_by_type(ckey, log_type)
- if(!ckey)
- log_debug("GLOB.logging.get_logs_by_type called with an invalid ckey")
- return
- if(!log_type || !(log_type in ALL_LOGS))
- log_debug("GLOB.logging.get_logs_by_type called with an invalid log_type '[log_type]'")
- return
-
- var/list/log_types_list = logs[ckey]
- // Check if logs exist for the ckey
- if(!length(log_types_list))
- return list()
-
- var/list/datum/log_record/log_records = log_types_list[log_type]
-
- // Check if logs exist for this type
- if(!log_records)
- return list()
- return log_records
-
-/datum/logging/vv_edit_var(var_name, var_value)
- message_admins("[key_name_admin(src)] attempted to VV edit a logging object. Inform the host at once.")
- log_admin("[key_name(src)] attempted to VV edit a logging object. Inform the host at once.")
- GLOB.discord_manager.send2discord_simple(DISCORD_WEBHOOK_ADMIN, "[key_name(src)] attempted to VV edit a logging object. Inform the host at once.")
- return FALSE
-
-/datum/logging/can_vv_delete()
- message_admins("[key_name_admin(src)] attempted to VV edit a logging object. Inform the host at once.")
- log_admin("[key_name(src)] attempted to VV edit a logging object. Inform the host at once.")
- GLOB.discord_manager.send2discord_simple(DISCORD_WEBHOOK_ADMIN, "[key_name(src)] attempted to VV edit a logging object. Inform the host at once.")
- return FALSE
diff --git a/code/datums/ores.dm b/code/datums/ores.dm
deleted file mode 100644
index 07e7ae6090038..0000000000000
--- a/code/datums/ores.dm
+++ /dev/null
@@ -1,212 +0,0 @@
-/datum/ore
- /// The type of ore that is dropped. Expected to be a subtype of [/obj/item/stack/ore].
- var/drop_type
- /// The lower bound for the amount of randomized ore dropped.
- var/drop_min = 1
- /// The upper bound for the amount of randomized ore dropped.
- var/drop_max = 5
- /// The probability that the ore will spread to nearby [/turf/simulated/mineral]s when placed.
- var/spread_chance = 0
- /// The icon state of the ore used for mining scanner overlays.
- var/scan_icon_state = ""
-
-/**
- * Called when the containing turf is "mined", such as with a pickaxe or other
- * digging implement.
- *
- * Returns [MINERAL_ALLOW_DIG] if the containing turf should be changed to its
- * "dug" state, [MINERAL_PREVENT_DIG] if it should remain as is.
- */
-/datum/ore/proc/on_mine(turf/source, mob/user, triggered_by_explosion = FALSE)
- var/amount = rand(drop_min, drop_max)
-
- if(ispath(drop_type, /obj/item/stack/ore))
- new drop_type(source, amount)
- SSticker.score?.score_ore_mined++
- SSblackbox.record_feedback("tally", "ore_mined", amount, type)
- else
- stack_trace("[source.type] [COORD(source)] had non-ore stack [drop_type]")
-
- return MINERAL_ALLOW_DIG
-
-/datum/ore/iron
- drop_type = /obj/item/stack/ore/iron
- spread_chance = 20
- scan_icon_state = "rock_Iron"
-
-/datum/ore/uranium
- drop_type = /obj/item/stack/ore/uranium
- spread_chance = 5
- scan_icon_state = "rock_Uranium"
-
-/datum/ore/diamond
- drop_type = /obj/item/stack/ore/diamond
- scan_icon_state = "rock_Diamond"
-
-/datum/ore/gold
- drop_type = /obj/item/stack/ore/gold
- spread_chance = 5
- scan_icon_state = "rock_Gold"
-
-/datum/ore/silver
- drop_type = /obj/item/stack/ore/silver
- spread_chance = 5
- scan_icon_state = "rock_Silver"
-
-/datum/ore/titanium
- drop_type = /obj/item/stack/ore/titanium
- spread_chance = 5
- scan_icon_state = "rock_Titanium"
-
-/datum/ore/plasma
- drop_type = /obj/item/stack/ore/plasma
- spread_chance = 8
- scan_icon_state = "rock_Plasma"
-
-/datum/ore/bluespace
- drop_type = /obj/item/stack/ore/bluespace_crystal
- drop_min = 1
- drop_max = 1
- scan_icon_state = "rock_BScrystal"
-
-/datum/ore/bananium
- drop_type = /obj/item/stack/ore/bananium
- drop_min = 3
- drop_max = 3
- scan_icon_state = "rock_Clown"
-
-/datum/ore/tranquillite
- drop_type = /obj/item/stack/ore/tranquillite
- drop_min = 3
- drop_max = 3
-
-/datum/ore/ancient_basalt
- drop_type = /obj/item/stack/ore/glass/basalt/ancient
- drop_min = 2
- drop_max = 2
-
-#define GIBTONITE_UNSTRUCK 0 //! The gibtonite ore is dormant.
-#define GIBTONITE_ACTIVE 1 //! The gibtonite ore is in its detonation countdown.
-#define GIBTONITE_STABLE 2 //! The gibtonite ore has been stabilized and its detonation countdown is cancelled.
-#define GIBTONITE_DETONATE 3 //! The gibtonite ore is about to explode.
-
-/datum/ore/gibtonite
- drop_max = 1
- scan_icon_state = "rock_Gibtonite"
- /// Amount of time from mining before gibtonite explodes.
- var/detonate_time
- /// The world.time that the detonate countdown started at.
- var/detonate_start_time
- /// The amount of time remaining if the gibtonite was stabilized before explosion, in half-seconds.
- var/remaining_time
- /// The state the ore is in. One of [GIBTONITE_UNSTRUCK], [GIBTONITE_ACTIVE], [GIBTONITE_STABLE], or [GIBTONITE_DETONATE].
- var/stage = GIBTONITE_UNSTRUCK
- /// The overlay used for when the gibtonite is in its detonation countdown mode.
- var/mutable_appearance/activated_overlay
- /// Whether an admin log should be generated for this gibtonite's detonation.
- /// Typically enabled if the detonation doesn't occur on the station z-level.
- /// Note that this is only for explosions caused while the gibtonite is still
- /// unmined, in contrast to [/obj/item/gibtonite/proc/GibtoniteReaction].
- var/notify_admins = FALSE
- /// The callback for the explosion that occurs if the gibtonite is not
- /// defused in time.
- var/explosion_callback
-
-/datum/ore/gibtonite/New()
- // So you don't know exactly when the hot potato will explode
- detonate_time = rand(4 SECONDS, 5 SECONDS)
-
-/datum/ore/gibtonite/proc/explosive_reaction(turf/source, mob/user, triggered_by_explosion = FALSE)
- activated_overlay = mutable_appearance(source.icon, "rock_Gibtonite_active", ON_EDGED_TURF_LAYER)
- source.add_overlay(activated_overlay)
- source.name = "gibtonite deposit"
- source.desc = "An active gibtonite reserve. Run!"
- stage = GIBTONITE_ACTIVE
- source.visible_message("There was gibtonite inside! It's going to explode!")
-
- if(!is_mining_level(source.z))
- notify_admins = TRUE
- if(!triggered_by_explosion)
- message_admins("[key_name_admin(user)] has triggered a gibtonite deposit reaction at [ADMIN_VERBOSEJMP(source)].")
- else
- message_admins("An explosion has triggered a gibtonite deposit reaction at [ADMIN_VERBOSEJMP(source)].")
-
- if(!triggered_by_explosion)
- log_game("[key_name(user)] has triggered a gibtonite deposit reaction at [AREACOORD(source)].")
- else
- log_game("An explosion has triggered a gibtonite deposit reaction at [AREACOORD(source)].")
-
- RegisterSignal(source, COMSIG_ATTACK_BY, PROC_REF(on_attackby))
- detonate_start_time = world.time
- explosion_callback = addtimer(CALLBACK(src, TYPE_PROC_REF(/datum/ore/gibtonite, detonate), source), detonate_time, TIMER_STOPPABLE)
-
-/datum/ore/gibtonite/on_mine(turf/source, mob/user, triggered_by_explosion = FALSE)
- switch(stage)
- if(GIBTONITE_UNSTRUCK)
- playsound(src,'sound/effects/hit_on_shattered_glass.ogg', 50, TRUE)
- explosive_reaction(source, user, triggered_by_explosion)
- return MINERAL_PREVENT_DIG
- if(GIBTONITE_ACTIVE)
- detonate(source)
-
- // Detonation takes care of this for us.
- return MINERAL_PREVENT_DIG
- if(GIBTONITE_STABLE)
- var/obj/item/gibtonite/gibtonite = new(source)
- if(remaining_time <= 0)
- gibtonite.quality = 3
- gibtonite.icon_state = "Gibtonite ore 3"
- if(remaining_time >= 1 && remaining_time <= 2)
- gibtonite.quality = 2
- gibtonite.icon_state = "Gibtonite ore 2"
-
- return MINERAL_ALLOW_DIG
-
- return MINERAL_PREVENT_DIG
-
-/datum/ore/gibtonite/proc/on_attackby(turf/source, obj/item/attacker, mob/user)
- SIGNAL_HANDLER // COMSIG_ATTACK_BY
-
- if(istype(attacker, /obj/item/mining_scanner) || istype(attacker, /obj/item/t_scanner/adv_mining_scanner) && stage == GIBTONITE_ACTIVE)
- user.visible_message("[user] holds [attacker] to [src]...", "You use [attacker] to locate where to cut off the chain reaction and attempt to stop it...")
- defuse(source)
- return COMPONENT_SKIP_AFTERATTACK
-
-/datum/ore/gibtonite/proc/detonate(turf/simulated/mineral/source)
- if(stage == GIBTONITE_STABLE)
- return
-
- // Don't explode twice please
- if(explosion_callback)
- deltimer(explosion_callback)
-
- stage = GIBTONITE_DETONATE
- explosion(source, 1, 3, 5, adminlog = notify_admins)
-
- if(!istype(source))
- return
-
- // Dunno where else to put this
- source.ChangeTurf(source.turf_type, source.defer_change)
- addtimer(CALLBACK(source, TYPE_PROC_REF(/turf, AfterChange)), 1, TIMER_UNIQUE)
-
-/datum/ore/gibtonite/proc/defuse(turf/source)
- var/world_time = world.time // Grab this immediately so we're fairly calculating countdown time
- if(stage == GIBTONITE_ACTIVE)
- source.cut_overlay(activated_overlay)
- activated_overlay.icon_state = "rock_Gibtonite_inactive"
- source.add_overlay(activated_overlay)
- source.desc = "An inactive gibtonite reserve. The ore can be extracted."
- stage = GIBTONITE_STABLE
-
- // ticks remaining / 10 = seconds remaining * 2 countdown decrements every second
- remaining_time = floor((detonate_time - (world_time - detonate_start_time)) / 5)
-
- if(remaining_time < 0)
- remaining_time = 0
- source.visible_message("The chain reaction was stopped! The gibtonite had [remaining_time] reactions left till the explosion!")
-
-#undef GIBTONITE_UNSTRUCK
-#undef GIBTONITE_ACTIVE
-#undef GIBTONITE_STABLE
-#undef GIBTONITE_DETONATE
diff --git a/code/datums/outfits/outfit_admin.dm b/code/datums/outfits/outfit_admin.dm
deleted file mode 100644
index bb24e3affe33a..0000000000000
--- a/code/datums/outfits/outfit_admin.dm
+++ /dev/null
@@ -1,1671 +0,0 @@
-// Used for 'select equipment'
-// code/modules/admin/verbs/debug.dm 566
-
-/datum/outfit/admin/pre_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
- . = ..()
- if(visualsOnly)
- return
-
- H.job = name
-
-/datum/outfit/admin/on_mind_initialize(mob/living/carbon/human/H)
- . = ..()
- H.mind.assigned_role = name
- H.mind.offstation_role = TRUE
-
-/proc/apply_to_card(obj/item/card/id/I, mob/living/carbon/human/H, list/access = list(), rank, special_icon)
- if(!istype(I) || !istype(H))
- return 0
-
- I.access = access
- I.registered_name = H.real_name
- I.rank = rank
- I.assignment = rank
- I.sex = capitalize(H.gender)
- I.age = H.age
- I.name = "[I.registered_name]'s ID Card ([I.assignment])"
- I.photo = get_id_photo(H)
-
- if(special_icon)
- I.icon_state = special_icon
-
-/datum/outfit/admin/syndicate
- name = "Syndicate Agent"
-
- uniform = /obj/item/clothing/under/syndicate
- back = /obj/item/storage/backpack
- belt = /obj/item/storage/belt/utility/full/multitool
- gloves = /obj/item/clothing/gloves/combat
- shoes = /obj/item/clothing/shoes/combat
- l_ear = /obj/item/radio/headset/syndicate
- id = /obj/item/card/id/syndicate
- r_pocket = /obj/item/radio/uplink
- backpack_contents = list(
- /obj/item/storage/box/engineer = 1,
- /obj/item/flashlight = 1,
- /obj/item/card/emag = 1,
- /obj/item/food/syndidonkpocket = 1
- )
-
- var/id_icon = "syndie"
- var/id_access = "Syndicate Operative"
- var/uplink_uses = 100
-
-/datum/outfit/admin/syndicate/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
- . = ..()
- if(visualsOnly)
- return
-
- var/obj/item/card/id/I = H.wear_id
- if(istype(I))
- apply_to_card(I, H, get_syndicate_access(id_access), name, id_icon)
-
- var/obj/item/radio/uplink/U = H.r_store
- if(istype(U))
- U.hidden_uplink.uplink_owner = "[H.key]"
- U.hidden_uplink.uses = uplink_uses
-
- var/obj/item/radio/R = H.l_ear
- if(istype(R))
- R.set_frequency(SYND_FREQ)
- H.faction += "syndicate"
-
-/datum/outfit/admin/syndicate_infiltrator
- name = "Syndicate Infiltrator"
-
-/datum/outfit/admin/syndicate_infiltrator/equip(mob/living/carbon/human/H, visualsOnly = FALSE)
- . = H.equip_syndicate_infiltrator(0, 100, FALSE)
- H.sec_hud_set_ID()
- H.faction |= "syndicate"
-
-/datum/outfit/admin/syndicate/operative
- name = "Syndicate Nuclear Operative"
-
- suit = /obj/item/clothing/suit/space/hardsuit/syndi
- belt = /obj/item/storage/belt/military
- mask = /obj/item/clothing/mask/gas/syndicate
- l_ear = /obj/item/radio/headset/syndicate/alt
- glasses = /obj/item/clothing/glasses/night
- shoes = /obj/item/clothing/shoes/magboots/syndie
- r_pocket = /obj/item/radio/uplink/nuclear
- l_pocket = /obj/item/pinpointer/advpinpointer
- l_hand = /obj/item/tank/internals/oxygen/red
-
- backpack_contents = list(
- /obj/item/storage/box/survival_syndi = 1,
- /obj/item/gun/projectile/automatic/pistol = 1,
- /obj/item/ammo_box/magazine/m10mm = 1,
- /obj/item/crowbar/red = 1,
- /obj/item/grenade/plastic/c4 = 1,
- /obj/item/food/syndidonkpocket = 1,
- /obj/item/flashlight = 1,
- /obj/item/clothing/shoes/combat = 1
- )
-
-/datum/outfit/admin/syndicate/operative/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
- . = ..()
- if(visualsOnly)
- return
-
- var/obj/item/bio_chip/explosive/E = new(H)
- E.implant(H)
-
-
-/datum/outfit/admin/syndicate/operative/freedom
- name = "Syndicate Freedom Operative"
- suit = /obj/item/clothing/suit/space/hardsuit/syndi/freedom
-
-
-/datum/outfit/admin/syndicate_strike_team
- name = "Syndicate Strike Team"
-
-/datum/outfit/admin/syndicate_strike_team/equip(mob/living/carbon/human/H, visualsOnly = FALSE)
- . = H.equip_syndicate_commando(FALSE, TRUE)
- H.faction |= "syndicate"
-
-/datum/outfit/admin/syndicate/spy
- name = "Syndicate Spy"
- uniform = /obj/item/clothing/under/suit/really_black
- shoes = /obj/item/clothing/shoes/chameleon/noslip
- uplink_uses = 200
- id_access = "Syndicate Agent"
-
- bio_chips = list(
- /obj/item/bio_chip/dust
- )
-
-
-/datum/outfit/admin/nt_vip
- name = "VIP Guest"
-
- uniform = /obj/item/clothing/under/suit/really_black
- back = /obj/item/storage/backpack/satchel
- gloves = /obj/item/clothing/gloves/color/black
- shoes = /obj/item/clothing/shoes/black
- head = /obj/item/clothing/head/that
- l_ear = /obj/item/radio/headset/ert
- id = /obj/item/card/id/centcom
- pda = /obj/item/pda
- backpack_contents = list(
- /obj/item/storage/box/engineer = 1
- )
-
-/datum/outfit/admin/nt_vip/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
- . = ..()
- if(visualsOnly)
- return
-
- var/obj/item/card/id/I = H.wear_id
- if(istype(I))
- apply_to_card(I, H, get_centcom_access("VIP Guest"), "VIP Guest")
- H.sec_hud_set_ID()
-
-/datum/outfit/admin/nt_navy_captain
- name = "NT Navy Captain"
-
- uniform = /obj/item/clothing/under/rank/centcom/captain
- back = /obj/item/storage/backpack/satchel
- belt = /obj/item/gun/energy/pulse/pistol
- gloves = /obj/item/clothing/gloves/color/white
- shoes = /obj/item/clothing/shoes/centcom
- head = /obj/item/clothing/head/beret/centcom/captain
- l_ear = /obj/item/radio/headset/centcom
- glasses = /obj/item/clothing/glasses/hud/security/sunglasses
- id = /obj/item/card/id/centcom
- pda = /obj/item/pda/centcom
- backpack_contents = list(
- /obj/item/storage/box/centcomofficer = 1,
- /obj/item/bio_chip_implanter/death_alarm = 1
- )
- bio_chips = list(
- /obj/item/bio_chip/mindshield,
- /obj/item/bio_chip/dust
- )
-
-/datum/outfit/admin/nt_navy_captain/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
- . = ..()
- if(visualsOnly)
- return
-
- var/obj/item/card/id/I = H.wear_id
- if(istype(I))
- apply_to_card(I, H, get_centcom_access("Nanotrasen Navy Captain"), "Nanotrasen Navy Captain")
- H.sec_hud_set_ID()
-
-/datum/outfit/admin/nt_diplomat
- name = "NT Diplomat"
-
- uniform = /obj/item/clothing/under/rank/centcom/diplomatic
- back = /obj/item/storage/backpack/satchel
- gloves = /obj/item/clothing/gloves/color/white
- shoes = /obj/item/clothing/shoes/centcom
- l_ear = /obj/item/radio/headset/centcom
- id = /obj/item/card/id/centcom
- r_pocket = /obj/item/lighter/zippo/nt_rep
- l_pocket = /obj/item/storage/fancy/cigarettes/dromedaryco
- pda = /obj/item/pda/centcom
- backpack_contents = list(
- /obj/item/storage/box/survival = 1,
- /obj/item/bio_chip_implanter/death_alarm = 1,
- )
- bio_chips = list(
- /obj/item/bio_chip/mindshield,
- /obj/item/bio_chip/dust
- )
-
-/datum/outfit/admin/nt_diplomat/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
- . = ..()
- if(visualsOnly)
- return
-
- var/obj/item/card/id/I = H.wear_id
- if(istype(I))
- apply_to_card(I, H, get_centcom_access("Nanotrasen Navy Representative"), "Nanotrasen Diplomat")
- H.sec_hud_set_ID()
-
-/datum/outfit/admin/nt_undercover
- name = "NT Undercover Operative"
- // Disguised NT special forces, sent to quietly eliminate or keep tabs on people in high positions (e.g: captain)
-
- uniform = /obj/item/clothing/under/color/random
- back = /obj/item/storage/backpack
- belt = /obj/item/storage/belt/utility/full/multitool
- gloves = /obj/item/clothing/gloves/color/yellow
- shoes = /obj/item/clothing/shoes/chameleon/noslip
- l_ear = /obj/item/radio/headset/centcom
- id = /obj/item/card/id
- pda = /obj/item/pda
- backpack_contents = list(
- /obj/item/storage/box/engineer = 1,
- /obj/item/flashlight = 1,
- /obj/item/pinpointer/crew = 1
- )
- bio_chips = list(
- /obj/item/bio_chip/dust
- )
- cybernetic_implants = list(
- /obj/item/organ/internal/cyberimp/eyes/hud/security,
- /obj/item/organ/internal/eyes/cybernetic/xray,
- /obj/item/organ/internal/cyberimp/brain/anti_stam/hardened,
- /obj/item/organ/internal/cyberimp/chest/nutriment/plus/hardened,
- /obj/item/organ/internal/cyberimp/arm/combat/centcom
- )
-
-/datum/outfit/admin/nt_undercover/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
- . = ..()
- if(visualsOnly)
- return
-
- var/obj/item/card/id/I = H.wear_id
- if(istype(I))
- apply_to_card(I, H, get_centcom_access("NT Undercover Operative"), "Assistant")
- H.sec_hud_set_ID() // Force it to show as assistant on sec huds
-
- var/obj/item/radio/R = H.l_ear
- if(istype(R))
- R.name = "radio headset"
- R.icon_state = "headset"
-
-/datum/outfit/admin/deathsquad_commando
- name = "NT Deathsquad"
-
- pda = /obj/item/pinpointer
- box = /obj/item/storage/box/deathsquad
- back = /obj/item/mod/control/pre_equipped/apocryphal
- belt = /obj/item/gun/projectile/revolver/mateba
- gloves = /obj/item/clothing/gloves/combat
- uniform = /obj/item/clothing/under/rank/centcom/deathsquad
- shoes = /obj/item/clothing/shoes/magboots/elite
- glasses = /obj/item/clothing/glasses/hud/security/night
- mask = /obj/item/clothing/mask/gas/sechailer/swat
- l_pocket = /obj/item/tank/internals/emergency_oxygen/double
- r_pocket = /obj/item/reagent_containers/hypospray/combat/nanites
- l_ear = /obj/item/radio/headset/alt/deathsquad
- id = /obj/item/card/id/ert/deathsquad
- suit_store = /obj/item/gun/energy/pulse
-
- backpack_contents = list(
- /obj/item/storage/box/smoke_grenades,
- /obj/item/ammo_box/a357,
- /obj/item/ammo_box/a357,
- /obj/item/ammo_box/a357,
- /obj/item/flashlight/seclite,
- /obj/item/grenade/barrier,
- /obj/item/melee/energy/sword/saber,
- /obj/item/shield/energy
- )
-
- cybernetic_implants = list(
- /obj/item/organ/internal/eyes/cybernetic/thermals/hardened,
- /obj/item/organ/internal/cyberimp/brain/anti_stam/hardened,
- /obj/item/organ/internal/cyberimp/chest/nutriment/plus/hardened,
- /obj/item/organ/internal/cyberimp/chest/reviver/hardened
- )
-
- bio_chips = list(
- /obj/item/bio_chip/mindshield, // No death alarm, Deathsquad are silent
- /obj/item/bio_chip/dust
- )
-
-/datum/outfit/admin/deathsquad_commando/leader
- name = "NT Deathsquad Leader"
- back = /obj/item/mod/control/pre_equipped/apocryphal/officer
-
- backpack_contents = list(
- /obj/item/storage/box/flashbangs,
- /obj/item/ammo_box/a357,
- /obj/item/flashlight/seclite,
- /obj/item/melee/energy/sword/saber,
- /obj/item/shield/energy,
- /obj/item/disk/nuclear/unrestricted
- )
-
-/datum/outfit/admin/deathsquad_commando/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
- . = ..()
- if(visualsOnly)
- return
-
- var/obj/item/card/id/I = H.wear_id
- if(istype(I))
- apply_to_card(I, H, get_centcom_access("Deathsquad Commando"), "Deathsquad")
- H.sec_hud_set_ID()
-
-/datum/outfit/admin/pirate
- name = "Space Pirate"
-
- uniform = /obj/item/clothing/under/costume/pirate
- back = /obj/item/storage/backpack/satchel
- belt = /obj/item/storage/belt/utility/full/multitool
- gloves = /obj/item/clothing/gloves/combat
- shoes = /obj/item/clothing/shoes/brown
- l_ear = /obj/item/radio/headset
- id = /obj/item/card/id
- r_hand = /obj/item/melee/energy/sword/pirate
- backpack_contents = list(
- /obj/item/storage/box/survival = 1,
- /obj/item/flashlight = 1
- )
-
-/datum/outfit/admin/pirate/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
- . = ..()
- if(visualsOnly)
- return
-
- var/obj/item/card/id/I = H.wear_id
- if(istype(I))
- apply_to_card(I, H, list(ACCESS_MAINT_TUNNELS), name)
-
-/datum/outfit/admin/pirate/first_mate
- name = "Space Pirate First Mate"
-
- glasses = /obj/item/clothing/glasses/eyepatch
- head = /obj/item/clothing/head/bandana
-
-/datum/outfit/admin/pirate/captain
- name = "Space Pirate Captain"
-
- suit = /obj/item/clothing/suit/pirate_black
- head = /obj/item/clothing/head/pirate
-
-/datum/outfit/admin/tunnel_clown
- name = "Tunnel Clown"
-
- uniform = /obj/item/clothing/under/rank/civilian/clown
- suit = /obj/item/clothing/suit/hooded/chaplain_hoodie
- back = /obj/item/storage/backpack
- belt = /obj/item/storage/belt/utility/full/multitool
- gloves = /obj/item/clothing/gloves/color/black
- shoes = /obj/item/clothing/shoes/clown_shoes
- mask = /obj/item/clothing/mask/gas/clown_hat
- l_ear = /obj/item/radio/headset
- glasses = /obj/item/clothing/glasses/thermal/monocle
- id = /obj/item/card/id
- l_pocket = /obj/item/food/grown/banana
- r_pocket = /obj/item/bikehorn
- r_hand = /obj/item/fireaxe
- backpack_contents = list(
- /obj/item/storage/box/survival = 1,
- /obj/item/flashlight = 1,
- /obj/item/reagent_containers/drinks/bottle/bottleofbanana = 1,
- /obj/item/grenade/clown_grenade = 1,
- /obj/item/melee/baton/cattleprod = 1,
- /obj/item/stock_parts/cell/super = 1,
- /obj/item/bikehorn/rubberducky = 1
- )
-
-/datum/outfit/admin/tunnel_clown/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
- . = ..()
- if(visualsOnly)
- return
-
- var/obj/item/card/id/I = H.wear_id
- if(istype(I))
- apply_to_card(I, H, list(ACCESS_CLOWN, ACCESS_THEATRE, ACCESS_MAINT_TUNNELS), "Tunnel Clown")
-
-/datum/outfit/admin/mime_assassin
- name = "Mime Assassin"
-
- uniform = /obj/item/clothing/under/rank/civilian/mime
- suit = /obj/item/clothing/suit/suspenders
- back = /obj/item/storage/backpack/mime
- belt = /obj/item/storage/belt/utility/full/multitool
- gloves = /obj/item/clothing/gloves/color/white
- shoes = /obj/item/clothing/shoes/black
- head = /obj/item/clothing/head/beret
- mask = /obj/item/clothing/mask/gas/mime
- l_ear = /obj/item/radio/headset
- glasses = /obj/item/clothing/glasses/thermal/monocle
- id = /obj/item/card/id/syndicate
- pda = /obj/item/pda/mime
- backpack_contents = list(
- /obj/item/storage/box/survival = 1,
- /obj/item/reagent_containers/drinks/bottle/bottleofnothing = 1,
- /obj/item/toy/crayon/mime = 1,
- /obj/item/gun/projectile/automatic/pistol = 1,
- /obj/item/ammo_box/magazine/m10mm = 1,
- /obj/item/suppressor = 1,
- /obj/item/card/emag = 1,
- /obj/item/radio/uplink = 1,
- /obj/item/food/syndidonkpocket = 1,
- /obj/item/flashlight = 1
- )
-
-/datum/outfit/admin/mime_assassin/pre_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
- . = ..()
- if(H.gender == FEMALE)
- uniform = /obj/item/clothing/under/rank/civilian/mime/sexy
- suit = /obj/item/clothing/mask/gas/sexymime
-
-/datum/outfit/admin/mime_assassin/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
- . = ..()
- if(visualsOnly)
- return
-
- var/obj/item/pda/PDA = H.wear_pda
- if(istype(PDA))
- PDA.owner = H.real_name
- PDA.ownjob = "Mime"
- PDA.name = "PDA-[H.real_name] ([PDA.ownjob])"
-
- var/obj/item/card/id/I = H.wear_id
- if(istype(I))
- apply_to_card(I, H, list(ACCESS_MIME, ACCESS_THEATRE, ACCESS_MAINT_TUNNELS), "Mime")
- H.sec_hud_set_ID()
-
-/datum/outfit/admin/greytide
- name = "Greytide"
-
- uniform = /obj/item/clothing/under/color/grey
- back = /obj/item/storage/backpack
- shoes = /obj/item/clothing/shoes/brown
- mask = /obj/item/clothing/mask/gas
- l_ear = /obj/item/radio/headset
- id = /obj/item/card/id
- l_hand = /obj/item/storage/toolbox/mechanical
- r_hand = /obj/item/flag/grey
- backpack_contents = list(
- /obj/item/storage/box/survival = 1,
- /obj/item/flashlight = 1
- )
-
-/datum/outfit/admin/greytide/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
- . = ..()
- if(visualsOnly)
- return
-
- var/obj/item/card/id/I = H.wear_id
- if(istype(I))
- apply_to_card(I, H, list(ACCESS_MAINT_TUNNELS), "Greytide")
-
-/datum/outfit/admin/greytide/leader
- name = "Greytide Leader"
-
- belt = /obj/item/storage/belt/utility/full/multitool
- gloves = /obj/item/clothing/gloves/color/yellow
-
- backpack_contents = list(
- /obj/item/storage/box/survival = 1,
- /obj/item/clothing/head/welding = 1,
- /obj/item/flashlight = 1
- )
-
-/datum/outfit/admin/greytide/leader/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
- . = ..(H, TRUE)
- if(visualsOnly)
- return
-
- var/obj/item/card/id/I = H.wear_id
- if(istype(I))
- apply_to_card(I, H, list(ACCESS_MAINT_TUNNELS), "Greytide Leader")
-
-/datum/outfit/admin/greytide/xeno
- name = "Greytide Xeno"
-
- uniform = /obj/item/clothing/under/color/black
- suit = /obj/item/clothing/suit/xenos
- back = /obj/item/storage/backpack/satchel
- belt = /obj/item/storage/belt/utility/full/multitool
- gloves = /obj/item/clothing/gloves/color/yellow
- shoes = /obj/item/clothing/shoes/black
- head = /obj/item/clothing/head/xenos
- glasses = /obj/item/clothing/glasses/thermal
- l_pocket = /obj/item/tank/internals/emergency_oxygen/double
- r_pocket = /obj/item/toy/figure/xeno
- backpack_contents = list(
- /obj/item/storage/box/survival = 1,
- /obj/item/clothing/head/welding = 1,
- /obj/item/flashlight = 1
- )
-
-/datum/outfit/admin/greytide/xeno/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
- . = ..(H, TRUE)
- if(visualsOnly)
- return
-
- var/obj/item/card/id/I = H.wear_id
- if(istype(I))
- apply_to_card(I, H, list(ACCESS_MAINT_TUNNELS), "Legit Xenomorph")
-
-
-
-/datum/outfit/admin/musician
- name = "Musician"
-
- uniform = /obj/item/clothing/under/costume/singerb
- back = /obj/item/storage/backpack
- shoes = /obj/item/clothing/shoes/singerb
- gloves = /obj/item/clothing/gloves/color/white
- l_ear = /obj/item/radio/headset
- r_ear = /obj/item/clothing/ears/headphones
- pda = /obj/item/pda
- id = /obj/item/card/id
- backpack_contents = list(
- /obj/item/storage/box/survival = 1,
- /obj/item/flashlight = 1,
- /obj/item/instrument/violin = 1,
- /obj/item/instrument/piano_synth = 1,
- /obj/item/instrument/guitar = 1,
- /obj/item/instrument/eguitar = 1,
- /obj/item/instrument/accordion = 1,
- /obj/item/instrument/saxophone = 1,
- /obj/item/instrument/trombone = 1,
- /obj/item/instrument/harmonica = 1
- )
-
-/datum/outfit/admin/musician/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
- . = ..()
- if(visualsOnly)
- return
-
- var/obj/item/card/id/I = H.wear_id
- if(istype(I))
- apply_to_card(I, H, list(ACCESS_MAINT_TUNNELS), "Bard")
-
- var/obj/item/clothing/ears/headphones/P = H.r_ear
- if(istype(P))
- P.toggle_visual_notes(H) // activate them, display musical notes effect
-
-// Soviet Military
-
-/datum/outfit/admin/soviet
- name = "Soviet Tourist"
- uniform = /obj/item/clothing/under/new_soviet
- back = /obj/item/storage/backpack/satchel
- head = /obj/item/clothing/head/sovietsidecap
- id = /obj/item/card/id/data
- shoes = /obj/item/clothing/shoes/combat
- l_ear = /obj/item/radio/headset/soviet
- backpack_contents = list(
- /obj/item/storage/box/survival = 1
- )
-
-/datum/outfit/admin/soviet/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
- . = ..()
- if(visualsOnly)
- return
- H.real_name = "[capitalize(pick(GLOB.first_names_soviet))] [capitalize(pick(GLOB.last_names_soviet))]"
- H.name = H.real_name
- H.add_language("Zvezhan")
- H.set_default_language(GLOB.all_languages["Zvezhan"])
- var/obj/item/card/id/I = H.wear_id
- if(istype(I))
- apply_to_card(I, H, list(ACCESS_MAINT_TUNNELS), name)
- H.sec_hud_set_ID()
-
-/datum/outfit/admin/soviet/conscript
- name = "Soviet Conscript"
-
- r_pocket = /obj/item/flashlight/seclite
- r_hand = /obj/item/gun/projectile/shotgun/boltaction
- belt = /obj/item/gun/projectile/revolver/nagant
-
- backpack_contents = list(
- /obj/item/storage/box/soviet = 1,
- /obj/item/ammo_box/a762 = 4
- )
-
-/datum/outfit/admin/soviet/soldier
- name = "Soviet Soldier"
-
- gloves = /obj/item/clothing/gloves/combat
- suit = /obj/item/clothing/suit/sovietcoat
- glasses = /obj/item/clothing/glasses/sunglasses
- r_pocket = /obj/item/flashlight/seclite
- belt = /obj/item/gun/projectile/automatic/pistol/aps
-
- backpack_contents = list(
- /obj/item/storage/box/soviet = 1,
- /obj/item/lighter = 1,
- /obj/item/storage/fancy/cigarettes/cigpack_robust = 1,
- /obj/item/ammo_box/magazine/apsm10mm = 2
- )
-
-/datum/outfit/admin/soviet/officer
- name = "Soviet Officer"
-
- gloves = /obj/item/clothing/gloves/combat
- suit = /obj/item/clothing/suit/sovietcoat/officer
- uniform = /obj/item/clothing/under/new_soviet/sovietofficer
- head = /obj/item/clothing/head/sovietofficerhat
- glasses = /obj/item/clothing/glasses/sunglasses
- belt = /obj/item/gun/projectile/revolver/mateba
- l_pocket = /obj/item/melee/classic_baton/telescopic
- r_pocket = /obj/item/flashlight/seclite
-
- backpack_contents = list(
- /obj/item/storage/box/soviet = 1,
- /obj/item/lighter/zippo = 1,
- /obj/item/storage/fancy/cigarettes/cigpack_syndicate = 1,
- /obj/item/ammo_box/a357 = 2
- )
-
-/datum/outfit/admin/soviet/marine
- name = "Soviet Marine"
-
- gloves = /obj/item/clothing/gloves/combat
- suit = /obj/item/clothing/suit/space/hardsuit/soviet
- head = null
- mask = /obj/item/clothing/mask/gas
- glasses = /obj/item/clothing/glasses/night
- belt = /obj/item/storage/belt/military/assault/soviet/full
- r_pocket = /obj/item/melee/classic_baton/telescopic
- l_hand = /obj/item/gun/projectile/automatic/ak814
- suit_store = /obj/item/tank/internals/emergency_oxygen/double
-
- backpack_contents = list(
- /obj/item/storage/box/soviet = 1,
- /obj/item/gun/projectile/automatic/pistol/aps = 1,
- /obj/item/ammo_box/magazine/apsm10mm = 2,
- /obj/item/storage/fancy/cigarettes/cigpack_syndicate = 1,
- /obj/item/lighter/zippo/engraved = 1
- )
-
-/datum/outfit/admin/soviet/marine/captain
- name = "Soviet Marine Captain"
-
- uniform = /obj/item/clothing/under/new_soviet/sovietofficer
- suit = /obj/item/clothing/suit/space/hardsuit/soviet/commander
-
- backpack_contents = list(
- /obj/item/storage/box/soviet = 1,
- /obj/item/gun/projectile/revolver/mateba = 1,
- /obj/item/ammo_box/a357 = 2,
- /obj/item/storage/fancy/cigarettes/cigpack_syndicate = 1,
- /obj/item/lighter/zippo/engraved = 1
- )
-
-/datum/outfit/admin/soviet/admiral
- name = "Soviet Admiral"
-
- gloves = /obj/item/clothing/gloves/combat
- uniform = /obj/item/clothing/under/new_soviet/sovietadmiral
- head = /obj/item/clothing/head/sovietadmiralhat
- belt = /obj/item/gun/projectile/revolver/mateba
- glasses = /obj/item/clothing/glasses/thermal/eyepatch
- l_pocket = /obj/item/melee/classic_baton/telescopic
-
- backpack_contents = list(
- /obj/item/storage/box/soviet = 1,
- /obj/item/ammo_box/a357 = 3
- )
-
-/datum/outfit/admin/solgov_rep
- name = "Trans-Solar Federation Representative"
-
- uniform = /obj/item/clothing/under/solgov/rep
- back = /obj/item/storage/backpack/satchel
- glasses = /obj/item/clothing/glasses/hud/security/night
- gloves = /obj/item/clothing/gloves/color/white
- shoes = /obj/item/clothing/shoes/centcom
- l_ear = /obj/item/radio/headset/ert
- id = /obj/item/card/id/silver
- r_pocket = /obj/item/lighter/zippo/blue
- l_pocket = /obj/item/storage/fancy/cigarettes/cigpack_robustgold
- pda = /obj/item/pda
- backpack_contents = list(
- /obj/item/storage/box/responseteam = 1,
- /obj/item/bio_chip_implanter/dust = 1,
- /obj/item/bio_chip_implanter/death_alarm = 1,
- )
-
- bio_chips = list(/obj/item/bio_chip/mindshield,
- /obj/item/bio_chip/death_alarm
- )
-
-/datum/outfit/admin/solgov_rep/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
- . = ..()
- if(visualsOnly)
- return
-
- var/obj/item/card/id/I = H.wear_id
- if(istype(I))
- apply_to_card(I, H, get_all_centcom_access(), name, "lifetimeid")
- I.assignment = "Trans-Solar Federation Representative"
- H.sec_hud_set_ID()
-
-
-/datum/outfit/admin/solgov
- name = "TSF Marine"
- uniform = /obj/item/clothing/under/solgov
- suit = /obj/item/clothing/suit/armor/bulletproof
- back = /obj/item/storage/backpack/ert/solgov
- belt = /obj/item/storage/belt/military/assault/marines/full
- head = /obj/item/clothing/head/soft/solgov/marines
- glasses = /obj/item/clothing/glasses/night
- gloves = /obj/item/clothing/gloves/combat
- shoes = /obj/item/clothing/shoes/combat
- l_ear = /obj/item/radio/headset/ert/alt/solgov
- id = /obj/item/card/id
- l_hand = /obj/item/gun/projectile/automatic/shotgun/bulldog
- suit_store = /obj/item/gun/projectile/automatic/pistol/m1911
- r_pocket = /obj/item/flashlight/seclite
- pda = /obj/item/pda
- box = /obj/item/storage/box/responseteam
- backpack_contents = list(
- /obj/item/clothing/shoes/magboots = 1,
- /obj/item/whetstone = 1,
- /obj/item/clothing/mask/gas/explorer/marines = 1,
- /obj/item/reagent_containers/hypospray/autoinjector/survival = 1
- )
- cybernetic_implants = list(
- /obj/item/organ/internal/cyberimp/arm/flash,
- /obj/item/organ/internal/cyberimp/chest/nutriment/hardened,
- /obj/item/organ/internal/cyberimp/eyes/hud/security
- )
- bio_chips = list(/obj/item/bio_chip/mindshield,
- /obj/item/bio_chip/death_alarm
- )
-
- var/is_solgov_lieutenant = FALSE
-
-
-/datum/outfit/admin/solgov/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
- . = ..()
- if(visualsOnly)
- return
-
- if(is_solgov_lieutenant)
- H.real_name = "Lieutenant [pick(GLOB.last_names)]"
- else
- H.real_name = "[pick("Corporal", "Sergeant", "Staff Sergeant", "Sergeant First Class", "Master Sergeant", "Sergeant Major")] [pick(GLOB.last_names)]"
- H.name = H.real_name
- var/obj/item/card/id/I = H.wear_id
- I.assignment = name
- if(istype(I) && is_solgov_lieutenant)
- apply_to_card(I, H, get_centcom_access("Emergency Response Team Leader"), name, "lifetimeid")
- else if(istype(I))
- apply_to_card(I, H, get_centcom_access("Emergency Response Team Member"), name, "lifetimeid")
- H.sec_hud_set_ID()
-
-/datum/outfit/admin/solgov/lieutenant
- name = "TSF Lieutenant"
- uniform = /obj/item/clothing/under/solgov/command
- head = /obj/item/clothing/head/beret/solgov
- glasses = /obj/item/clothing/glasses/night
- back = /obj/item/storage/backpack/satchel
- shoes = /obj/item/clothing/shoes/magboots/elite
- l_ear = /obj/item/radio/headset/ert/alt/commander/solgov
- l_hand = null
- belt = /obj/item/melee/baton/loaded
- suit_store = /obj/item/gun/projectile/automatic/pistol/deagle
- l_pocket = /obj/item/pinpointer/advpinpointer
- backpack_contents = list(
- /obj/item/storage/box/handcuffs = 1,
- /obj/item/reagent_containers/hypospray/autoinjector/survival = 1,
- /obj/item/clothing/mask/gas/explorer/marines = 1,
- /obj/item/ammo_box/magazine/m50 = 3
- )
- is_solgov_lieutenant = TRUE
-
-/datum/outfit/admin/solgov/elite
- name = "MARSOC Marine"
- uniform = /obj/item/clothing/under/solgov/elite
- suit = /obj/item/clothing/suit/space/hardsuit/ert/solgov
- head = null
- mask = /obj/item/clothing/mask/gas/explorer/marines
- belt = /obj/item/storage/belt/military/assault/marines/elite/full
- shoes = /obj/item/clothing/shoes/magboots/elite
- l_hand = /obj/item/gun/projectile/automatic/ar
- backpack_contents = list(
- /obj/item/whetstone = 1,
- /obj/item/reagent_containers/hypospray/autoinjector/survival = 1
- )
- cybernetic_implants = list(
- /obj/item/organ/internal/cyberimp/eyes/hud/security,
- /obj/item/organ/internal/cyberimp/chest/nutriment/hardened,
- /obj/item/organ/internal/cyberimp/brain/anti_stam/hardened,
- /obj/item/organ/internal/cyberimp/arm/flash,
- /obj/item/organ/internal/eyes/cybernetic/shield
- )
-
-/datum/outfit/admin/solgov/elite/lieutenant
- name = "MARSOC Lieutenant"
- uniform = /obj/item/clothing/under/solgov/command/elite
- suit = /obj/item/clothing/suit/space/hardsuit/ert/solgov/command
- head = null
- mask = /obj/item/clothing/mask/gas/explorer/marines
- glasses = /obj/item/clothing/glasses/night
- belt = /obj/item/melee/baton/loaded
- l_hand = null
- suit_store = /obj/item/gun/projectile/automatic/pistol/deagle
- l_pocket = /obj/item/pinpointer/advpinpointer
- l_ear = /obj/item/radio/headset/ert/alt/commander/solgov
- backpack_contents = list(
- /obj/item/storage/box/handcuffs = 1,
- /obj/item/reagent_containers/hypospray/autoinjector/survival = 1,
- /obj/item/ammo_box/magazine/m50 = 3
- )
- is_solgov_lieutenant = TRUE
-
-/datum/outfit/admin/trader
- name = "Trader"
- uniform = /obj/item/clothing/under/rank/cargo/tech
- back = /obj/item/storage/backpack/industrial
- belt = /obj/item/melee/classic_baton
- shoes = /obj/item/clothing/shoes/black
- l_ear = /obj/item/radio/headset
- glasses = /obj/item/clothing/glasses/sunglasses
- id = /obj/item/card/id/supply
- pda = /obj/item/pda
- backpack_contents = list(
- /obj/item/hand_labeler = 1,
- /obj/item/hand_labeler_refill = 2
- )
- box = /obj/item/storage/box/survival
-
-/datum/outfit/admin/trader/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
- . = ..()
- if(visualsOnly)
- return
-
- var/obj/item/card/id/I = H.wear_id
- if(istype(I))
- apply_to_card(I, H, list(ACCESS_TRADE_SOL, ACCESS_MAINT_TUNNELS, ACCESS_EXTERNAL_AIRLOCKS), name)
- H.sec_hud_set_ID()
-
-/datum/outfit/admin/trader/sol
- name = "Trans-Solar Federation Trader"
- suit = /obj/item/clothing/suit/jacket/bomber/cargo
- head = /obj/item/clothing/head/soft/cargo
-
-/datum/outfit/admin/trader/cyber
- name = "Cybersun Industries Trader"
- uniform = /obj/item/clothing/under/syndicate/tacticool
- suit = /obj/item/clothing/suit/jacket/bomber/syndicate
- gloves = /obj/item/clothing/gloves/color/black
- shoes = /obj/item/clothing/shoes/combat
- belt = /obj/item/melee/classic_baton/telescopic
- back = /obj/item/storage/backpack/security
- box = /obj/item/storage/box/survival
-
-/datum/outfit/admin/trader/commie
- name = "USSP Trader"
- uniform = /obj/item/clothing/under/new_soviet
- suit = /obj/item/clothing/suit/sovietcoat
- head = /obj/item/clothing/head/ushanka
- box = /obj/item/storage/box/soviet
-
-/datum/outfit/admin/trader/commie/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
- . = ..()
- if(visualsOnly)
- return
- H.add_language("Zvezhan")
-
-/datum/outfit/admin/trader/unathi
- name = "Glint-Scales Trader"
- uniform = /obj/item/clothing/under/rank/cargo/qm
- suit = /obj/item/clothing/suit/unathi/robe
- shoes = /obj/item/clothing/shoes/footwraps
-
-/datum/outfit/admin/trader/vulp
- name = "Steadfast Trading Co. Trader"
- uniform = /obj/item/clothing/under/rank/cargo/qm/formal
- suit = /obj/item/clothing/suit/jacket/leather/overcoat
- belt = /obj/item/melee/classic_baton/telescopic
-
-/datum/outfit/admin/trader/ipc
- name = "Synthetic Union Trader"
- uniform = /obj/item/clothing/under/misc/vice
- suit = /obj/item/clothing/suit/storage/iaa/blackjacket/armored
- belt = /obj/item/melee/classic_baton/telescopic
- back = /obj/item/storage/backpack/robotics
-
-/datum/outfit/admin/trader/vox
- name = "Skipjack Trader"
- uniform = /obj/item/clothing/under/vox/vox_casual
- suit = /obj/item/clothing/suit/hooded/vox_robes
- gloves = /obj/item/clothing/gloves/color/yellow/vox
- shoes = /obj/item/clothing/shoes/magboots/vox
- belt = /obj/item/melee/classic_baton/telescopic
- mask = /obj/item/clothing/mask/breath/vox/respirator
- suit_store = /obj/item/tank/internals/emergency_oxygen/double/vox
- box = /obj/item/storage/box/survival_vox
-
-/datum/outfit/admin/trader/skrell
- name = "Solar-Central Compact Trader"
- uniform = /obj/item/clothing/under/misc/durathread
- suit = /obj/item/clothing/suit/space/skrell/white
- belt = /obj/item/melee/classic_baton/telescopic
-
-/datum/outfit/admin/trader/grey
- name = "Technocracy Trader"
- uniform = /obj/item/clothing/under/costume/psyjump
- suit = /obj/item/clothing/suit/jacket/bomber/robo
- belt = /obj/item/melee/classic_baton/telescopic
- back = /obj/item/storage/backpack/robotics
-
-/datum/outfit/admin/trader/nian
- name = "Merchant Guild Trader"
- uniform = /obj/item/clothing/under/suit/really_black
- suit = /obj/item/clothing/suit/pimpcoat
- shoes = /obj/item/clothing/shoes/fluff/noble_boot
- belt = /obj/item/melee/classic_baton/ntcane
-
-/datum/outfit/admin/chrono
- name = "Chrono Legionnaire"
-
- uniform = /obj/item/clothing/under/syndicate
- suit = /obj/item/clothing/suit/space/chronos
- back = /obj/item/chrono_eraser
- gloves = /obj/item/clothing/gloves/combat
- shoes = /obj/item/clothing/shoes/magboots/advance
- head = /obj/item/clothing/head/helmet/space/chronos
- mask = /obj/item/clothing/mask/gas/syndicate
- glasses = /obj/item/clothing/glasses/night
- id = /obj/item/card/id/syndicate
- suit_store = /obj/item/tank/internals/emergency_oxygen/double
-
-/datum/outfit/admin/chrono/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
- . = ..()
- if(visualsOnly)
- return
-
- var/obj/item/card/id/I = H.wear_id
- if(istype(I))
- apply_to_card(I, H, get_all_accesses() + get_all_centcom_access(), name, "syndie")
-
-/datum/outfit/admin/spacegear
- name = "Standard Space Gear"
-
- uniform = /obj/item/clothing/under/color/grey
- suit = /obj/item/clothing/suit/space
- back = /obj/item/tank/jetpack/oxygen
- shoes = /obj/item/clothing/shoes/magboots
- head = /obj/item/clothing/head/helmet/space
- mask = /obj/item/clothing/mask/breath
- l_ear = /obj/item/radio/headset
- id = /obj/item/card/id
-
-/datum/outfit/admin/spacegear/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
- . = ..()
- if(visualsOnly)
- return
-
- if(istype(H.back, /obj/item/tank/jetpack))
- var/obj/item/tank/jetpack/J = H.back
- J.turn_on()
- J.toggle_internals(H)
-
- var/obj/item/card/id/I = H.wear_id
- if(istype(I))
- apply_to_card(I, H, get_all_accesses(), "Space Explorer")
-
-/datum/outfit/admin/modsuit
- name = "MODsuit - Generic"
- back = /obj/item/mod/control/pre_equipped/standard
- suit_store = /obj/item/tank/internals/oxygen
- mask = /obj/item/clothing/mask/breath
- shoes = /obj/item/clothing/shoes/magboots
- id = /obj/item/card/id
-
-/datum/outfit/admin/modsuit/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
- . = ..()
- if(visualsOnly)
- return
-
- if(istype(H.back, /obj/item/tank/internals/oxygen))
- var/obj/item/tank/internals/oxygen/J = H.back
- J.toggle_internals(H)
-
- var/obj/item/card/id/I = H.wear_id
- if(istype(I))
- apply_to_card(I, H, get_all_accesses(), "MODsuit Tester")
-
-/datum/outfit/admin/modsuit/engineer
- name = "MODsuit - Engineer"
- back = /obj/item/mod/control/pre_equipped/engineering
-
-/datum/outfit/admin/modsuit/ce
- name = "MODsuit - CE"
- back = /obj/item/mod/control/pre_equipped/advanced
- shoes = /obj/item/clothing/shoes/magboots/advance
-
-/datum/outfit/admin/modsuit/mining
- name = "MODsuit - Mining"
- back = /obj/item/mod/control/pre_equipped/mining/asteroid
-
-/datum/outfit/admin/modsuit/syndi
- name = "MODsuit - Syndi"
- back = /obj/item/mod/control/pre_equipped/traitor
- shoes = /obj/item/clothing/shoes/magboots/syndie
-
-/// Technically not a MODsuit, we'll bundle it up in here for the future when it does become one
-/datum/outfit/admin/modsuit/wizard
- name = "Hardsuit - Wizard"
- suit = /obj/item/clothing/suit/space/hardsuit/wizard
- shoes = /obj/item/clothing/shoes/magboots/wizard
-
-/datum/outfit/admin/modsuit/medical
- name = "MODsuit - Medical"
- back = /obj/item/mod/control/pre_equipped/medical
-
-/datum/outfit/admin/modsuit/atmos
- name = "MODsuit - Atmos"
- back = /obj/item/mod/control/pre_equipped/atmospheric
-
-/datum/outfit/admin/tournament
- name = "Tournament Generic"
- suit = /obj/item/clothing/suit/armor/vest
- shoes = /obj/item/clothing/shoes/black
- head = /obj/item/clothing/head/helmet/thunderdome
- r_pocket = /obj/item/grenade/smokebomb
- l_hand = /obj/item/kitchen/knife
- r_hand = /obj/item/gun/energy/pulse/destroyer
-
-/datum/outfit/admin/tournament/red
- name = "Tournament Standard Red"
- uniform = /obj/item/clothing/under/color/red
-
-/datum/outfit/admin/tournament/green
- name = "Tournament Standard Green"
- uniform = /obj/item/clothing/under/color/green
-
-/// gangster are supposed to fight each other. --rastaf0
-/datum/outfit/admin/tournament/tournament_gangster
- name = "Tournament Gangster"
-
- uniform = /obj/item/clothing/under/rank/security/detective
- suit = /obj/item/clothing/suit/storage/det_suit
- shoes = /obj/item/clothing/shoes/black
- head = /obj/item/clothing/head/det_hat
- glasses = /obj/item/clothing/glasses/thermal/monocle
- l_pocket = /obj/item/ammo_box/a357
- r_hand = /obj/item/gun/projectile/automatic/proto
-
-/// Steven Seagal FTW
-/datum/outfit/admin/tournament/tournament_chef
- name = "Tournament Chef"
-
- uniform = /obj/item/clothing/under/rank/civilian/chef
- suit = /obj/item/clothing/suit/chef
- shoes = /obj/item/clothing/shoes/black
- head = /obj/item/clothing/head/chefhat
- l_pocket = /obj/item/kitchen/knife
- r_pocket = /obj/item/kitchen/knife
- l_hand = /obj/item/kitchen/knife
- r_hand = /obj/item/kitchen/rollingpin
-
-/datum/outfit/admin/tournament/tournament_janitor
- name = "Tournament Janitor"
-
- uniform = /obj/item/clothing/under/rank/civilian/janitor
- back = /obj/item/storage/backpack
- shoes = /obj/item/clothing/shoes/black
- l_hand = /obj/item/reagent_containers/glass/bucket
- backpack_contents = list(
- /obj/item/grenade/chem_grenade/cleaner = 2,
- /obj/item/stack/tile/plasteel = 7
- )
-
-/datum/outfit/admin/tournament/tournament_janitor/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
- . = ..()
- if(visualsOnly)
- return
-
- var/obj/item/reagent_containers/R = H.l_hand
- if(istype(R))
- R.reagents.add_reagent("water", 70)
-
-/datum/outfit/admin/survivor
- name = "Survivor"
-
- uniform = /obj/item/clothing/under/misc/overalls
- back = /obj/item/storage/backpack
- gloves = /obj/item/clothing/gloves/color/latex
- shoes = /obj/item/clothing/shoes/white
- l_ear = /obj/item/radio/headset
- id = /obj/item/card/id
- backpack_contents = list(
- /obj/item/storage/box/survival = 1
- )
-
-/datum/outfit/admin/survivor/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
- . = ..()
-
- for(var/obj/item/I in H.contents)
- if(!istype(I, /obj/item/bio_chip))
- I.add_mob_blood(H)
-
- if(visualsOnly)
- return
-
- var/obj/item/card/id/I = H.wear_id
- if(istype(I))
- apply_to_card(I, H, list(ACCESS_MAINT_TUNNELS), "Survivor")
-
-/datum/outfit/admin/masked_killer
- name = "Masked Killer"
-
- uniform = /obj/item/clothing/under/misc/overalls
- suit = /obj/item/clothing/suit/apron
- back = /obj/item/storage/backpack
- gloves = /obj/item/clothing/gloves/color/latex
- shoes = /obj/item/clothing/shoes/white
- head = /obj/item/clothing/head/welding
- mask = /obj/item/clothing/mask/surgical
- l_ear = /obj/item/radio/headset
- glasses = /obj/item/clothing/glasses/thermal/monocle
- id = /obj/item/card/id/syndicate
- l_pocket = /obj/item/kitchen/knife
- r_pocket = /obj/item/scalpel
- r_hand = /obj/item/fireaxe
- backpack_contents = list(
- /obj/item/storage/box/survival = 1,
- /obj/item/flashlight = 1
- )
-
-/datum/outfit/admin/masked_killer/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
- . = ..()
-
- for(var/obj/item/I in H.contents)
- if(!istype(I, /obj/item/bio_chip))
- I.add_mob_blood(H)
-
- if(visualsOnly)
- return
-
- var/obj/item/card/id/I = H.wear_id
- if(istype(I))
- apply_to_card(I, H, list(ACCESS_MAINT_TUNNELS), "Masked Killer", "syndie")
-
-/datum/outfit/admin/singuloth_knight
- name = "Singuloth Knight"
-
- uniform = /obj/item/clothing/under/syndicate/combat
- suit = /obj/item/clothing/suit/space/hardsuit/singuloth
- back = /obj/item/storage/backpack/satchel
- l_hand = /obj/item/knighthammer
- belt = /obj/item/claymore/ceremonial
- gloves = /obj/item/clothing/gloves/combat
- shoes = /obj/item/clothing/shoes/magboots
- mask = /obj/item/clothing/mask/breath
- l_ear = /obj/item/radio/headset/ert
- glasses = /obj/item/clothing/glasses/meson/cyber
- id = /obj/item/card/id
- suit_store = /obj/item/tank/internals/oxygen
- backpack_contents = list(
- /obj/item/storage/box/survival = 1,
- /obj/item/flashlight = 1
- )
-
-/datum/outfit/admin/singuloth_knight/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
- . = ..()
- if(visualsOnly)
- return
-
- var/obj/item/card/id/I = H.wear_id
- if(istype(I))
- apply_to_card(I, H, get_all_accesses(), "Singuloth Knight")
-
-/datum/outfit/admin/dark_lord
- name = "Dark Lord"
-
- uniform = /obj/item/clothing/under/color/black
- suit = /obj/item/clothing/suit/hooded/chaplain_hoodie
- back = /obj/item/storage/backpack
- gloves = /obj/item/clothing/gloves/combat
- shoes = /obj/item/clothing/shoes/chameleon/noslip
- l_ear = /obj/item/radio/headset/syndicate
- id = /obj/item/card/id/syndicate
- l_hand = /obj/item/dualsaber/red
- backpack_contents = list(
- /obj/item/storage/box/survival = 1,
- /obj/item/flashlight = 1,
- )
-
-/datum/outfit/admin/dark_lord/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
- . = ..()
- if(visualsOnly)
- return
-
- var/obj/item/clothing/suit/hooded/chaplain_hoodie/C = H.wear_suit
- if(istype(C))
- C.name = "dark lord robes"
- C.hood.name = "dark lord hood"
-
- var/obj/item/card/id/I = H.wear_id
- if(istype(I))
- apply_to_card(I, H, get_all_accesses(), "Dark Lord", "syndie")
-
-
-/datum/outfit/admin/ancient_vampire
- name = "Ancient Vampire"
-
- uniform = /obj/item/clothing/under/suit/victsuit/red
- suit = /obj/item/clothing/suit/draculacoat
- back = /obj/item/storage/backpack
- gloves = /obj/item/clothing/gloves/combat
- shoes = /obj/item/clothing/shoes/chameleon/noslip
- l_ear = /obj/item/radio/headset/syndicate
- id = /obj/item/card/id
- backpack_contents = list(
- /obj/item/storage/box/survival = 1,
- /obj/item/flashlight = 1,
- /obj/item/clothing/under/color/black = 1
- )
-
-/datum/outfit/admin/ancient_vampire/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
- . = ..()
- if(visualsOnly)
- return
-
- var/obj/item/clothing/suit/hooded/chaplain_hoodie/C = new(H.loc)
- if(istype(C))
- C.name = "ancient robes"
- C.hood.name = "ancient hood"
- H.equip_to_slot_or_del(C, ITEM_SLOT_IN_BACKPACK)
-
- var/obj/item/card/id/I = H.wear_id
- if(istype(I))
- apply_to_card(I, H, get_all_accesses(), "Ancient One", "data")
-
-/datum/outfit/admin/ancient_vampire/on_mind_initialize(mob/living/carbon/human/H)
- . = ..()
- H.mind.make_vampire()
- var/datum/antagonist/vampire/V = H.mind.has_antag_datum(/datum/antagonist/vampire)
- V.bloodusable = 9999
- V.bloodtotal = 9999
- V.add_subclass(SUBCLASS_ANCIENT, FALSE)
- H.dna.SetSEState(GLOB.jumpblock, TRUE)
- singlemutcheck(H, GLOB.jumpblock, MUTCHK_FORCED)
- H.update_mutations()
- H.gene_stability = 100
-
-/datum/outfit/admin/ancient_mindflayer
- name = "Ancient Mindflayer"
-
- // Shamelessly stolen from the `Dark Lord`
- uniform = /obj/item/clothing/under/color/black
- back = /obj/item/storage/backpack
- gloves = /obj/item/clothing/gloves/color/yellow
- shoes = /obj/item/clothing/shoes/chameleon/noslip
- l_ear = /obj/item/radio/headset/syndicate
- id = /obj/item/card/id
- backpack_contents = list(
- /obj/item/storage/box/survival = 1,
- /obj/item/flashlight = 1,
- )
-
-/datum/outfit/admin/ancient_mindflayer/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
- . = ..()
- if(visualsOnly)
- return
-
- var/obj/item/clothing/suit/hooded/chaplain_hoodie/C = new(H.loc)
- if(istype(C))
- C.name = "ancient robes"
- C.hood.name = "ancient hood"
- H.equip_to_slot_or_del(C, ITEM_SLOT_IN_BACKPACK)
-
- var/obj/item/card/id/I = H.wear_id
- if(istype(I))
- apply_to_card(I, H, get_all_accesses(), "Ancient One", "data")
-
-/datum/outfit/admin/ancient_mindflayer/on_mind_initialize(mob/living/carbon/human/H)
- . = ..()
- H.mind.make_mind_flayer()
- var/datum/antagonist/mindflayer/flayer = H.mind.has_antag_datum(/datum/antagonist/mindflayer)
- flayer.usable_swarms = 9999
- H.dna.SetSEState(GLOB.jumpblock, TRUE)
- singlemutcheck(H, GLOB.jumpblock, MUTCHK_FORCED)
- H.update_mutations()
- H.gene_stability = 100
-
-/datum/outfit/admin/wizard
- name = "Blue Wizard"
- uniform = /obj/item/clothing/under/color/lightpurple
- suit = /obj/item/clothing/suit/wizrobe
- back = /obj/item/storage/backpack
- shoes = /obj/item/clothing/shoes/sandal
- head = /obj/item/clothing/head/wizard
- l_ear = /obj/item/radio/headset
- id = /obj/item/card/id
- r_pocket = /obj/item/teleportation_scroll
- l_hand = /obj/item/staff
- r_hand = /obj/item/spellbook
- backpack_contents = list(
- /obj/item/storage/box/engineer = 1
- )
-
-/datum/outfit/admin/wizard/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
- . = ..()
- if(visualsOnly)
- return
-
- var/obj/item/card/id/I = H.wear_id
- if(istype(I))
- apply_to_card(I, H, get_all_accesses(), "Wizard")
-
-/datum/outfit/admin/wizard/red
- name = "Wizard - Red Wizard"
-
- suit = /obj/item/clothing/suit/wizrobe/red
- head = /obj/item/clothing/head/wizard/red
-
-/datum/outfit/admin/wizard/marisa
- name = "Wizard - Marisa Wizard"
-
- suit = /obj/item/clothing/suit/wizrobe/marisa
- shoes = /obj/item/clothing/shoes/sandal/marisa
- head = /obj/item/clothing/head/wizard/marisa
-
-/datum/outfit/admin/wizard/arch
- name = "Wizard - Arch Wizard"
-
- suit = /obj/item/clothing/suit/wizrobe/magusred
- head = /obj/item/clothing/head/wizard/magus
- belt = /obj/item/storage/belt/wands/full
- l_hand = null
- backpack_contents = list(
- /obj/item/storage/box/engineer = 1,
- /obj/item/clothing/suit/space/hardsuit/wizard/arch = 1,
- /obj/item/clothing/shoes/magboots = 1,
- /obj/item/kitchen/knife/ritual = 1,
- /obj/item/clothing/suit/wizrobe/red = 1,
- /obj/item/clothing/head/wizard/red = 1
- )
-
-/datum/outfit/admin/wizard/arch/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
- . = ..()
- if(visualsOnly)
- return
- var/obj/item/spellbook/B = H.r_hand
- if(istype(B))
- B.owner = H // force-bind it so it can never be stolen, no matter what.
- B.name = "Archwizard Spellbook"
- B.uses = 50
- var/obj/item/card/id/I = H.wear_id
- if(istype(I))
- apply_to_card(I, H, get_all_accesses(), "Arch Wizard")
-
-
-/datum/outfit/admin/dark_priest
- name = "Dark Priest"
-
- uniform = /obj/item/clothing/under/color/black
- suit = /obj/item/clothing/suit/hooded/chaplain_hoodie
- back = /obj/item/storage/backpack
- head = /obj/item/clothing/head/hooded/chaplain_hood
- gloves = /obj/item/clothing/gloves/color/black
- shoes = /obj/item/clothing/shoes/black
- l_ear = /obj/item/radio/headset/syndicate
- id = /obj/item/card/id/syndicate
- r_hand = /obj/item/nullrod/armblade
- backpack_contents = list(
- /obj/item/storage/box/survival = 1,
- /obj/item/flashlight = 1,
- )
- bio_chips = list(
- /obj/item/bio_chip/dust
- )
-
-/datum/outfit/admin/dark_priest/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
- . = ..()
- if(visualsOnly)
- return
- var/obj/item/card/id/I = H.wear_id
- if(istype(I))
- apply_to_card(I, H, get_all_accesses(), "Dark Priest", "syndie")
- var/obj/item/nullrod/armblade/B = H.r_hand
- if(istype(B))
- B.force = 20
- B.name = "blessing of the reaper"
- B.desc = "Sometimes, someone's just gotta die."
- var/obj/item/radio/headset/R = H.l_ear
- if(istype(R))
- R.flags |= NODROP
-
-/datum/outfit/admin/honksquad
- name = "Honksquad"
-
- uniform = /obj/item/clothing/under/rank/civilian/clown
- mask = /obj/item/clothing/mask/gas/clown_hat
- back = /obj/item/storage/backpack/clown
- id = /obj/item/card/id/clown
-
- backpack_contents = list(
- /obj/item/storage/box/survival = 1,
- /obj/item/bikehorn = 1,
- /obj/item/stamp/clown = 1,
- /obj/item/toy/crayon/rainbow = 1,
- /obj/item/reagent_containers/spray/waterflower = 1,
- /obj/item/food/grown/banana = 1,
- )
-
- shoes = /obj/item/clothing/shoes/clown_shoes
- suit = /obj/item/clothing/suit/storage/det_suit
- pda = /obj/item/pda/clown
- l_ear = /obj/item/radio/headset
- r_pocket = /obj/item/reagent_containers/patch/jestosterone
-
-/datum/outfit/admin/honksquad/pre_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
- . = ..()
- if(H.gender == FEMALE)
- uniform = /obj/item/clothing/under/rank/civilian/clown/sexy
- mask = /obj/item/clothing/mask/gas/clown_hat/sexy
-
- if(prob(50))
- // You have to do it like this to make it work with assoc lists without a runtime.
- // Trust me.
- backpack_contents.Add(/obj/item/gun/energy/clown)
- backpack_contents[/obj/item/gun/energy/clown] = 1 // Amount. Not boolean. Do not TRUE this. You turkey.
- else
- backpack_contents.Add(/obj/item/gun/throw/piecannon)
- backpack_contents[/obj/item/gun/throw/piecannon] = 1
-
- var/clown_rank = pick("Trickster First Class", "Master Clown", "Major Prankster")
- var/clown_name = pick(GLOB.clown_names)
- H.real_name = "[clown_rank] [clown_name]"
-
-/datum/outfit/admin/honksquad/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
- . = ..()
- if(visualsOnly)
- return
-
- // Setup their clumsy and comic sans gene
- H.dna.SetSEState(GLOB.clumsyblock, TRUE)
- H.dna.SetSEState(GLOB.comicblock, TRUE)
- H.check_mutations = TRUE
-
- // Setup their headset
- var/obj/item/radio/R = H.l_ear
- if(istype(R))
- R.set_frequency(DTH_FREQ) // Clowns can be part of "special operations"
-
- // And their PDA
- var/obj/item/pda/P = H.wear_pda
- if(istype(P))
- P.owner = H.real_name
- P.ownjob = "Emergency Response Clown"
- P.name = "PDA-[H.real_name] ([P.ownjob])"
-
- // And their ID
- var/obj/item/card/id/I = H.wear_id
- if(istype(I))
- apply_to_card(I, H, list(ACCESS_CLOWN), "Emergency Response Clown")
- H.sec_hud_set_ID()
-
-/datum/outfit/admin/observer
- name = "Observer"
-
- uniform = /obj/item/clothing/under/costume/tourist_suit
- back = /obj/item/storage/backpack/satchel
- shoes = /obj/item/clothing/shoes/black
- box = /obj/item/storage/box/survival
- backpack_contents = list(
- /obj/item/bio_chip_implanter/dust = 1
- )
-
-/datum/outfit/admin/observer/plasmaman
- name = "Observer (Plasma)"
-
- uniform = /obj/item/clothing/under/plasmaman/assistant
- head = /obj/item/clothing/head/helmet/space/plasmaman/assistant
- mask = /obj/item/clothing/mask/breath
- belt = /obj/item/tank/internals/plasmaman/belt/full
- box = /obj/item/storage/box/survival_plasmaman
-
-/datum/outfit/admin/observer/vox
- name = "Observer (Vox)"
-
- mask = /obj/item/clothing/mask/breath/vox
- belt = /obj/item/tank/internals/emergency_oxygen/double/vox
- box = /obj/item/storage/box/survival_vox
-
-/datum/outfit/admin/enforcer
- name = "Oblivion Enforcer"
-
- uniform = /obj/item/clothing/under/color/white/enforcer
- shoes = /obj/item/clothing/shoes/white/enforcer
- back = /obj/item/storage/backpack/satchel
- id = /obj/item/card/id/data
- //The hood on this gets enabled on the after-equip proc.
- suit = /obj/item/clothing/suit/hooded/oblivion
- gloves = /obj/item/clothing/gloves/color/white/supermatter_immune
- mask = /obj/item/clothing/mask/gas/voice_modulator/oblivion
- l_ear = /obj/item/radio/headset
- suit_store = /obj/item/supermatter_halberd
- r_pocket = /obj/item/tank/internals/emergency_oxygen/double
- box = /obj/item/storage/box/wizard
-
- //The spells that the enforcer has.
- var/list/spell_paths = list(/datum/spell/aoe/conjure/summon_supermatter,
- /datum/spell/charge_up/bounce/lightning, /datum/spell/summonitem)
-
-/datum/outfit/admin/enforcer/post_equip(mob/living/carbon/human/H)
- . = ..()
-
- ADD_TRAIT(H, SM_HALLUCINATION_IMMUNE, MAGIC_TRAIT)
-
- H.real_name = "Unknown" //Enforcers sacrifice their name to Oblivion for their power
-
- var/obj/item/clothing/suit/hooded/oblivion/robes = H.wear_suit
- if(istype(robes))
- robes.ToggleHood()
-
- var/obj/item/card/id/I = H.wear_id
- if(istype(I))
- apply_to_card(I, H, get_all_accesses(), "Oblivion Enforcer")
-
-/datum/outfit/admin/enforcer/on_mind_initialize(mob/living/carbon/human/H)
- . = ..()
- for(var/spell_path in spell_paths)
- var/S = new spell_path
- H.mind.AddSpell(S)
-
-/datum/outfit/admin/viper
- name = "TSF Viper Infiltrator"
-
- uniform = /obj/item/clothing/under/solgov/viper
- back = /obj/item/storage/backpack/satchel
- belt = /obj/item/storage/belt/viper
- gloves = /obj/item/clothing/gloves/color/black
- shoes = /obj/item/clothing/shoes/jackboots
- head = null // will end up being the bandana
- mask = /obj/item/clothing/mask/bandana/black // will end up being a cigar
- l_ear = /obj/item/radio/headset/ert/alt/solgov
- glasses = /obj/item/clothing/glasses/thermal/eyepatch
- id = /obj/item/card/id
- l_pocket = /obj/item/kitchen/knife/combat
- r_pocket = /obj/item/gun/projectile/automatic/pistol
- box = /obj/item/storage/box/responseteam
-
- backpack_contents = list(
- /obj/item/storage/box/smoke_grenades = 1,
- /obj/item/lighter/zippo = 1,
- /obj/item/clothing/mask/cigarette/cigar = 3,
- /obj/item/clothing/mask/gas/explorer = 1
- )
-
- bio_chips = list(/obj/item/bio_chip/stealth)
-
- cybernetic_implants = list(
- /obj/item/organ/internal/cyberimp/eyes/hud/security,
- /obj/item/organ/internal/cyberimp/chest/nutriment/hardened
- )
-
-/datum/outfit/admin/viper/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
- . = ..()
- if(visualsOnly)
- return
- var/codename = pick("Viper", "Serpent", "Python", "Boa", "Basilisk", "Snake", "Mamba", "Sidewinder")
- if(prob(50))
- var/codename_prefix = pick("Exposed", "Unveiled", "Phantom", "Mirage", "Punished", "Invisible", "Swift")
- codename = "[codename_prefix] [codename]"
- H.rename_character(null, codename)
-
- var/hair_color = "#361A00"
-
- var/obj/item/organ/external/head/head_organ = H.get_organ("head")
- head_organ.h_style = "Bedhead 2"
- head_organ.f_style = "Full Beard"
- head_organ.hair_colour = hair_color
- head_organ.sec_facial_colour = hair_color
- head_organ.facial_colour = hair_color
- head_organ.sec_hair_colour = hair_color
- H.update_hair()
- H.update_fhair()
- H.update_dna()
-
- H.wear_mask.adjustmask(H) // push it back on the head
- equip_item(H, /obj/item/clothing/mask/cigarette/cigar, ITEM_SLOT_MASK) // get them their cigar
- if(istype(H.glasses, /obj/item/clothing/glasses)) // this is gonna be always true
- var/obj/item/clothing/glasses/glassass = H.glasses
- glassass.over_mask = TRUE
- H.update_inv_glasses()
- H.gloves.siemens_coefficient = 0 // black "insulated" gloves, since combat gloves look kinda shit
- var/obj/item/card/id/I = H.wear_id
- if(istype(I))
- apply_to_card(I, H, list(ACCESS_MAINT_TUNNELS), "Solar Federation Infilitrator", "lifetimeid")
-
- H.DeleteComponent(/datum/component/footstep)
-
- var/datum/martial_art/cqc/CQC = new()
- CQC.teach(H)
-
-/datum/outfit/admin/tourist
- name = "Tourist"
- uniform = /obj/item/clothing/under/misc/redhawaiianshirt
- back = /obj/item/storage/backpack/satchel/withwallet
- belt = /obj/item/storage/belt/fannypack
- head = /obj/item/clothing/head/boaterhat
- l_ear = /obj/item/radio/headset
- glasses = /obj/item/clothing/glasses/sunglasses_fake
- shoes = /obj/item/clothing/shoes/sandal
- id = /obj/item/card/id/assistant
- box = /obj/item/storage/box/survival
- pda = /obj/item/pda/clear
-
- backpack_contents = list(
- /obj/item/camera = 1,
- /obj/item/camera_film = 1,
- /obj/item/stack/spacecash/c200 = 1,
- /obj/item/storage/fancy/cigarettes/cigpack_robustgold = 1,
- /obj/item/lighter/zippo = 1
- )
-
-/datum/outfit/admin/tourist/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
- . = ..()
- if(visualsOnly)
- return
-
- // Sets the ID and secHUD icon!
- var/obj/item/card/id/I = H.wear_id
- if(istype(I))
- apply_to_card(I, H, list(ACCESS_MAINT_TUNNELS), name, "tourist")
- // Checking if the person has an account already
- var/datum/money_account/account = H.mind.initial_account
- if(!account)
- // If they don't, we create a new one and get it's account number.
- SSjobs.CreateMoneyAccount(H, null, null)
- account = H.mind.initial_account
- I.associated_account_number = account.account_number
- I.associated_account_number = account.account_number
- H.sec_hud_set_ID()
-
- // PDA setup
- var/obj/item/pda/P = H.wear_pda
- if(istype(P))
- P.owner = H.real_name
- P.ownjob = "Tourist"
- P.name = "PDA-[H.real_name] ([P.ownjob])"
diff --git a/code/datums/outfits/outfit_debug.dm b/code/datums/outfits/outfit_debug.dm
deleted file mode 100644
index 559c9b8b9a21e..0000000000000
--- a/code/datums/outfits/outfit_debug.dm
+++ /dev/null
@@ -1,333 +0,0 @@
-/datum/outfit/admin/debug
- name = "Debug outfit"
-
- uniform = /obj/item/clothing/under/costume/patriotsuit
- back = /obj/item/mod/control/pre_equipped/debug
- backpack_contents = list(
- /obj/item/melee/energy/axe = 1,
- /obj/item/storage/part_replacer/bluespace/tier4 = 1,
- /obj/item/gun/magic/wand/resurrection/debug = 1,
- /obj/item/gun/magic/wand/death/debug = 1,
- /obj/item/debug/human_spawner = 1
- )
- belt = /obj/item/storage/belt/military/abductor/full
- l_ear = /obj/item/radio/headset/centcom/debug
- glasses = /obj/item/clothing/glasses/hud/debug
- mask = /obj/item/clothing/mask/gas/welding/advanced
- shoes = /obj/item/clothing/shoes/combat/swat
-
- box = /obj/item/storage/box/debug/debugtools
- suit_store = /obj/item/tank/internals/oxygen
- gloves = /obj/item/clothing/gloves/combat
- id = /obj/item/card/id/admin
- pda = /obj/item/pda/centcom
-
- internals_slot = ITEM_SLOT_SUIT_STORE
- toggle_helmet = TRUE
-
- cybernetic_implants = list(
- /obj/item/organ/internal/cyberimp/arm/surgery/debug,
- /obj/item/organ/internal/cyberimp/chest/nutriment/hardened,
- /obj/item/organ/internal/cyberimp/arm/janitorial/debug
- )
-
-
-/datum/outfit/admin/debug/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
- . = ..()
- if(visualsOnly)
- return
- var/obj/item/card/id/I = H.wear_id
- if(istype(I))
- apply_to_card(I, H, get_all_accesses(), "Debugger", "admin")
-
- H.dna.SetSEState(GLOB.breathlessblock, 1)
- singlemutcheck(H, GLOB.breathlessblock, MUTCHK_FORCED)
- H.dna.default_blocks.Add(GLOB.breathlessblock)
- H.check_mutations = 1
-
-/obj/item/radio/headset/centcom/debug
- name = "AVD-CNED bowman headset"
- ks2type = /obj/item/encryptionkey/syndicate/all_channels
-
-/// has to be a subtype and stuff
-/obj/item/encryptionkey/syndicate/all_channels
- name = "AVD-CNED Encryption Key"
- desc = "Lets you listen to everything. Use in hand to toggle voice changing. Alt-click to change your fake name."
- icon_state = "com_cypherkey"
- channels = list("Response Team" = 1, "Special Ops" = 1, "Science" = 1, "Command" = 1, "Medical" = 1, "Engineering" = 1, "Security" = 1, "Supply" = 1, "Service" = 1, "Procedure" = 1) // just in case
- syndie = TRUE
- change_voice = FALSE
-
-/obj/item/encryptionkey/syndicate/all_channels/Initialize(mapload)
- . = ..()
- for(var/channel in SSradio.radiochannels)
- channels[channel] = 1 // yeah, all channels, sure, probably fine
-
-/obj/item/encryptionkey/syndicate/all_channels/attack_self__legacy__attackchain(mob/user, pickupfireoverride)
- change_voice = !change_voice
- to_chat(user, "You switch [src] to [change_voice ? "" : "not "]change your voice on syndicate communications.")
-
-/obj/item/encryptionkey/syndicate/all_channels/AltClick(mob/user)
- var/new_name = tgui_input_text(user, "Enter new fake agent name...", "New name", max_length = MAX_NAME_LEN)
- if(!new_name)
- return
- fake_name = new_name
-
-/obj/item/clothing/mask/gas/welding/advanced
- name = "AVD-CNED welding mask"
- desc = "Retinal damage is no joke."
- tint = FLASH_PROTECTION_NONE
- flags_cover = MASKCOVERSEYES|MASKCOVERSMOUTH // vomit prevention when being surrounded by tons of dead bodies
-
-/obj/item/gun/magic/wand/resurrection/debug
- desc = "Is it possible for something to be even more powerful than regular magic? This wand is."
- max_charges = 500
- variable_charges = FALSE
- can_charge = TRUE
- recharge_rate = 1
-
-/obj/item/gun/magic/wand/death/debug
- desc = "In some obscure circles, this is known as the 'cloning tester's friend'."
- max_charges = 500
- variable_charges = FALSE
- can_charge = TRUE
- recharge_rate = 1
-
-/obj/item/clothing/glasses/hud/debug
- name = "AVD-CNED glasses"
- desc = "Diagnostic, Hydroponic, Medical, Security, and Skills HUD. Built-in advanced reagent scanner. Alt-click to toggle X-ray vision."
- icon_state = "nvgmeson"
- flags_cover = GLASSESCOVERSEYES
- flash_protect = FLASH_PROTECTION_WELDER
- scan_reagents_advanced = TRUE
-
- prescription_upgradable = FALSE
-
- hud_types = list(DATA_HUD_MEDICAL_ADVANCED, DATA_HUD_DIAGNOSTIC_ADVANCED, DATA_HUD_SECURITY_ADVANCED, DATA_HUD_HYDROPONIC)
- examine_extensions = list(EXAMINE_HUD_SECURITY_READ, EXAMINE_HUD_SECURITY_WRITE, EXAMINE_HUD_MEDICAL_READ, EXAMINE_HUD_MEDICAL_WRITE, EXAMINE_HUD_SKILLS)
-
- var/xray = FALSE
-
-/obj/item/clothing/glasses/hud/debug/equipped(mob/living/carbon/human/user, slot)
- ..()
- if(xray)
- add_xray(user)
-
-/obj/item/clothing/glasses/hud/debug/dropped(mob/living/carbon/human/user)
- ..()
- if(xray)
- remove_xray(user)
-
-/obj/item/clothing/glasses/hud/debug/AltClick(mob/user)
- if(!ishuman(user))
- return
- var/mob/living/carbon/human/human_user = user
- if(human_user.glasses != src)
- return
- if(xray)
- remove_xray(human_user)
- else
- add_xray(human_user)
- xray = !xray
- to_chat(user, "You [!xray ? "de" : ""]activate the x-ray setting on [src]")
- human_user.update_sight()
-
-/obj/item/clothing/glasses/hud/debug/proc/remove_xray(mob/user)
- see_in_dark = initial(see_in_dark)
- lighting_alpha = initial(lighting_alpha)
- REMOVE_TRAIT(user, TRAIT_XRAY_VISION, "debug_glasses[UID()]")
-
-/obj/item/clothing/glasses/hud/debug/proc/add_xray(mob/user)
- see_in_dark = 8
- lighting_alpha = LIGHTING_PLANE_ALPHA_INVISIBLE
- ADD_TRAIT(user, TRAIT_XRAY_VISION, "debug_glasses[UID()]")
-
-/obj/item/debug/human_spawner
- name = "human spawner"
- desc = "Spawn a human by aiming at a turf and clicking. Use in hand to change type."
- icon = 'icons/obj/guns/magic.dmi'
- icon_state = "nothingwand"
- lefthand_file = 'icons/mob/inhands/items_lefthand.dmi'
- righthand_file = 'icons/mob/inhands/items_righthand.dmi'
- w_class = WEIGHT_CLASS_SMALL
- var/datum/species/selected_species
- var/activate_mind = FALSE
-
-/obj/item/debug/human_spawner/examine(mob/user)
- . = ..()
- . += "Alt-Click to toggle mind-activation on spawning."
-
-/obj/item/debug/human_spawner/afterattack__legacy__attackchain(atom/target, mob/user, proximity)
- ..()
- if(!isturf(target))
- return
- var/mob/living/carbon/human/H = new /mob/living/carbon/human(target)
- if(selected_species)
- H.setup_dna(selected_species.type)
- if(activate_mind)
- H.mind_initialize()
-
-/obj/item/debug/human_spawner/attack_self__legacy__attackchain(mob/user)
- ..()
- var/choice = input("Select a species", "Human Spawner", null) in GLOB.all_species
- selected_species = GLOB.all_species[choice]
-
-/obj/item/debug/human_spawner/AltClick(mob/user)
- if(!Adjacent(user))
- return
- activate_mind = !activate_mind
- to_chat(user, "Any humans spawned will [activate_mind ? "" : "not "]spawn with an initialized mind.")
-
-/obj/item/rcd/combat/admin
- name = "AVD-CNED RCD"
- max_matter = INFINITY
- matter = INFINITY
- locked = FALSE
-
-/obj/item/stack/spacecash/debug
- amount = 50000
- max_amount = 50000
-
-/obj/item/bodyanalyzer/debug
- name = "AVD-CNED handheld body analyzer"
- cell_type = /obj/item/stock_parts/cell/infinite
- scan_time = 1 SECONDS
- scan_cd = 0 SECONDS
-
-/obj/item/scalpel/laser/manager/debug
- name = "AVD-CNED IMS"
- desc = "A wonder of modern medicine. This tool functions as any other sort of surgery tool, and finishes in only a fraction of the time. Hey, how'd you get your hands on this, anyway?"
- toolspeed = 0.01
-
-/obj/item/scalpel/laser/manager/debug/attack_self__legacy__attackchain(mob/user)
- . = ..()
- toolspeed = toolspeed == 0.5 ? 0.01 : 0.5
- to_chat(user, "[src] is now set to toolspeed [toolspeed]")
- playsound(src, 'sound/effects/pop.ogg', 50, 0) //Change the mode
-
-/obj/item/organ/internal/cyberimp/arm/surgery/debug
- name = "AVD-CNED surgical toolset implant"
- contents = newlist(
- /obj/item/scalpel/laser/manager/debug,
- /obj/item/hemostat/alien, // its needed specifically for some surgeries
- /obj/item/circular_saw/alien,
- /obj/item/healthanalyzer/advanced,
- /obj/item/gun/medbeam,
- /obj/item/handheld_defibrillator,
- /obj/item/bodyanalyzer/debug
- )
-
-/obj/item/organ/internal/cyberimp/arm/janitorial/debug
- name = "AVD-CNED janitorial toolset implant... is that a... tazer?"
- desc = "A set of advanced janitorial tools hidden behind a concealed panel on the user's arm with a tazer? What the fuck."
- parent_organ = "l_arm" // left arm by default cuz im lazy
- slot = "l_arm_device"
-
- contents = newlist(
- /obj/item/mop/advanced/debug,
- /obj/item/soap/syndie/debug,
- /obj/item/lightreplacer/bluespace/debug,
- /obj/item/reagent_containers/spray/cleaner/advanced/debug,
- /obj/item/gun/energy/gun/advtaser/mounted // yeah why not
- )
-
-/obj/item/mop/advanced/debug
- name = "AVD-CNED mop"
- desc = "I know you want to do it. Make shit slippery."
- mopcap = 100
- mopspeed = 1
- refill_rate = 50
-
-/obj/item/soap/syndie/debug
- name = "super soap"
- desc = "The fastest soap in the space-west."
- cleanspeed = 1
-
-/obj/item/lightreplacer/bluespace/debug
- name = "AVD-CNED light... thingy. You know what it is."
- max_uses = 20000
- uses = 20000
-
-/obj/item/reagent_containers/spray/cleaner/advanced/debug
- name = "AVD-CNED advanced space cleaner"
- desc = "AVD-CNED!-brand non-foaming space cleaner! How fancy."
- volume = 50000
- spray_maxrange = 10
- spray_currentrange = 10
- list_reagents = list("cleaner" = 50000)
- delay = 0.1 SECONDS // it costs 1000 reagents to fire this cleaner... for 12 seconds.
-
-
-//
-// Funny matryoshka doll boxes
-//
-
-/obj/item/storage/box/debug
- w_class = WEIGHT_CLASS_NORMAL
- max_w_class = WEIGHT_CLASS_NORMAL
- max_combined_w_class = 1000
- storage_slots = 99
- allow_same_size = TRUE
-
-/obj/item/storage/box/debug/debugtools
- name = "debug tools"
-
-/obj/item/storage/box/debug/debugtools/populate_contents()
- new /obj/item/card/emag(src)
- new /obj/item/rcd/combat/admin(src)
- new /obj/item/geiger_counter(src)
- new /obj/item/healthanalyzer/advanced(src)
- new /obj/item/rpd/bluespace(src)
- new /obj/item/stack/spacecash/debug(src)
- new /obj/item/storage/box/beakers/bluespace(src)
- new /obj/item/storage/box/debug/material(src)
- new /obj/item/storage/box/debug/misc_debug(src)
- new /obj/item/storage/box/centcomofficer(src)
- new /obj/item/radio/uplink/admin(src)
-
-/obj/item/storage/box/debug/material
- name = "box of materials"
-
-/obj/item/storage/box/debug/material/populate_contents()
- new /obj/item/stack/sheet/metal/fifty(src)
- new /obj/item/stack/sheet/plasteel/fifty(src)
- new /obj/item/stack/sheet/plastic/fifty(src)
- new /obj/item/stack/tile/brass/fifty(src)
- new /obj/item/stack/sheet/runed_metal/fifty(src)
- new /obj/item/stack/sheet/glass/fifty(src)
- new /obj/item/stack/sheet/rglass/fifty(src)
- new /obj/item/stack/sheet/plasmaglass/fifty(src)
- new /obj/item/stack/sheet/plasmarglass/fifty(src)
- new /obj/item/stack/sheet/titaniumglass/fifty(src)
- new /obj/item/stack/sheet/plastitaniumglass/fifty(src)
- new /obj/item/stack/sheet/mineral/sandstone/fifty(src)
- new /obj/item/stack/sheet/mineral/diamond/fifty(src)
- new /obj/item/stack/sheet/mineral/uranium/fifty(src)
- new /obj/item/stack/sheet/mineral/plasma/fifty(src)
- new /obj/item/stack/sheet/mineral/gold/fifty(src)
- new /obj/item/stack/sheet/mineral/silver/fifty(src)
- new /obj/item/stack/sheet/mineral/bananium/fifty(src)
- new /obj/item/stack/sheet/mineral/tranquillite/fifty(src)
- new /obj/item/stack/sheet/mineral/titanium/fifty(src)
- new /obj/item/stack/sheet/mineral/plastitanium/fifty(src)
- new /obj/item/stack/sheet/mineral/abductor/fifty(src)
- new /obj/item/stack/sheet/mineral/adamantine/fifty(src)
-
-/obj/item/storage/box/debug/misc_debug
- name = "misc admin items"
-
-// put cool admin-only shit here :)
-/obj/item/storage/box/debug/misc_debug/populate_contents()
- new /obj/item/badmin_book(src)
- new /obj/item/reagent_containers/drinks/bottle/vodka/badminka(src)
- new /obj/item/crowbar/power(src) // >admin only lol
- new /obj/item/clothing/gloves/fingerless/rapid/admin(src)
- new /obj/item/clothing/under/misc/acj(src)
- new /obj/item/clothing/suit/advanced_protective_suit(src)
- new /obj/item/multitool/ai_detect/admin(src) // for giggles and shits
- new /obj/item/adminfu_scroll(src)
- new /obj/item/teleporter/admin(src)
- new /obj/item/storage/belt/bluespace/admin(src) // god i love storage nesting
- new /obj/item/mining_scanner/admin(src)
- new /obj/item/gun/energy/meteorgun/pen(src)
-
diff --git a/code/datums/outfits/plasmamen_outfits.dm b/code/datums/outfits/plasmamen_outfits.dm
deleted file mode 100644
index c18f4e4a0579b..0000000000000
--- a/code/datums/outfits/plasmamen_outfits.dm
+++ /dev/null
@@ -1,199 +0,0 @@
-/datum/outfit/plasmaman
- name = "Plasmaman"
-
- head = /obj/item/clothing/head/helmet/space/plasmaman
- uniform = /obj/item/clothing/under/plasmaman
-
-/datum/outfit/plasmaman/bar
- name = "Plasmaman Bartender"
-
- head = /obj/item/clothing/head/helmet/space/plasmaman/white
- uniform = /obj/item/clothing/under/plasmaman/enviroslacks
-
-/datum/outfit/plasmaman/chef
- name = "Plasmaman Chef"
-
- head = /obj/item/clothing/head/helmet/space/plasmaman/chef
- uniform = /obj/item/clothing/under/plasmaman/chef
-
-/datum/outfit/plasmaman/botany
- name = "Plasmaman Botany"
-
- head = /obj/item/clothing/head/helmet/space/plasmaman/botany
- uniform = /obj/item/clothing/under/plasmaman/botany
-
-/datum/outfit/plasmaman/librarian
- name = "Plasmaman Librarian"
-
- head = /obj/item/clothing/head/helmet/space/plasmaman/librarian
- uniform = /obj/item/clothing/under/plasmaman/librarian
-
-/datum/outfit/plasmaman/chaplain
- name = "Plasmaman Chaplain"
-
- head = /obj/item/clothing/head/helmet/space/plasmaman/chaplain
- uniform = /obj/item/clothing/under/plasmaman/chaplain
-
-/datum/outfit/plasmaman/janitor
- name = "Plasmaman Janitor"
-
- head = /obj/item/clothing/head/helmet/space/plasmaman/janitor
- uniform = /obj/item/clothing/under/plasmaman/janitor
-
-/datum/outfit/plasmaman/security
- name = "Plasmaman Security"
-
- head = /obj/item/clothing/head/helmet/space/plasmaman/security
- uniform = /obj/item/clothing/under/plasmaman/security
-
-/datum/outfit/plasmaman/detective
- name = "Plasmaman Detective"
-
- head = /obj/item/clothing/head/helmet/space/plasmaman/white
- uniform = /obj/item/clothing/under/plasmaman/enviroslacks
-
-/datum/outfit/plasmaman/warden
- name = "Plasmaman Warden"
-
- head = /obj/item/clothing/head/helmet/space/plasmaman/security/warden
- uniform = /obj/item/clothing/under/plasmaman/security/warden
-
-/datum/outfit/plasmaman/hos
- name = "Plasmaman Head of Security"
-
- head = /obj/item/clothing/head/helmet/space/plasmaman/security/hos
- uniform = /obj/item/clothing/under/plasmaman/security/hos
-
-/datum/outfit/plasmaman/cargo
- name = "Plasmaman Cargo"
-
- head = /obj/item/clothing/head/helmet/space/plasmaman/cargo
- uniform = /obj/item/clothing/under/plasmaman/cargo
-
-/datum/outfit/plasmaman/expedition
- name = "Plasmaman Expedition"
-
- head = /obj/item/clothing/head/helmet/space/plasmaman/expedition
- uniform = /obj/item/clothing/under/plasmaman/expedition
-
-/datum/outfit/plasmaman/mining
- name = "Plasmaman Mining"
-
- head = /obj/item/clothing/head/helmet/space/plasmaman/mining
- uniform = /obj/item/clothing/under/plasmaman/mining
-
-/datum/outfit/plasmaman/medical
- name = "Plasmaman Medical"
-
- head = /obj/item/clothing/head/helmet/space/plasmaman/medical
- uniform = /obj/item/clothing/under/plasmaman/medical
-
-/datum/outfit/plasmaman/cmo
- name = "Plasmaman Chief Medical Officer"
-
- head = /obj/item/clothing/head/helmet/space/plasmaman/cmo
- uniform = /obj/item/clothing/under/plasmaman/cmo
-
-/datum/outfit/plasmaman/viro
- name = "Plasmaman Virology"
-
- head = /obj/item/clothing/head/helmet/space/plasmaman/viro
- uniform = /obj/item/clothing/under/plasmaman/viro
-
-/datum/outfit/plasmaman/chemist
- name = "Plasmaman Chemist"
-
- head = /obj/item/clothing/head/helmet/space/plasmaman/chemist
- uniform = /obj/item/clothing/under/plasmaman/chemist
-
-/datum/outfit/plasmaman/genetics
- name = "Plasmaman Genetics"
-
- head = /obj/item/clothing/head/helmet/space/plasmaman/genetics
- uniform = /obj/item/clothing/under/plasmaman/genetics
-
-/datum/outfit/plasmaman/science
- name = "Plasmaman Science"
-
- head = /obj/item/clothing/head/helmet/space/plasmaman/science
- uniform = /obj/item/clothing/under/plasmaman/science
-
-/datum/outfit/plasmaman/rd
- name = "Plasmaman Research Director"
-
- head = /obj/item/clothing/head/helmet/space/plasmaman/rd
- uniform = /obj/item/clothing/under/plasmaman/rd
-
-/datum/outfit/plasmaman/robotics
- name = "Plasmaman Robotics"
-
- head = /obj/item/clothing/head/helmet/space/plasmaman/robotics
- uniform = /obj/item/clothing/under/plasmaman/robotics
-
-/datum/outfit/plasmaman/engineering
- name = "Plasmaman Engineering"
-
- head = /obj/item/clothing/head/helmet/space/plasmaman/engineering
- uniform = /obj/item/clothing/under/plasmaman/engineering
-
-/datum/outfit/plasmaman/ce
- name = "Plasmaman Chief Engineer"
-
- head = /obj/item/clothing/head/helmet/space/plasmaman/engineering/ce
- uniform = /obj/item/clothing/under/plasmaman/engineering/ce
-
-/datum/outfit/plasmaman/atmospherics
- name = "Plasmaman Atmospherics"
-
- head = /obj/item/clothing/head/helmet/space/plasmaman/atmospherics
- uniform = /obj/item/clothing/under/plasmaman/atmospherics
-
-/datum/outfit/plasmaman/mime
- name = "Plasmamime"
-
- head = /obj/item/clothing/head/helmet/space/plasmaman/mime
- uniform = /obj/item/clothing/under/plasmaman/mime
- mask = /obj/item/clothing/mask/gas/mime
-
-/datum/outfit/plasmaman/clown
- name = "Plasmaclown"
-
- head = /obj/item/clothing/head/helmet/space/plasmaman/clown
- uniform = /obj/item/clothing/under/plasmaman/clown
- mask = /obj/item/clothing/mask/gas/clown_hat
-
-/datum/outfit/plasmaman/hop
- name = "Plasmaman Head of Personnel"
-
- head = /obj/item/clothing/head/helmet/space/plasmaman/hop
- uniform = /obj/item/clothing/under/plasmaman/hop
-
-/datum/outfit/plasmaman/captain
- name = "Plasmaman Captain"
-
- head = /obj/item/clothing/head/helmet/space/plasmaman/captain
- uniform = /obj/item/clothing/under/plasmaman/captain
-
-/datum/outfit/plasmaman/blueshield
- name = "Plasmaman Blueshield"
-
- head = /obj/item/clothing/head/helmet/space/plasmaman/blueshield
- uniform = /obj/item/clothing/under/plasmaman/blueshield
-
-/datum/outfit/plasmaman/wizard
- name = "Plasmaman Wizard"
-
- head = /obj/item/clothing/head/helmet/space/plasmaman/wizard
- uniform = /obj/item/clothing/under/plasmaman/wizard
-
-/datum/outfit/plasmaman/assistant
- name = "Assistant Plasmaman"
-
- head = /obj/item/clothing/head/helmet/space/plasmaman/assistant
- uniform = /obj/item/clothing/under/plasmaman/assistant
-
-/datum/outfit/plasmaman/trainer
- name = "Plasmaman Career Trainer"
-
- head = /obj/item/clothing/head/helmet/space/plasmaman/trainer
- uniform = /obj/item/clothing/under/plasmaman/trainer
diff --git a/code/datums/periodic_news.dm b/code/datums/periodic_news.dm
deleted file mode 100644
index ed8102f06cfa4..0000000000000
--- a/code/datums/periodic_news.dm
+++ /dev/null
@@ -1,140 +0,0 @@
-// This system defines news that will be displayed in the course of a round.
-// Uses BYOND's type system to put everything into a nice format
-
-/datum/news_announcement
-
- var/round_time // time of the round at which this should be announced, in seconds
- var/message // body of the message
- var/author = "Nanotrasen Editor"
- var/channel_name = "Nyx Daily"
- var/can_be_redacted = FALSE
- var/message_type = "Story"
-
-/datum/news_announcement/revolution_inciting_event/paycuts_suspicion
- round_time = 60*10
- message = {"Reports have leaked that Nanotrasen Inc. is planning to put paycuts into
- effect on many of its Research Stations in Tau Ceti. Apparently these research
- stations haven't been able to yield the expected revenue, and thus adjustments
- have to be made."}
- author = "Unauthorized"
-
-/datum/news_announcement/revolution_inciting_event/paycuts_confirmation
- round_time = 60*40
- message = {"Earlier rumours about paycuts on Research Stations in the Tau Ceti system have
- been confirmed. Shockingly, however, the cuts will only affect lower tier
- personnel. Heads of Staff will, according to our sources, not be affected."}
- author = "Unauthorized"
-
-/datum/news_announcement/revolution_inciting_event/human_experiments
- round_time = 60*90
- message = {"Unbelievable reports about human experimentation have reached our ears. According
- to a refugee from one of the Tau Ceti Research Stations, their station, in order
- to increase revenue, has refactored several of their facilities to perform experiments
- on live humans, including virology research, genetic manipulation, and \"feeding them
- to the slimes to see what happens\". Allegedly, these test subjects were neither
- humanified monkeys nor volunteers, but rather unqualified staff that were forced into
- the experiments, and reported to have died in a \"work accident\" by Nanotrasen Inc."}
- author = "Unauthorized"
-
-/datum/news_announcement/bluespace_research
- round_time = 60*20
- message = {"The new field of research trying to explain several interesting spacetime oddities,
- also known as \"Bluespace Research\", has reached new heights. Of the several
- hundred space stations now orbiting in Tau Ceti, fifteen are now specially equipped
- to experiment with and research Bluespace effects. Rumours have it some of these
- stations even sport functional \"travel gates\" that can instantly move a whole research
- team to an alternate reality."}
-
-/datum/news_announcement/random_junk/cheesy_honkers
- author = "Assistant Editor Carl Ritz"
- channel_name = "The Gibson Gazette"
- message = {"Do cheesy honkers increase risk of having a miscarriage? Several health administrations
- say so!"}
- round_time = 60 * 15
-
-/datum/news_announcement/random_junk/net_block
- author = "Assistant Editor Carl Ritz"
- channel_name = "The Gibson Gazette"
- message = {"Several corporations banding together to block access to 'wetskrell.nt', site administrators
- claiming violation of net laws."}
- round_time = 60 * 50
-
-/datum/news_announcement/random_junk/found_ssd
- channel_name = "Nyx Daily"
- author = "Doctor Eric Hanfield"
-
- message = {"Several people have been found unconscious at their terminals. It is thought that it was due
- to a lack of sleep or of simply migraines from staring at the screen too long. Camera footage
- reveals that many of them were playing games instead of working and their pay has been docked
- accordingly."}
- round_time = 60 * 90
-
-/datum/news_announcement/lotus_tree
- channel_name = "Nyx Daily"
- author = "Reporter Leland H. Howards"
-
- message = {"The newly-christened civilian transport Lotus Tree suffered two very large explosions near the
- bridge today, and there are unconfirmed reports that the death toll has passed 50. The cause of
- the explosions remain unknown, but there is speculation that it might have something to do with
- the recent change of regulation in the Moore-Lee Corporation, a major funder of the ship, when M-L
- announced that they were officially acknowledging inter-species marriage and providing couples
- with marriage tax-benefits."}
- round_time = 60 * 30
-
-/datum/news_announcement/food_riots/breaking_news
- channel_name = "Nyx Daily"
- author = "Reporter Ro'kii Ar-Raqis"
-
- message = {"Breaking news: Food riots have broken out throughout the Refuge asteroid colony in the Tenebrae
- Lupus system. This comes only hours after Nanotrasen officials announced they will no longer trade with the
- colony, citing the increased presence of \"hostile factions\" on the colony has made trade too dangerous to
- continue. Nanotrasen officials have not given any details about said factions. More on that at the top of
- the hour."}
- round_time = 60 * 10
-
-/datum/news_announcement/food_riots/more
- channel_name = "Nyx Daily"
- author = "Reporter Ro'kii Ar-Raqis"
-
- message = {"More on the Refuge food riots: The Refuge Council has condemned Nanotrasen's withdrawal from
- the colony, claiming \"there has been no increase in anti-Nanotrasen activity\", and \"\[the only] reason
- Nanotrasen withdrew was because the \[Tenebrae Lupus] system's Plasma deposits have been completely mined out.
- We have little to trade with them now\". Nanotrasen officials have denied these allegations, calling them
- \"further proof\" of the colony's anti-Nanotrasen stance. Meanwhile, Refuge Security has been unable to quell
- the riots. More on this at 6."}
- round_time = 60 * 60
-GLOBAL_LIST_INIT(newscaster_standard_feeds, list(/datum/news_announcement/bluespace_research, /datum/news_announcement/lotus_tree, /datum/news_announcement/random_junk, /datum/news_announcement/food_riots))
-
-/proc/process_newscaster()
- check_for_newscaster_updates(SSticker.mode.newscaster_announcements)
-
-GLOBAL_LIST_EMPTY(announced_news_types)
-
-/proc/check_for_newscaster_updates(type)
- for(var/subtype in subtypesof(type))
- var/datum/news_announcement/news = new subtype()
- if(news.round_time * 10 <= world.time && !(subtype in GLOB.announced_news_types))
- GLOB.announced_news_types += subtype
- announce_newscaster_news(news)
-
-/proc/announce_newscaster_news(datum/news_announcement/news)
-
- var/datum/feed_channel/sendto = GLOB.news_network.get_channel_by_name(news.channel_name)
- if(!sendto)
- sendto = new /datum/feed_channel
- sendto.channel_name = news.channel_name
- sendto.author = news.author
- sendto.frozen = TRUE
- sendto.admin_locked = TRUE
- GLOB.news_network.channels += sendto
-
- var/datum/feed_message/newMsg = new /datum/feed_message
- newMsg.author = news.author ? news.author : sendto.author
- newMsg.admin_locked = !news.can_be_redacted
- newMsg.body = news.message
-
- sendto.add_message(newMsg)
-
- for(var/nc in GLOB.allNewscasters)
- var/obj/machinery/newscaster/NC = nc
- NC.alert_news(news.channel_name)
diff --git a/code/datums/proximity/advanced_proximity_monitor.dm b/code/datums/proximity/advanced_proximity_monitor.dm
deleted file mode 100644
index bd47bfd6cb909..0000000000000
--- a/code/datums/proximity/advanced_proximity_monitor.dm
+++ /dev/null
@@ -1,168 +0,0 @@
-#define FIELD_TURFS_KEY "field_turfs"
-#define EDGE_TURFS_KEY "edge_turfs"
-
-/**
- * Movable and easily code-modified fields! Allows for custom AOE effects that affect movement
- * and anything inside of them, and can do custom turf effects!
- * Supports automatic recalculation/reset on movement.
- *
- * "What do I gain from using advanced over standard prox monitors?"
- * - You can set different effects on edge vs field entrance
- * - You can set effects when the proximity monitor starts and stops tracking a turf
- */
-/datum/proximity_monitor/advanced
- /// If TRUE, edge turfs will be included as "in the field" for effects
- /// Can be used in certain situations where you may have effects that trigger only at the edge,
- /// while also wanting the field effect to trigger at edge turfs as well
- var/edge_is_a_field = FALSE
- /// All turfs on the inside of the proximity monitor - range - 1 turfs
- var/list/turf/field_turfs = list()
- /// All turfs on the very last tile of the proximity monitor's radius
- var/list/turf/edge_turfs = list()
-
-/datum/proximity_monitor/advanced/Destroy()
- cleanup_field()
- return ..()
-
-/datum/proximity_monitor/advanced/proc/cleanup_field()
- for(var/turf/turf as anything in edge_turfs)
- cleanup_edge_turf(turf)
- edge_turfs = list()
- for(var/turf/turf as anything in field_turfs)
- cleanup_field_turf(turf)
- field_turfs = list()
-
-//Call every time the field moves (done automatically if you use update_center) or a setup specification is changed.
-/datum/proximity_monitor/advanced/proc/recalculate_field(full_recalc = FALSE)
- var/list/new_turfs = update_new_turfs()
-
- var/list/old_field_turfs = field_turfs
- var/list/old_edge_turfs = edge_turfs
- field_turfs = new_turfs[FIELD_TURFS_KEY]
- edge_turfs = new_turfs[EDGE_TURFS_KEY]
- if(full_recalc)
- field_turfs = list()
- edge_turfs = list()
-
- for(var/turf/old_turf as anything in old_field_turfs - field_turfs)
- if(QDELETED(src))
- return
- cleanup_field_turf(old_turf)
- for(var/turf/old_turf as anything in old_edge_turfs - edge_turfs)
- if(QDELETED(src))
- return
- cleanup_edge_turf(old_turf)
-
- if(full_recalc)
- old_field_turfs = list()
- old_edge_turfs = list()
- field_turfs = new_turfs[FIELD_TURFS_KEY]
- edge_turfs = new_turfs[EDGE_TURFS_KEY]
-
- for(var/turf/new_turf as anything in field_turfs - old_field_turfs)
- if(QDELETED(src))
- return
- setup_field_turf(new_turf)
-
- for(var/turf/new_turf as anything in edge_turfs - old_edge_turfs)
- if(QDELETED(src))
- return
- setup_edge_turf(new_turf)
-
-/datum/proximity_monitor/advanced/on_initialized(turf/location, atom/created, init_flags)
- . = ..()
- on_entered(location, created, null)
-
-/datum/proximity_monitor/advanced/on_entered(turf/source, atom/movable/entered, turf/old_loc)
- . = ..()
- if(get_dist(source, host) == current_range)
- field_edge_crossed(entered, old_loc, source)
- else
- field_turf_crossed(entered, old_loc, source)
-
-/datum/proximity_monitor/advanced/on_moved(atom/movable/movable, atom/old_loc)
- . = ..()
- if(ignore_if_not_on_turf)
- //Early return if it's not the host that has moved.
- if(movable != host)
- return
- //Cleanup the field if the host was on a turf but isn't anymore.
- if(!isturf(host.loc))
- if(isturf(old_loc))
- cleanup_field()
- return
- recalculate_field(full_recalc = FALSE)
-
-/datum/proximity_monitor/advanced/on_uncrossed(turf/source, atom/movable/gone, direction)
- if(get_dist(source, host) == current_range)
- field_edge_uncrossed(gone, source, get_turf(gone))
- else
- field_turf_uncrossed(gone, source, get_turf(gone))
-
-/// Called when a turf in the field of the monitor is linked
-/datum/proximity_monitor/advanced/proc/setup_field_turf(turf/target)
- return
-
-/// Called when a turf in the field of the monitor is unlinked
-/// Do NOT call this manually, requires management of the field_turfs list
-/datum/proximity_monitor/advanced/proc/cleanup_field_turf(turf/target)
- PRIVATE_PROC(TRUE)
- return
-
-/// Called when a turf in the edge of the monitor is linked
-/datum/proximity_monitor/advanced/proc/setup_edge_turf(turf/target)
- if(edge_is_a_field) // If the edge is considered a field, set it up like one
- setup_field_turf(target)
-
-/// Called when a turf in the edge of the monitor is unlinked
-/// Do NOT call this manually, requires management of the edge_turfs list
-/datum/proximity_monitor/advanced/proc/cleanup_edge_turf(turf/target)
- if(edge_is_a_field) // If the edge is considered a field, clean it up like one
- cleanup_field_turf(target)
-
-/datum/proximity_monitor/advanced/proc/update_new_turfs()
- if(ignore_if_not_on_turf && !isturf(host.loc))
- return list(FIELD_TURFS_KEY = list(), EDGE_TURFS_KEY = list())
- var/list/local_field_turfs = list()
- var/list/local_edge_turfs = list()
- var/turf/center = get_turf(host)
- if(current_range > 0)
- local_field_turfs += RANGE_TURFS(current_range - 1, center)
- if(current_range > 1)
- local_edge_turfs = RANGE_TURFS(current_range, center) - local_field_turfs
- return list(FIELD_TURFS_KEY = local_field_turfs, EDGE_TURFS_KEY = local_edge_turfs)
-
-//Gets edge direction/corner, only works with square radius/WDH fields!
-/datum/proximity_monitor/advanced/proc/get_edgeturf_direction(turf/T, turf/center_override = null)
- var/turf/checking_from = get_turf(host)
- if(istype(center_override))
- checking_from = center_override
- if(!(T in edge_turfs))
- return
- if(((T.x == (checking_from.x + current_range)) || (T.x == (checking_from.x - current_range))) && ((T.y == (checking_from.y + current_range)) || (T.y == (checking_from.y - current_range))))
- return get_dir(checking_from, T)
- if(T.x == (checking_from.x + current_range))
- return EAST
- if(T.x == (checking_from.x - current_range))
- return WEST
- if(T.y == (checking_from.y - current_range))
- return SOUTH
- if(T.y == (checking_from.y + current_range))
- return NORTH
-
-/datum/proximity_monitor/advanced/proc/field_turf_crossed(atom/movable/movable, turf/old_location, turf/new_location)
- return
-
-/datum/proximity_monitor/advanced/proc/field_turf_uncrossed(atom/movable/movable, turf/old_location, turf/new_location)
- return
-
-/datum/proximity_monitor/advanced/proc/field_edge_crossed(atom/movable/movable, turf/old_location, turf/new_location)
- if(edge_is_a_field) // If the edge is considered a field, pass crossed to that
- field_turf_crossed(movable, old_location, new_location)
-
-/datum/proximity_monitor/advanced/proc/field_edge_uncrossed(atom/movable/movable, turf/old_location, turf/new_location)
- if(edge_is_a_field) // If the edge is considered a field, pass uncrossed to that
- field_turf_uncrossed(movable, old_location, new_location)
-
-#undef FIELD_TURFS_KEY
-#undef EDGE_TURFS_KEY
diff --git a/code/datums/proximity/proximity_monitor.dm b/code/datums/proximity/proximity_monitor.dm
deleted file mode 100644
index 09a7d32f916d5..0000000000000
--- a/code/datums/proximity/proximity_monitor.dm
+++ /dev/null
@@ -1,89 +0,0 @@
-/datum/proximity_monitor
- ///The atom we are tracking
- var/atom/host
- ///The atom that will receive HasProximity calls.
- var/atom/hasprox_receiver
- ///The range of the proximity monitor. Things moving wihin it will trigger HasProximity calls.
- var/current_range
- ///If we don't check turfs in range if the host's loc isn't a turf
- var/ignore_if_not_on_turf
- ///The signals of the connect range component, needed to monitor the turfs in range.
- var/static/list/loc_connections = list(
- COMSIG_ATOM_ENTERED = PROC_REF(on_entered),
- COMSIG_ATOM_EXITED = PROC_REF(on_uncrossed),
- COMSIG_ATOM_AFTER_SUCCESSFUL_INITIALIZED_ON = PROC_REF(on_initialized),
- )
-
-/datum/proximity_monitor/New(atom/_host, range = 1, _ignore_if_not_on_turf = TRUE)
- ignore_if_not_on_turf = _ignore_if_not_on_turf
- current_range = range
- set_host(_host)
-
-/datum/proximity_monitor/proc/set_host(atom/new_host, atom/new_receiver)
- if(new_host == host)
- return
- if(host) //No need to delete the connect range and containers comps. They'll be updated with the new tracked host.
- UnregisterSignal(host, list(COMSIG_MOVABLE_MOVED, COMSIG_PARENT_QDELETING))
- if(hasprox_receiver)
- UnregisterSignal(hasprox_receiver, COMSIG_PARENT_QDELETING)
- if(new_receiver)
- hasprox_receiver = new_receiver
- if(new_receiver != new_host)
- RegisterSignal(new_receiver, COMSIG_PARENT_QDELETING, PROC_REF(on_host_or_receiver_del))
- else if(hasprox_receiver == host) //Default case
- hasprox_receiver = new_host
- host = new_host
- RegisterSignal(new_host, COMSIG_PARENT_QDELETING, PROC_REF(on_host_or_receiver_del))
- var/static/list/containers_connections = list(COMSIG_MOVABLE_MOVED = PROC_REF(on_moved), COMSIG_MOVABLE_Z_CHANGED = PROC_REF(on_z_change))
- AddComponent(/datum/component/connect_containers, host, containers_connections)
- RegisterSignal(host, COMSIG_MOVABLE_MOVED, PROC_REF(on_moved))
- RegisterSignal(host, COMSIG_MOVABLE_Z_CHANGED, PROC_REF(on_z_change))
- set_range(current_range, TRUE)
-
-/datum/proximity_monitor/proc/on_host_or_receiver_del(datum/source)
- SIGNAL_HANDLER // COMSIG_PARENT_QDELETING
- qdel(src)
-
-/datum/proximity_monitor/Destroy()
- host = null
- hasprox_receiver = null
- return ..()
-
-/datum/proximity_monitor/proc/set_range(range, force_rebuild = FALSE)
- if(!force_rebuild && range == current_range)
- return FALSE
- . = TRUE
- current_range = range
-
- //If the connect_range component exists already, this will just update its range. No errors or duplicates.
- AddComponent(/datum/component/connect_range, host, loc_connections, range, !ignore_if_not_on_turf)
-
-/datum/proximity_monitor/proc/on_moved(atom/movable/source, atom/old_loc)
- SIGNAL_HANDLER // COMSIG_MOVABLE_MOVED
- if(source == host)
- hasprox_receiver?.HasProximity(host)
-
-/datum/proximity_monitor/proc/on_z_change()
- SIGNAL_HANDLER // COMSIG_MOVABLE_Z_CHANGED
- return
-
-/datum/proximity_monitor/proc/set_ignore_if_not_on_turf(does_ignore = TRUE)
- if(ignore_if_not_on_turf == does_ignore)
- return
- ignore_if_not_on_turf = does_ignore
- //Update the ignore_if_not_on_turf
- AddComponent(/datum/component/connect_range, host, loc_connections, current_range, ignore_if_not_on_turf)
-
-/datum/proximity_monitor/proc/on_uncrossed()
- SIGNAL_HANDLER // COMSIG_ATOM_EXITED
- return //Used by the advanced subtype for effect fields.
-
-/datum/proximity_monitor/proc/on_entered(atom/source, atom/movable/arrived, turf/old_loc)
- SIGNAL_HANDLER // COMSIG_ATOM_ENTERED
- if(source != host)
- hasprox_receiver?.HasProximity(arrived)
-
-/datum/proximity_monitor/proc/on_initialized(turf/location, atom/created, init_flags)
- SIGNAL_HANDLER // COMSIG_ATOM_AFTER_SUCCESSFUL_INITIALIZED_ON
- if(location != host)
- hasprox_receiver?.HasProximity(created)
diff --git a/code/datums/proximity/singulo_proximity_monitor.dm b/code/datums/proximity/singulo_proximity_monitor.dm
deleted file mode 100644
index e7b870ba346a0..0000000000000
--- a/code/datums/proximity/singulo_proximity_monitor.dm
+++ /dev/null
@@ -1,28 +0,0 @@
-/datum/proximity_monitor/advanced/singulo
-
-/datum/proximity_monitor/advanced/singulo/on_entered(turf/source, atom/movable/entered, turf/old_loc)
- . = ..()
- if(!isprojectile(entered))
- return
-
- var/angle_to_singulo = ATAN2(host.y - source.y, host.x - source.x)
- var/distance_to_singulo = get_dist(source, host)
-
- var/obj/item/projectile/P = entered
- var/distance = distance_to_singulo
- var/projectile_angle = P.Angle
- var/angle_to_projectile = angle_to_singulo
- if(angle_to_projectile == 180)
- angle_to_projectile = -180
- angle_to_projectile -= projectile_angle
- if(angle_to_projectile > 180)
- angle_to_projectile -= 360
- else if(angle_to_projectile < -180)
- angle_to_projectile += 360
-
- if(distance == 0)
- qdel(P)
- return
- projectile_angle += angle_to_projectile / (distance ** 2)
- P.damage += 10 / distance
- P.set_angle(projectile_angle)
diff --git a/code/datums/radiation_wave.dm b/code/datums/radiation_wave.dm
deleted file mode 100644
index 241766da2271f..0000000000000
--- a/code/datums/radiation_wave.dm
+++ /dev/null
@@ -1,141 +0,0 @@
-/datum/radiation_wave
- /// The thing that spawned this radiation wave
- var/source
- /// The center of the wave
- var/turf/master_turf
- /// How far we've moved
- var/steps = 0
- /// How strong it was originaly
- var/intensity
- /// How much contaminated material it still has
- var/remaining_contam
- /// Higher than 1 makes it drop off faster, 0.5 makes it drop off half etc
- var/range_modifier
- /// The distance from the source point the wave can cover without losing any strength.
- var/source_radius
- /// The direction of movement
- var/move_dir
- /// The directions to the side of the wave, stored for easy looping
- var/list/__dirs
- /// Whether or not this radiation wave can create contaminated objects
- var/can_contaminate
-
-/datum/radiation_wave/New(atom/_source, dir, _intensity = 0, _range_modifier = RAD_DISTANCE_COEFFICIENT, _can_contaminate = TRUE, _source_radius = 0)
-
- source = "[_source] \[[_source.UID()]\]"
-
- master_turf = get_turf(_source)
-
- move_dir = dir
- __dirs = list()
- __dirs += turn(dir, 90)
- __dirs += turn(dir, -90)
-
- intensity = _intensity
- remaining_contam = intensity
- range_modifier = _range_modifier
- can_contaminate = _can_contaminate
- source_radius = _source_radius
- START_PROCESSING(SSradiation, src)
-
-/datum/radiation_wave/Destroy()
- . = QDEL_HINT_IWILLGC
- STOP_PROCESSING(SSradiation, src)
- ..()
-
-/datum/radiation_wave/process()
- master_turf = get_step(master_turf, move_dir)
- if(!master_turf)
- qdel(src)
- return
- steps++
- var/list/atoms = get_rad_atoms()
-
- var/strength
- if(steps > source_radius + 1)
- strength = INVERSE_SQUARE(intensity, max(range_modifier * (steps - source_radius), 1), 1)
- else
- strength = intensity
-
- if(strength < RAD_BACKGROUND_RADIATION)
- qdel(src)
- return
- radiate(atoms, strength)
- check_obstructions(atoms) // reduce our overall strength if there are radiation insulators
-
-/datum/radiation_wave/proc/get_rad_atoms()
- var/list/atoms = list()
- var/distance = steps
- var/cmove_dir = move_dir
- var/cmaster_turf = master_turf
-
- if(cmove_dir == NORTH || cmove_dir == SOUTH)
- distance-- //otherwise corners overlap
-
- atoms += get_rad_contents(cmaster_turf)
-
- var/turf/place
- for(var/dir in __dirs) //There should be just 2 dirs in here, left and right of the direction of movement
- place = cmaster_turf
- for(var/i in 1 to distance)
- place = get_step(place, dir)
- if(!place)
- break
- atoms += get_rad_contents(place)
-
- return atoms
-
-/datum/radiation_wave/proc/check_obstructions(list/atoms)
- var/width = steps
- var/cmove_dir = move_dir
- if(cmove_dir == NORTH || cmove_dir == SOUTH)
- width--
- width = 1 + (2 * width)
-
- for(var/k in 1 to length(atoms))
- var/atom/thing = atoms[k]
- if(!thing)
- continue
- if(SEND_SIGNAL(thing, COMSIG_ATOM_RAD_WAVE_PASSING, src, width) & COMPONENT_RAD_WAVE_HANDLED)
- continue
- if(thing.rad_insulation != RAD_NO_INSULATION)
- intensity *= (1 - ((1 - thing.rad_insulation) / width))
-
-/datum/radiation_wave/proc/radiate(list/atoms, strength)
- var/can_contam = strength >= RAD_MINIMUM_CONTAMINATION
- var/contamination_strength = (strength - RAD_MINIMUM_CONTAMINATION) * RAD_CONTAMINATION_STR_COEFFICIENT
- contamination_strength = max(contamination_strength, RAD_BACKGROUND_RADIATION)
- // It'll never reach 100% chance but the further out it gets the more likely it'll contaminate
- var/contamination_chance = 100 - (90 / (1 + steps * 0.1))
- for(var/k in atoms)
- var/atom/thing = k
- if(QDELETED(thing))
- continue
- thing.rad_act(strength)
-
- // This list should only be for types which don't get contaminated but you want to look in their contents
- // If you don't want to look in their contents and you don't want to rad_act them:
- // modify the ignored_things list in __HELPERS/radiation.dm instead
- var/static/list/blacklisted = typecacheof(list(
- /turf,
- /obj/structure/cable,
- /obj/machinery/atmospherics,
- /obj/item/ammo_casing,
- /obj/item/bio_chip,
- /obj/singularity,
- ))
- if(!can_contaminate || !can_contam || blacklisted[thing.type])
- continue
- if(thing.flags_2 & RAD_NO_CONTAMINATE_2 || SEND_SIGNAL(thing, COMSIG_ATOM_RAD_CONTAMINATING, strength) & COMPONENT_BLOCK_CONTAMINATION)
- continue
-
- if(contamination_strength > remaining_contam)
- contamination_strength = remaining_contam
- if(!prob(contamination_chance))
- continue
- if(SEND_SIGNAL(thing, COMSIG_ATOM_RAD_CONTAMINATING, strength) & COMPONENT_BLOCK_CONTAMINATION)
- continue
- remaining_contam -= contamination_strength
- if(remaining_contam < RAD_BACKGROUND_RADIATION)
- can_contaminate = FALSE
- thing.AddComponent(/datum/component/radioactive, contamination_strength, source)
diff --git a/code/datums/ruins/bridges/bridges.dm b/code/datums/ruins/bridges/bridges.dm
deleted file mode 100644
index 50be4d96c8345..0000000000000
--- a/code/datums/ruins/bridges/bridges.dm
+++ /dev/null
@@ -1,288 +0,0 @@
-#define LONG_BRIDGE_THEME_CULT "cult"
-#define LONG_BRIDGE_THEME_HIERO "hiero"
-#define LONG_BRIDGE_THEME_CLOCKWORK "clockwork"
-#define LONG_BRIDGE_THEME_STONE "stone"
-#define LONG_BRIDGE_THEME_WOOD "wood"
-#define LONG_BRIDGE_THEME_CATWALK "catwalk"
-
-/**
- * A spawner for dynamic bridges, largely with hardcoded expectations to be
- * operating on Lavaland.
- *
- * It does this by starting at its spawn point, placed by a
- * [/datum/river_spawner] during generation, and walking in each direction
- * "forwards" and "backwards" until it reaches the maximum possible length of
- * the bridge, or a tile that doesn't appear to be a valid "passage"
- * (essentially a tile that doesn't have lava on both sides and thus wouldn't
- * look good or make an effective bridge). If it manages to do this and find two
- * tiles that work as the start and end of the bridge, it places the paths,
- * pillars, and "cleans up" the tiles contiguous to its entrance and exit turfs,
- * removing any lava if present.
- *
- * It attempts this first in the vertical direction, and then the horizontal. So
- * "fowards" and "backwards" can mean NORTH/SOUTH or EAST/WEST depending on the
- * direction it's attempting.
- *
- * There are several bridge "themes" implemented in `make_walkway()` and
- * `make_pillar()`, and more can be added if desired.
- */
-/obj/effect/spawner/dynamic_bridge
- var/max_length = 8
- var/min_length = 4
- var/bridge_theme = LONG_BRIDGE_THEME_CULT
- var/list/forwards_backwards
- var/list/side_to_side
- var/turf/forward_goal
- var/turf/backward_goal
-
-/obj/effect/spawner/dynamic_bridge/Initialize(mapload)
- . = ..()
- bridge_theme = pick(
- LONG_BRIDGE_THEME_CULT,
- LONG_BRIDGE_THEME_HIERO,
- LONG_BRIDGE_THEME_CLOCKWORK,
- LONG_BRIDGE_THEME_STONE,
- LONG_BRIDGE_THEME_WOOD,
- LONG_BRIDGE_THEME_CATWALK,
- )
- return INITIALIZE_HINT_LATELOAD
-
-/obj/effect/spawner/dynamic_bridge/LateInitialize()
- forwards_backwards = list(NORTH, SOUTH)
- side_to_side = list(EAST, WEST)
- if(!attempt_bridge())
- forward_goal = null
- backward_goal = null
- forwards_backwards = list(EAST, WEST)
- side_to_side = list(NORTH, SOUTH)
- attempt_bridge()
-
- qdel(src)
-
-/// Returns whether the passed in turf is a valid "landing". A valid landing is
-/// a tile that hasn't been reserved by another bridge, and has a non-lava tile
-/// leading directly to it.
-/obj/effect/spawner/dynamic_bridge/proc/valid_landing(turf/T, direction)
- if(T.flags & LAVA_BRIDGE || T.flags & NO_LAVA_GEN)
- return FALSE
-
- var/turf/end = get_step(T, direction)
- if(end.flags & LAVA_BRIDGE || !(ismineralturf(end) || istype(end, /turf/simulated/floor/plating/asteroid)))
- return FALSE
-
- return TRUE
-
-/// Returns whether the passed in turf is a valid "passage". A valid passage is
-/// a lava tile that has lava on both sides of it. Invalid passage tiles do not
-/// look good as bridge walkways and defeat the purpose of there is floor right
-/// next to it.
-/obj/effect/spawner/dynamic_bridge/proc/valid_passage(turf/T)
- if(T.flags & LAVA_BRIDGE)
- return FALSE
- if(!istype(T, /turf/simulated/floor/lava/mapping_lava))
- return FALSE
- if(!istype(get_step(T, side_to_side[1]), /turf/simulated/floor/lava/mapping_lava))
- return FALSE
- if(!istype(get_step(T, side_to_side[2]), /turf/simulated/floor/lava/mapping_lava))
- return FALSE
-
- return TRUE
-
-/obj/effect/spawner/dynamic_bridge/proc/make_pillar(turf/T)
- for(var/obj/structure/spawner/S in T)
- qdel(S)
- for(var/mob/living/M in T)
- qdel(M)
- for(var/obj/structure/flora/F in T)
- qdel(F)
-
- switch(bridge_theme)
- if(LONG_BRIDGE_THEME_CULT)
- T.ChangeTurf(/turf/simulated/wall/cult)
- if(LONG_BRIDGE_THEME_HIERO)
- T.ChangeTurf(/turf/simulated/wall/indestructible/hierophant)
- if(LONG_BRIDGE_THEME_CLOCKWORK)
- T.ChangeTurf(/turf/simulated/wall/clockwork)
- if(LONG_BRIDGE_THEME_STONE)
- T.ChangeTurf(/turf/simulated/wall/cult)
- if(LONG_BRIDGE_THEME_WOOD)
- T.ChangeTurf(/turf/simulated/wall/mineral/wood/nonmetal)
- if(LONG_BRIDGE_THEME_CATWALK)
- new /obj/structure/lattice/catwalk/mining(T)
- new /obj/structure/marker_beacon/dock_marker/collision(T)
-
- T.flags |= LAVA_BRIDGE
-
-/obj/structure/bridge_walkway
- name = "floor"
- icon = 'icons/turf/floors.dmi'
- layer = ABOVE_OPEN_TURF_LAYER
- anchored = TRUE
- resistance_flags = LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF
-
-/obj/structure/bridge_walkway/Destroy()
- var/turf/T = get_turf(src)
- if(T)
- T.layer = PLATING_LAYER
- T.clear_filters()
-
- return ..()
-
-/obj/structure/bridge_walkway/cult
- icon_state = "cult"
-
-/obj/structure/bridge_walkway/hiero
- icon = 'icons/turf/floors/hierophant_floor.dmi'
- icon_state = "floor"
-
-/obj/structure/bridge_walkway/clockwork
- name = "clockwork floor"
- icon_state = "clockwork_floor"
-
-/obj/structure/bridge_walkway/clockwork/Initialize(mapload)
- . = ..()
- var/static/list/loc_connections = list(
- COMSIG_ATOM_ENTERED = PROC_REF(on_crossed)
- )
- AddElement(/datum/element/connect_loc, loc_connections)
-
-// Pretend to be a normal clockwork floor and duplicate its visual effect
-/obj/structure/bridge_walkway/clockwork/proc/on_crossed(atom/crosser)
- var/counter = 0
- for(var/obj/effect/temp_visual/ratvar/floor/floor in contents)
- if(++counter == 3)
- return
-
- if(!. && isliving(crosser))
- addtimer(CALLBACK(src, PROC_REF(spawn_visual)), 0.2 SECONDS, TIMER_DELETE_ME)
-
-/obj/structure/bridge_walkway/clockwork/proc/spawn_visual()
- new /obj/effect/temp_visual/ratvar/floor(loc)
-
-/obj/structure/bridge_walkway/wood
- icon_state = "wood"
-
-/obj/effect/spawner/dynamic_bridge/proc/make_walkway(turf/T)
- for(var/obj/structure/spawner/S in T)
- qdel(S)
- for(var/mob/living/M in T)
- qdel(M)
- for(var/obj/structure/flora/F in T)
- qdel(F)
-
- switch(bridge_theme)
- if(LONG_BRIDGE_THEME_CULT)
- new /obj/structure/bridge_walkway/cult(T)
- if(LONG_BRIDGE_THEME_HIERO)
- new /obj/structure/bridge_walkway/hiero(T)
- if(LONG_BRIDGE_THEME_CLOCKWORK)
- new /obj/structure/bridge_walkway/clockwork(T)
- if(LONG_BRIDGE_THEME_STONE)
- // Stone tiles are different sizes and shapes so these are
- // "safe-looking" arrangements.
- switch(rand(1, 5))
- if(1)
- new /obj/structure/stone_tile/block(T)
- var/obj/structure/stone_tile/block/cracked/C = new(T)
- C.dir = NORTH
- if(2)
- new /obj/structure/stone_tile/slab(T)
- if(3)
- new /obj/structure/stone_tile/surrounding(T)
- new /obj/structure/stone_tile/center(T)
- if(4)
- new /obj/structure/stone_tile/slab/cracked(T)
- if(5)
- new /obj/structure/stone_tile/burnt(T)
- var/obj/structure/stone_tile/surrounding_tile/ST = new(T)
- ST.dir = WEST
- var/obj/structure/stone_tile/block/B = new(T)
- B.dir = NORTH
- if(LONG_BRIDGE_THEME_WOOD)
- var/obj/structure/bridge_walkway/wood/tile = new(T)
- if(prob(20))
- tile.icon_state = pick("wood-broken", "wood-broken2", "wood-broken3", "wood-broken4", "wood-broken5", "wood-broken6", "wood-broken7")
- if(LONG_BRIDGE_THEME_CATWALK)
- new /obj/structure/lattice/catwalk/mining(T)
-
- T.flags |= LAVA_BRIDGE
-
-/// Make a tile safe for player passage, for use at the bridge entrance and exits
-/obj/effect/spawner/dynamic_bridge/proc/cleanup_edge(turf/T)
- if(istype(T, /turf/simulated/floor/lava/mapping_lava))
- T.ChangeTurf(/turf/simulated/floor/plating/asteroid/basalt/lava_land_surface)
- T.icon_state = "basalt" // hate
-
-/obj/effect/spawner/dynamic_bridge/proc/attempt_bridge()
- var/count = 1
- var/walk_dir = forwards_backwards[1]
- var/turf/forward_step = get_turf(src)
- var/turf/backward_step = get_turf(src)
- var/bad_passage = FALSE
-
- while(count <= max_length && !(forward_goal && backward_goal) && !bad_passage)
- if(walk_dir == forwards_backwards[1])
- if(!forward_goal)
- if(out_of_bounds(walk_dir, forward_step))
- break // Out of bounds
- forward_step = get_step(forward_step, walk_dir)
- if(valid_landing(forward_step, walk_dir))
- forward_goal = forward_step
- else if(!valid_passage(forward_step))
- bad_passage = TRUE
- count++
- walk_dir = forwards_backwards[2]
- else
- if(!backward_goal)
- if(out_of_bounds(walk_dir, backward_step))
- break // Out of bounds
- backward_step = get_step(backward_step, walk_dir)
- if(valid_landing(backward_step, walk_dir))
- backward_goal = backward_step
- else if(!valid_passage(backward_step))
- bad_passage = TRUE
- count++
- walk_dir = forwards_backwards[1]
-
- if(!bad_passage && count >= min_length && forward_goal && backward_goal)
- for(var/turf/T in get_line(forward_goal, backward_goal))
- make_walkway(T)
- for(var/turf/T in list(
- get_step(forward_goal, side_to_side[1]),
- get_step(forward_goal, side_to_side[2]),
- get_step(backward_goal, side_to_side[1]),
- get_step(backward_goal, side_to_side[2])))
- make_pillar(T)
- for(var/turf/T in get_line(
- get_step(get_step(forward_goal, side_to_side[1]), forwards_backwards[1]),
- get_step(get_step(forward_goal, side_to_side[2]), forwards_backwards[1])))
- cleanup_edge(T)
- for(var/turf/T in get_line(
- get_step(get_step(backward_goal, side_to_side[1]), forwards_backwards[2]),
- get_step(get_step(backward_goal, side_to_side[2]), forwards_backwards[2])))
- cleanup_edge(T)
-
- return TRUE
-
-/// Checks if we are going out of bounds. Returns TRUE if we are close (less than or equal to 2 turfs) to a border
-/obj/effect/spawner/dynamic_bridge/proc/out_of_bounds(direction, turf/current_turf)
- if(!direction || !current_turf)
- return TRUE
-
- switch(direction)
- if(NORTH)
- return current_turf.y >= world.maxy - 2
- if(EAST)
- return current_turf.x >= world.maxx - 2
- if(SOUTH)
- return current_turf.y <= 2
- if(WEST)
- return current_turf.x <= 2
- return TRUE
-
-#undef LONG_BRIDGE_THEME_CULT
-#undef LONG_BRIDGE_THEME_HIERO
-#undef LONG_BRIDGE_THEME_CLOCKWORK
-#undef LONG_BRIDGE_THEME_STONE
-#undef LONG_BRIDGE_THEME_WOOD
-#undef LONG_BRIDGE_THEME_CATWALK
diff --git a/code/datums/ruins/ruin_placer.dm b/code/datums/ruins/ruin_placer.dm
deleted file mode 100644
index 0d53c9bb71b75..0000000000000
--- a/code/datums/ruins/ruin_placer.dm
+++ /dev/null
@@ -1,203 +0,0 @@
-#define DEFAULT_PADDING 32
-
-/datum/ruin_placement
- var/datum/map_template/ruin/ruin
- var/base_padding
- var/padding
-
-/datum/ruin_placement/New(datum/map_template/ruin/ruin_, padding_ = DEFAULT_PADDING, base_padding_ = 0)
- . = ..()
- ruin = ruin_
- base_padding = base_padding_
- padding = padding_
-
-/datum/ruin_placement/proc/reduce_padding()
- padding = max(floor(padding / 2) - 1, -1)
-
-/datum/ruin_placement/proc/try_to_place(zlist_or_zlevel, area_whitelist)
- var/list/z_levels = islist(zlist_or_zlevel) ? zlist_or_zlevel : list(zlist_or_zlevel)
-
- // Our goal is to maximize padding, so we'll perform some number of attempts
- // on one z-level, then the next, until we reach some limit, then reduce the
- // padding and start again.
- padding = DEFAULT_PADDING
- while(padding >= 0)
- var/width_border = base_padding + round(ruin.width / 2) + padding
- var/height_border = base_padding + round(ruin.height / 2) + padding
-
- for(var/z_level in z_levels)
- var/placement_tries = PLACEMENT_TRIES
- while(placement_tries > 0)
- placement_tries--
-
- var/turf/central_turf = locate(
- rand(width_border, world.maxx - width_border),
- rand(height_border, world.maxy - height_border),
- z_level
- )
- var/valid = TRUE
-
- if(!central_turf)
- continue
-
- // Expand the original bounds of the ruin with our padding and call
- // that our list of affected turfs.
- var/list/bounds = ruin.get_coordinate_bounds(central_turf, centered = TRUE)
- var/datum/coords/bottom_left = bounds["bottom_left"]
- var/datum/coords/top_right = bounds["top_right"]
- bottom_left.x_pos -= padding
- bottom_left.y_pos -= padding
- top_right.x_pos += padding
- top_right.y_pos += padding
- var/list/affected_turfs = block(bottom_left.x_pos, bottom_left.y_pos, z_level, top_right.x_pos, top_right.y_pos, z_level)
-
- // One sanity check just in case
- if(!ruin.fits_in_map_bounds(central_turf, centered = TRUE))
- valid = FALSE
-
- for(var/turf/check in affected_turfs)
- var/area/new_area = get_area(check)
- if(!(istype(new_area, area_whitelist)) || check.flags & NO_RUINS)
- valid = FALSE
- break
-
- if(!valid)
- continue
-
- for(var/turf/T in affected_turfs)
- for(var/obj/structure/spawner/nest in T)
- qdel(nest)
- for(var/mob/living/simple_animal/monster in T)
- qdel(monster)
- for(var/obj/structure/flora/ash/plant in T)
- qdel(plant)
-
- ruin.load(central_turf, centered = TRUE)
- for(var/turf/T in ruin.get_affected_turfs(central_turf, centered = TRUE)) // Just flag the actual ruin turfs!
- T.flags |= NO_RUINS
- new /obj/effect/landmark/ruin(central_turf, ruin)
- ruin.loaded++
-
- log_world("Ruin \"[ruin.name]\" placed at ([central_turf.x], [central_turf.y], [central_turf.z])")
-
- var/map_filename = splittext(ruin.mappath, "/")
- map_filename = map_filename[length(map_filename)]
- SSblackbox.record_feedback("associative", "ruin_placement", 1, list(
- "map" = map_filename,
- "coords" = "[central_turf.x],[central_turf.y],[central_turf.z]"
- ))
-
- return TRUE
-
- // Ran out of placement tries for this z-level/padding, move to the next z-level
-
- // Ran out of z-levels to try with this padding, cut it and start again
- reduce_padding()
-
- // Ran out of z-levels, we got nowhere to place it
- return FALSE
-
-/datum/ruin_placer
- var/ruin_budget
- var/area_whitelist
- var/list/templates
- var/base_padding
-
-/datum/ruin_placer/proc/place_ruins(z_levels)
- if(!z_levels || !length(z_levels))
- WARNING("No Z levels provided - Not generating ruins")
- return
-
- for(var/zl in z_levels)
- var/turf/T = locate(1, 1, zl)
- if(!T)
- WARNING("Z level [zl] does not exist - Not generating ruins")
- return
-
- var/list/ruins = templates.Copy()
-
- var/list/forced_ruins = list() //These go first on the z level associated (same random one by default)
- var/list/ruins_availible = list() //we can try these in the current pass
- var/forced_z //If set we won't pick z level and use this one instead.
-
- //Set up the starting ruin list
- for(var/key in ruins)
- var/datum/map_template/ruin/R = ruins[key]
- if(R.get_cost() > ruin_budget) //Why would you do that
- continue
- if(R.always_place)
- forced_ruins[R] = -1
- if(R.unpickable)
- continue
- ruins_availible[R] = R.placement_weight
-
- while(ruin_budget > 0 && (length(ruins_availible) || length(forced_ruins)))
- var/datum/map_template/ruin/current_pick
- var/forced = FALSE
- if(length(forced_ruins)) //We have something we need to load right now, so just pick it
- for(var/ruin in forced_ruins)
- current_pick = ruin
- if(forced_ruins[ruin] > 0) //Load into designated z
- forced_z = forced_ruins[ruin]
- forced = TRUE
- break
- else //Otherwise just pick random one
- current_pick = pickweight(ruins_availible)
-
- var/datum/ruin_placement/placement = new(current_pick, base_padding_ = base_padding)
- var/placement_success = placement.try_to_place(forced_z ? forced_z : z_levels, area_whitelist)
-
- //That's done remove from priority even if it failed
- if(forced)
- //TODO : handle forced ruins with multiple variants
- forced_ruins -= current_pick
- forced = FALSE
-
- if(placement_success)
- ruin_budget -= current_pick.get_cost()
- if(!current_pick.allow_duplicates)
- for(var/datum/map_template/ruin/R in ruins_availible)
- if(R.id == current_pick.id)
- ruins_availible -= R
- if(current_pick.never_spawn_with)
- for(var/blacklisted_type in current_pick.never_spawn_with)
- for(var/possible_exclusion in ruins_availible)
- if(istype(possible_exclusion,blacklisted_type))
- ruins_availible -= possible_exclusion
- else
- for(var/datum/map_template/ruin/R in ruins_availible)
- if(R.id == current_pick.id)
- ruins_availible -= R
- log_world("Failed to place [current_pick.name] ruin.")
-
- forced_z = 0
-
- //Update the availible list
- for(var/datum/map_template/ruin/R in ruins_availible)
- if(R.get_cost() > ruin_budget)
- ruins_availible -= R
-
- log_world("Ruin loader finished with [ruin_budget] left to spend.")
-
-#undef DEFAULT_PADDING
-
-/datum/ruin_placer/space
- area_whitelist = /area/space
- base_padding = TRANSITIONEDGE + SPACERUIN_MAP_EDGE_PAD
-
-/datum/ruin_placer/space/New()
- ruin_budget = rand(
- GLOB.configuration.ruins.space_ruin_budget_min,
- GLOB.configuration.ruins.space_ruin_budget_max
- )
- templates = GLOB.space_ruins_templates
-
-/datum/ruin_placer/lavaland
- area_whitelist = /area/lavaland/surface/outdoors/unexplored
-
-/datum/ruin_placer/lavaland/New()
- ruin_budget = rand(
- GLOB.configuration.ruins.lavaland_ruin_budget_min,
- GLOB.configuration.ruins.lavaland_ruin_budget_max
- )
- templates = GLOB.lava_ruins_templates
diff --git a/code/datums/spells/alien_spells/basetype_alien_spell.dm b/code/datums/spells/alien_spells/basetype_alien_spell.dm
deleted file mode 100644
index 02ac8ddab2a2f..0000000000000
--- a/code/datums/spells/alien_spells/basetype_alien_spell.dm
+++ /dev/null
@@ -1,46 +0,0 @@
-/mob/living/carbon/proc/use_plasma_spell(amount, mob/living/carbon/user)
- var/obj/item/organ/internal/alien/plasmavessel/vessel = get_int_organ(/obj/item/organ/internal/alien/plasmavessel)
- if(amount > vessel.stored_plasma)
- return FALSE
- add_plasma(-amount)
- return TRUE
-
-/* add_plasma just requires the plasma amount, so admins can easily varedit it and stuff
-Updates the spell's actions on use as well, so they know when they can or can't use their powers*/
-
-/mob/living/carbon/proc/add_plasma(amount)
- var/obj/item/organ/internal/alien/plasmavessel/vessel = get_int_organ(/obj/item/organ/internal/alien/plasmavessel)
- if(!vessel)
- return
- vessel.stored_plasma = clamp(vessel.stored_plasma + amount, 0, vessel.max_plasma)
- update_plasma_display(src)
- for(var/datum/action/spell_action/action in actions)
- action.UpdateButtons()
-
-/datum/spell/alien_spell
- action_background_icon_state = "bg_alien"
- clothes_req = FALSE
- base_cooldown = 0
- create_attack_logs = FALSE
- /// Every alien spell creates only logs, no attack messages on someone placing weeds, but you DO get attack messages on neurotoxin and corrosive acid
- create_custom_logs = TRUE
- antimagic_flags = NONE
- /// How much plasma it costs to use this
- var/plasma_cost = 0
-
-/// Every single alien spell uses a "spell name + plasmacost" format
-/datum/spell/alien_spell/New()
- ..()
- if(plasma_cost)
- name = "[name] ([plasma_cost])"
- action.name = name
- action.desc = desc
- action.UpdateButtons()
-
-/datum/spell/alien_spell/write_custom_logs(list/targets, mob/user)
- user.create_log(ATTACK_LOG, "Cast the spell [name]")
-
-/datum/spell/alien_spell/create_new_handler()
- var/datum/spell_handler/alien/handler = new
- handler.plasma_cost = plasma_cost
- return handler
diff --git a/code/datums/spells/alien_spells/basetype_alien_touch.dm b/code/datums/spells/alien_spells/basetype_alien_touch.dm
deleted file mode 100644
index f979b2af6ba37..0000000000000
--- a/code/datums/spells/alien_spells/basetype_alien_touch.dm
+++ /dev/null
@@ -1,50 +0,0 @@
-/datum/spell/touch/alien_spell
- name = "Basetype Alien spell"
- desc = "You should not see this in game, if you do file a github report!"
- hand_path = "/obj/item/melee/touch_attack/alien"
- /// Extremely fast cooldown, only present so the cooldown system doesn't explode
- base_cooldown = 1
- action_background_icon_state = "bg_alien"
- clothes_req = FALSE
- antimagic_flags = NONE
- create_attack_logs = FALSE
- /// Every alien spell creates only logs, no attack messages on someone placing weeds, but you DO get attack messages on neurotoxin and corrosive acid
- create_custom_logs = TRUE
- /// We want a special on remove message, so we got this variable to do so
- on_remove_message = FALSE
- /// How much plasma it costs to use this
- var/plasma_cost = 0
- action_icon_state = "gib"
-
-/datum/spell/touch/alien_spell/New()
- ..()
- if(plasma_cost)
- name = "[name] ([plasma_cost])"
-
-/datum/spell/touch/alien_spell/Click(mob/user = usr)
- if(attached_hand)
- to_chat(user, "You withdraw your [src].")
- ..()
-
-/datum/spell/touch/alien_spell/write_custom_logs(list/targets, mob/user)
- user.create_log(ATTACK_LOG, "Cast the spell [name]")
-
-/datum/spell/touch/alien_spell/create_new_handler()
- var/datum/spell_handler/alien/H = new
- H.plasma_cost = plasma_cost
- return H
-
-/obj/item/melee/touch_attack/alien
- name = "Basetype Alien touch_attack"
- desc = "You should not see this in game, if you do file a github report!"
- /// Alien spells don't have catchphrases
- catchphrase = null
- /// Beepsky shouldn't be arresting you over this
- needs_permit = FALSE
-
-/obj/item/melee/touch_attack/alien/proc/plasma_check(plasma, mob/living/carbon/user)
- var/plasma_current = user.get_plasma()
- if(plasma_current < plasma)
- qdel(src)
- return FALSE
- return TRUE
diff --git a/code/datums/spells/alien_spells/build_resin_structure.dm b/code/datums/spells/alien_spells/build_resin_structure.dm
deleted file mode 100644
index b33967cd40a42..0000000000000
--- a/code/datums/spells/alien_spells/build_resin_structure.dm
+++ /dev/null
@@ -1,93 +0,0 @@
-#define RESIN_WALL "Resin Wall"
-#define RESIN_NEST "Resin Nest"
-#define RESIN_DOOR "Resin Door"
-#define REVIVAL_NEST "Revival Nest"
-
-/datum/spell/alien_spell/build_resin
- name = "Build Resin Structure"
- desc = "Allows you to create resin structures. Does not work while in space."
- plasma_cost = 55
- action_icon_state = "alien_resin"
-
-/datum/spell/alien_spell/build_resin/create_new_targeting()
- return new /datum/spell_targeting/self
-
-/datum/spell/alien_spell/build_resin/cast(list/targets, mob/living/carbon/user)
- var/static/list/resin_buildings = list(RESIN_WALL = image(icon = 'icons/obj/smooth_structures/alien/resin_wall.dmi', icon_state = "resin_wall-0"),
- RESIN_NEST = image(icon = 'icons/mob/alien.dmi', icon_state = "nest"),
- RESIN_DOOR = image(icon = 'icons/obj/smooth_structures/alien/resin_door.dmi', icon_state = "resin"),
- REVIVAL_NEST = image(icon = 'icons/mob/alien.dmi', icon_state = "placeholder_rejuv_nest"))
- var/choice = show_radial_menu(user, user, resin_buildings, src, radius = 40)
- var/turf/turf_to_spawn_at = user.loc
- if(!choice)
- revert_cast(user)
- return
- if(!do_mob(user, user, 3 SECONDS))
- revert_cast()
- return
- if(isspaceturf(turf_to_spawn_at))
- to_chat(user, "You cannot build the [choice] while in space!")
- revert_cast(user)
- return
- var/obj/structure/alien/resin/resin_on_turf = locate() in turf_to_spawn_at
- if(resin_on_turf)
- to_chat(user, "There is already a resin construction here.")
- revert_cast(user)
- return
- user.visible_message("[user] vomits up a thick purple substance and shapes it!")
- switch(choice)
- if(RESIN_WALL)
- new /obj/structure/alien/resin/wall(turf_to_spawn_at)
- if(RESIN_NEST)
- new /obj/structure/bed/nest(turf_to_spawn_at)
- if(RESIN_DOOR)
- new /obj/structure/alien/resin/door(turf_to_spawn_at)
- if(REVIVAL_NEST)
- new /obj/structure/bed/revival_nest(turf_to_spawn_at)
-
-/datum/spell/touch/alien_spell/consume_resin
- name = "Consume resin structures"
- desc = "Allows you to rip and tear straight through resin structures."
- action_icon_state = "alien_resin"
- hand_path = "/obj/item/melee/touch_attack/alien/consume_resin"
- plasma_cost = 10
- base_cooldown = 5 SECONDS
-
-/obj/item/melee/touch_attack/alien/consume_resin
- name = "Resin consumption"
- desc = "The hunger..."
- icon_state = "alien_acid"
-
-/obj/item/melee/touch_attack/alien/consume_resin/after_attack(atom/target, mob/user, proximity_flag, click_parameters)
- . = ..()
- if(target == user)
- to_chat(user, "You stop trying to consume resin.")
- return
- if(!proximity_flag || !iscarbon(user) || HAS_TRAIT(user, TRAIT_HANDS_BLOCKED))
- return
- var/mob/living/carbon/C = user
- if(istype(target, /obj/structure/alien/weeds))
- qdel(target)
- if(istype(target, /obj/structure/alien/weeds/node))
- C.add_plasma(50)
- C.visible_message("[C] rips and tears into [target] with their teeth!", "You viciously rip apart and consume [target]!")
- return
- if(!plasma_check(10, C))
- to_chat(C, "You don't have enough plasma to perform this action!")
- return
- var/static/list/resin_objects = list(/obj/structure/alien/resin, /obj/structure/alien/egg, /obj/structure/bed/nest, /obj/structure/bed/revival_nest)
- for(var/resin_type in resin_objects)
- if(!istype(target, resin_type))
- continue
- C.visible_message("[C] rips and tears into [target] with their teeth!")
- if(!do_after(C, 3 SECONDS, target = target))
- return
- to_chat(C, "You viciously rip apart and consume [target]!")
- C.add_plasma(-10)
- qdel(target)
- handle_delete(user)
-
-#undef RESIN_WALL
-#undef RESIN_NEST
-#undef RESIN_DOOR
-#undef REVIVAL_NEST
diff --git a/code/datums/spells/alien_spells/corrosive_acid_spit.dm b/code/datums/spells/alien_spells/corrosive_acid_spit.dm
deleted file mode 100644
index eca1d74ac8637..0000000000000
--- a/code/datums/spells/alien_spells/corrosive_acid_spit.dm
+++ /dev/null
@@ -1,76 +0,0 @@
-/datum/spell/touch/alien_spell/corrosive_acid
- name = "Corrosive acid"
- desc = "Spit acid on someone in range, this acid melts through nearly anything and heavily damages anyone lacking proper safety equipment."
- hand_path = "/obj/item/melee/touch_attack/alien/corrosive_acid"
- action_icon_state = "alien_acid"
- plasma_cost = 200
- base_cooldown = 15 SECONDS
-
-/obj/item/melee/touch_attack/alien/corrosive_acid
- name = "Corrosive acid"
- desc = "A fistfull of death."
- icon_state = "alien_acid"
-
-/obj/item/melee/touch_attack/alien/corrosive_acid/after_attack(atom/target, mob/user, proximity_flag, click_parameters)
- . = ..()
- if(target == user)
- to_chat(user, "You withdraw your readied acid.")
- return
- if(!proximity_flag || isalien(target) || !iscarbon(user) || HAS_TRAIT(user, TRAIT_HANDS_BLOCKED)) // Don't want xenos ditching out of cuffs
- return
- var/mob/living/carbon/C = user
- if(!plasma_check(200, C))
- to_chat(C, "You don't have enough plasma to perform this action!")
- return
- var/acid_damage_modifier = 100
- if(isliving(target))
- acid_damage_modifier = 50
- if(target.acid_act(2 * acid_damage_modifier, acid_damage_modifier))
- visible_message("[C] vomits globs of vile stuff all over [target]. It begins to sizzle and melt under the bubbling mess of acid!")
- add_attack_logs(C, target, "Applied corrosive acid") // Want this logged
- C.add_plasma(-200)
- else
- to_chat(C, "You cannot dissolve this object.")
- handle_delete(user)
-
-/datum/spell/touch/alien_spell/burning_touch
- name = "Blazing touch"
- desc = "Boil acid within your hand to burn through anything you touch with it, deals a lot of damage to aliens and destroys resin structures instantly."
- hand_path = "/obj/item/melee/touch_attack/alien/burning_touch"
- action_icon_state = "alien_acid"
- plasma_cost = 100
- base_cooldown = 10 SECONDS
-
-/obj/item/melee/touch_attack/alien/burning_touch
- name = "Blazing touch"
- desc = "The air warps around your hand, somehow the heat doesn't hurt."
- icon_state = "alien_acid"
-
-/obj/item/melee/touch_attack/alien/burning_touch/after_attack(atom/target, mob/user, proximity_flag, click_parameters)
- . = ..()
- if(target == user)
- to_chat(user, "You cool down your boiled aid.")
- return
- if(!proximity_flag || !iscarbon(user) || HAS_TRAIT(user, TRAIT_HANDS_BLOCKED))
- return
- var/mob/living/carbon/C = user
- if(!plasma_check(100, C))
- to_chat(C, "You don't have enough plasma to perform this action!")
- return
- if(isliving(target))
- var/mob/living/guy_to_burn = target
- add_attack_logs(C, target, "Applied blazing touch") // Want this logged
- guy_to_burn.adjustFireLoss(60)
- guy_to_burn.adjust_fire_stacks(3)
- guy_to_burn.IgniteMob()
- C.visible_message("[C] touches [target] and a fireball erupts on contact!")
- C.add_plasma(-100)
- else
- var/static/list/resin_objects = list(/obj/structure/alien/resin, /obj/structure/alien/egg, /obj/structure/bed/nest, /obj/structure/bed/revival_nest)
- for(var/resin_type in resin_objects)
- if(!istype(target, resin_type))
- continue
- C.visible_message("[C] touches [target] and burns right through it!")
- C.add_plasma(-100)
- qdel(target)
- handle_delete(user)
diff --git a/code/datums/spells/banana_touch.dm b/code/datums/spells/banana_touch.dm
deleted file mode 100644
index 165ba07208ef3..0000000000000
--- a/code/datums/spells/banana_touch.dm
+++ /dev/null
@@ -1,74 +0,0 @@
-/datum/spell/touch/banana
- name = "Banana Touch"
- desc = "A spell popular at wizard birthday parties, this spell will put on a clown costume on the target, \
- stun them with a loud HONK, and mutate them to make them more entertaining! \
- Warning : Effects are permanent on non-wizards."
- hand_path = /obj/item/melee/touch_attack/banana
-
- base_cooldown = 30 SECONDS
- clothes_req = TRUE
- cooldown_min = 100 //50 deciseconds reduction per rank
- action_icon_state = "clown"
-
-/obj/item/melee/touch_attack/banana
- name = "banana touch"
- desc = "It's time to start clowning around."
- catchphrase = "NWOLC YRGNA"
- on_use_sound = 'sound/items/AirHorn.ogg'
- icon_state = "banana_touch"
- item_state = "banana_touch"
- var/is_apprentice_spell = FALSE
-
-/datum/spell/touch/banana/apprentice
- hand_path = /obj/item/melee/touch_attack/banana/apprentice
-
-/obj/item/melee/touch_attack/banana/apprentice
- is_apprentice_spell = TRUE
-
-/obj/item/melee/touch_attack/banana/after_attack(atom/target, mob/user, proximity_flag, click_parameters)
- . = ..()
-
- if(is_apprentice_spell && iswizard(target) && target != user)
- to_chat(user, "Seriously?! Honk THEM, not me!")
- return
- if(!proximity_flag || target == user || blocked_by_antimagic || !ishuman(target) || !iscarbon(user) || HAS_TRAIT(user, TRAIT_HANDS_BLOCKED))
- return
-
- var/datum/effect_system/smoke_spread/s = new
- s.set_up(5, FALSE, target)
- s.start()
-
- to_chat(user, "HONK")
- var/mob/living/carbon/human/H = target
- H.bananatouched()
- handle_delete(user)
-
-/mob/living/carbon/human/proc/bananatouched()
- to_chat(src, "HONK")
- Weaken(14 SECONDS)
- Stuttering(30 SECONDS)
- do_jitter_animation(30 SECONDS)
-
- if(iswizard(src) || (mind && mind.special_role == SPECIAL_ROLE_WIZARD_APPRENTICE)) //Wizards get non-cursed clown robes and magical mask.
- drop_item_to_ground(shoes, force = TRUE)
- drop_item_to_ground(wear_mask, force = TRUE)
- drop_item_to_ground(head, force = TRUE)
- drop_item_to_ground(wear_suit, force = TRUE)
- equip_to_slot_if_possible(new /obj/item/clothing/head/wizard/clown, ITEM_SLOT_HEAD, TRUE, TRUE)
- equip_to_slot_if_possible(new /obj/item/clothing/suit/wizrobe/clown, ITEM_SLOT_OUTER_SUIT, TRUE, TRUE)
- equip_to_slot_if_possible(new /obj/item/clothing/shoes/clown_shoes/magical, ITEM_SLOT_SHOES, TRUE, TRUE)
- equip_to_slot_if_possible(new /obj/item/clothing/mask/gas/clownwiz, ITEM_SLOT_MASK, TRUE, TRUE)
- else
- qdel(shoes)
- qdel(wear_mask)
- qdel(w_uniform)
- equip_to_slot_if_possible(new /obj/item/clothing/under/rank/civilian/clown/nodrop, ITEM_SLOT_JUMPSUIT, TRUE, TRUE)
- equip_to_slot_if_possible(new /obj/item/clothing/shoes/clown_shoes/nodrop, ITEM_SLOT_SHOES, TRUE, TRUE)
- equip_to_slot_if_possible(new /obj/item/clothing/mask/gas/clown_hat/nodrop, ITEM_SLOT_MASK, TRUE, TRUE)
- dna.SetSEState(GLOB.clumsyblock, TRUE, TRUE)
- dna.SetSEState(GLOB.comicblock, TRUE, TRUE)
- singlemutcheck(src, GLOB.clumsyblock, MUTCHK_FORCED)
- singlemutcheck(src, GLOB.comicblock, MUTCHK_FORCED)
- if(!(iswizard(src) || (mind && mind.special_role == SPECIAL_ROLE_WIZARD_APPRENTICE))) //Mutations are permanent on non-wizards. Can still be removed by genetics fuckery but not mutadone.
- dna.default_blocks.Add(GLOB.clumsyblock)
- dna.default_blocks.Add(GLOB.comicblock)
diff --git a/code/datums/spells/bloodcrawl.dm b/code/datums/spells/bloodcrawl.dm
deleted file mode 100644
index 9bc2864a656c7..0000000000000
--- a/code/datums/spells/bloodcrawl.dm
+++ /dev/null
@@ -1,300 +0,0 @@
-/datum/spell/bloodcrawl
- name = "Blood Crawl"
- desc = "Use pools of blood to phase out of existence."
- base_cooldown = 1 SECONDS
- clothes_req = FALSE
- cooldown_min = 0
- should_recharge_after_cast = FALSE
- overlay = null
- action_icon_state = "bloodcrawl"
- action_background_icon_state = "bg_demon"
- antimagic_flags = NONE
- var/allowed_type = /obj/effect/decal/cleanable
- var/phased = FALSE
-
-/datum/spell/bloodcrawl/create_new_targeting()
- var/datum/spell_targeting/targeted/T = new()
- T.selection_type = SPELL_SELECTION_RANGE
- T.allowed_type = allowed_type
- T.random_target = TRUE
- T.range = 1
- T.use_turf_of_user = TRUE
- return T
-
-/datum/spell/bloodcrawl/valid_target(obj/effect/decal/cleanable/target, user)
- return target.can_bloodcrawl_in()
-
-/datum/spell/bloodcrawl/can_cast(mob/living/user, charge_check, show_message)
- . = ..()
- if(!.)
- return
- if(!isliving(user))
- return FALSE
- if(SEND_SIGNAL(user, COMSIG_MOB_PRE_JAUNT, get_turf(user)) & COMPONENT_BLOCK_JAUNT)
- return FALSE
-
-/datum/spell/bloodcrawl/cast(list/targets, mob/living/user)
- var/atom/target = targets[1]
- if(!phased)
- if(phaseout(target, user))
- phased = TRUE
- cooldown_handler.revert_cast()
- else
- if(phasein(target, user))
- phased = FALSE
- cooldown_handler.start_recharge()
-
-
-
-//Travel through pools of blood. Slaughter Demon powers for everyone!
-#define BLOODCRAWL 1
-#define BLOODCRAWL_EAT 2
-
-
-/obj/item/bloodcrawl
- name = "blood crawl"
- desc = "You are unable to hold anything while in this form."
- icon = 'icons/effects/blood.dmi'
- flags = NODROP|ABSTRACT
-
-/// Can't use the wizard one, blocked by jaunt/slow
-/obj/effect/dummy/slaughter
- name = "odd blood"
- icon = 'icons/effects/effects.dmi'
- icon_state = "nothing"
- density = FALSE
- anchored = TRUE
- invisibility = 60
- resistance_flags = LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF
-
-/obj/effect/dummy/slaughter/relaymove(mob/user, direction)
- forceMove(get_step(src, direction))
-
-/obj/effect/dummy/slaughter/ex_act()
- return
-
-/obj/effect/dummy/slaughter/bullet_act()
- return
-
-/obj/effect/dummy/slaughter/singularity_act()
- return
-
-
-/datum/spell/bloodcrawl/proc/block_hands(mob/living/carbon/C)
- if(C.l_hand || C.r_hand)
- to_chat(C, "You may not hold items while blood crawling!")
- return FALSE
- var/obj/item/bloodcrawl/B1 = new(C)
- var/obj/item/bloodcrawl/B2 = new(C)
- B1.icon_state = "bloodhand_left"
- B2.icon_state = "bloodhand_right"
- C.put_in_hands(B1)
- C.put_in_hands(B2)
- C.regenerate_icons()
- return TRUE
-
-/obj/effect/temp_visual/dir_setting/bloodcrawl
- icon = 'icons/mob/mob.dmi'
- icon_state = "blank" // Flicks are used instead
- duration = 0.6 SECONDS
- layer = MOB_LAYER + 0.1
-
-/obj/effect/temp_visual/dir_setting/bloodcrawl/Initialize(mapload, set_dir, animation_state)
- . = ..()
- flick(animation_state, src) // Setting the icon_state to the animation has timing issues and can cause frame skips
-
-/datum/spell/bloodcrawl/proc/sink_animation(atom/A, mob/living/L)
- var/turf/mob_loc = get_turf(L)
- mob_loc.visible_message(
- "[L] sinks into [A].",
- "You hear something sinking into a thick liquid."
- )
- playsound(mob_loc, 'sound/misc/enter_blood.ogg', 100, TRUE, -1)
- new /obj/effect/temp_visual/dir_setting/bloodcrawl(mob_loc, L.dir, "jaunt")
-
-/datum/spell/bloodcrawl/proc/handle_consumption(mob/living/L, mob/living/victim, atom/A, obj/effect/dummy/slaughter/holder)
- if(!HAS_TRAIT(L, TRAIT_BLOODCRAWL_EAT))
- return
-
- if(!istype(victim))
- return
- if(victim.stat == CONSCIOUS)
- A.visible_message(
- "[victim] kicks free of [A] just before entering it!",
- "You hear something sinking into a thick liquid and someone struggling!"
- )
- L.stop_pulling()
- return
-
- victim.forceMove(holder)
- victim.emote("scream")
- A.visible_message(
- "[L] drags [victim] into [A]!",
- "You hear something being dragged into a thick liquid!"
- )
- L.stop_pulling()
- to_chat(L, "You begin to feast on [victim]. You cannot move while you are doing this.")
- A.visible_message(
- "Loud eating sounds come from the blood...",
- "The sound of torn flesh and snapping bones fills the air..."
- )
- var/sound
- if(isslaughterdemon(L))
- var/mob/living/simple_animal/demon/slaughter/SD = L
- sound = SD.feast_sound
- else
- sound = 'sound/misc/demon_consume.ogg'
-
- for(var/i in 1 to 3)
- playsound(get_turf(L), sound, 100, 1)
- sleep(3 SECONDS)
-
- if(!victim)
- to_chat(L, "You happily devour... nothing? Your meal vanished at some point!")
- return
- if(victim.mind)
- to_chat(L, "You devour [victim]. Your health is fully restored.")
- L.adjustBruteLoss(-1000)
- L.adjustFireLoss(-1000)
- L.adjustOxyLoss(-1000)
- L.adjustToxLoss(-1000)
- else if((ishuman(victim) || isrobot(victim)))
- to_chat(L, "You devour [victim], but their lack of intelligence renders their flesh dull and unappetising, leaving you wanting for more.")
- L.adjustBruteLoss(-50)
- if(!isslaughterdemon(L))
- L.adjustFireLoss(-50)
- else if(isanimal(victim))
- to_chat(L, "You devour [victim], but this measly meal barely sates your appetite!")
- L.adjustBruteLoss(-25)
- if(!isslaughterdemon(L))
- L.adjustFireLoss(-25)
-
- if(isslaughterdemon(L))
- var/mob/living/simple_animal/demon/slaughter/demon = L
- demon.devoured++
- to_chat(victim, "You feel teeth sink into your flesh, and the--")
- var/obj/item/organ/internal/regenerative_core/legion/core = victim.get_int_organ(/obj/item/organ/internal/regenerative_core/legion)
- if(core)
- core.remove(victim)
- qdel(core)
- victim.adjustBruteLoss(1000)
- victim.forceMove(demon)
- demon.consumed_mobs.Add(victim)
- ADD_TRAIT(victim, TRAIT_UNREVIVABLE, "demon")
- if(ishuman(victim))
- var/mob/living/carbon/human/H = victim
- if(H.w_uniform && istype(H.w_uniform, /obj/item/clothing/under))
- var/obj/item/clothing/under/U = H.w_uniform
- U.sensor_mode = SENSOR_OFF
- else
- victim.ghostize()
- qdel(victim)
-
-/datum/spell/bloodcrawl/proc/post_phase_in(mob/living/L, obj/effect/dummy/slaughter/holder)
- L.notransform = FALSE
-
-/datum/spell/bloodcrawl/proc/phaseout(obj/effect/decal/cleanable/B, mob/living/L)
-
- if(iscarbon(L) && !block_hands(L))
- return FALSE
- L.notransform = TRUE
- INVOKE_ASYNC(src, PROC_REF(async_phase), B, L)
- return TRUE
-
-/datum/spell/bloodcrawl/proc/async_phase(obj/effect/decal/cleanable/B, mob/living/L)
- var/turf/mobloc = get_turf(L)
- sink_animation(B, L)
- var/obj/effect/dummy/slaughter/holder = new /obj/effect/dummy/slaughter(mobloc)
- L.forceMove(holder)
- L.ExtinguishMob()
- handle_consumption(L, L.pulling, B, holder)
- post_phase_in(L, holder)
-
-/datum/spell/bloodcrawl/proc/rise_animation(turf/tele_loc, mob/living/L, atom/A)
- new /obj/effect/temp_visual/dir_setting/bloodcrawl(tele_loc, L.dir, "jauntup")
- if(prob(25) && isdemon(L))
- var/list/voice = list('sound/hallucinations/behind_you1.ogg', 'sound/hallucinations/im_here1.ogg', 'sound/hallucinations/turn_around1.ogg', 'sound/hallucinations/i_see_you1.ogg')
- playsound(tele_loc, pick(voice),50, TRUE, -1)
- A.visible_message(
- "[L] rises out of [A]!",
- "You hear something rising out of a thick liquid!"
- )
- playsound(get_turf(tele_loc), 'sound/misc/exit_blood.ogg', 100, TRUE, -1)
-
-/datum/spell/bloodcrawl/proc/unblock_hands(mob/living/carbon/C)
- if(!istype(C))
- return
- for(var/obj/item/bloodcrawl/BC in C)
- qdel(BC)
-
-/datum/spell/bloodcrawl/proc/rise_message(atom/A)
- A.visible_message(
- "[A] starts to bubble...",
- "You can hear bubbling..."
- )
-
-/datum/spell/bloodcrawl/proc/post_phase_out(atom/A, mob/living/L)
- if(isslaughterdemon(L))
- var/mob/living/simple_animal/demon/slaughter/S = L
- S.speed = 0
- S.boost = world.time + 6 SECONDS
- L.color = A.color
- addtimer(VARSET_CALLBACK(L, color, null), 6 SECONDS)
-
-
-/datum/spell/bloodcrawl/proc/phasein(atom/A, mob/living/L)
-
- if(L.notransform)
- to_chat(L, "Finish eating first!")
- return FALSE
- rise_message(A)
- if(!do_after(L, 2 SECONDS, target = A))
- return FALSE
- if(!A)
- return FALSE
- var/turf/tele_loc = isturf(A) ? A : A.loc
- var/holder = L.loc
- L.forceMove(tele_loc)
- L.client.eye = L
-
- rise_animation(tele_loc, L, A)
-
- unblock_hands(L)
-
- QDEL_NULL(holder)
-
- post_phase_out(A, L)
- return TRUE
-
-/datum/spell/bloodcrawl/shadow_crawl
- name = "Shadow Crawl"
- desc = "Fade into the shadows, increasing your speed and making you incomprehensible. Will not work in brightened terrane."
- allowed_type = /turf
- action_background_icon_state = "shadow_demon_bg"
- action_icon_state = "shadow_crawl"
-
-/datum/spell/bloodcrawl/shadow_crawl/valid_target(turf/target, user)
- return target.get_lumcount() < 0.2
-
-/datum/spell/bloodcrawl/shadow_crawl/rise_message(atom/A)
- return
-
-/datum/spell/bloodcrawl/shadow_crawl/rise_animation(turf/tele_loc, mob/living/L, atom/A)
- new /obj/effect/temp_visual/dir_setting/bloodcrawl(get_turf(L), L.dir, "shadowwalk_appear")
-
-/datum/spell/bloodcrawl/shadow_crawl/handle_consumption(mob/living/L, mob/living/victim, atom/A, obj/effect/dummy/slaughter/holder)
- return
-
-/datum/spell/bloodcrawl/shadow_crawl/sink_animation(atom/A, mob/living/L)
- A.visible_message("[L] sinks into the shadows...")
- new /obj/effect/temp_visual/dir_setting/bloodcrawl(get_turf(L), L.dir, "shadowwalk_disappear")
-
-/datum/spell/bloodcrawl/shadow_crawl/post_phase_in(mob/living/L, obj/effect/dummy/slaughter/holder)
- ..()
- if(!istype(L, /mob/living/simple_animal/demon/shadow))
- return
- var/mob/living/simple_animal/demon/shadow/S = L
- S.RegisterSignal(holder, COMSIG_MOVABLE_MOVED, TYPE_PROC_REF(/mob/living/simple_animal/demon/shadow, check_darkness))
-
-#undef BLOODCRAWL
-#undef BLOODCRAWL_EAT
diff --git a/code/datums/spells/chaplain_bless.dm b/code/datums/spells/chaplain_bless.dm
deleted file mode 100644
index 518f51207c8ee..0000000000000
--- a/code/datums/spells/chaplain_bless.dm
+++ /dev/null
@@ -1,52 +0,0 @@
-
-/datum/spell/chaplain_bless
- name = "Bless"
- desc = "Blesses a single person."
-
- base_cooldown = 6 SECONDS
- clothes_req = FALSE
- invocation = "none"
- invocation_type = "none"
- antimagic_flags = NONE
-
- selection_activated_message = "You prepare a blessing. Click on a target to start blessing."
- selection_deactivated_message = "The crew will be blessed another time."
- cooldown_min = 20
- action_icon_state = "shield"
-
-/datum/spell/chaplain_bless/create_new_targeting()
- var/datum/spell_targeting/click/T = new()
- T.range = 1
- T.click_radius = -1
- return T
-
-/datum/spell/chaplain_bless/valid_target(mob/living/carbon/human/target, mob/user)
- return target.mind && target.ckey && !target.stat
-
-/datum/spell/chaplain_bless/cast(list/targets, mob/living/user = usr)
- if(!istype(user))
- to_chat(user, "Somehow, you are not a living mob. This should never happen. Report this bug.")
- revert_cast()
- return
-
- if(!user.mind)
- to_chat(user, "Somehow, you are mindless. This should never happen. Report this bug.")
- revert_cast()
- return
-
- if(!HAS_MIND_TRAIT(user, TRAIT_HOLY))
- to_chat(user, "Somehow, you are not holy enough to use this ability. This should never happen. Report this bug.")
- revert_cast()
- return
-
- var/mob/living/carbon/human/target = targets[1]
-
- spawn(0) // allows cast to complete even if recipient ignores the prompt
- if(tgui_alert(target, "[user] wants to bless you, in the name of [user.p_their()] religion. Accept?", "Accept Blessing?", list("Yes", "No")) == "Yes") // prevents forced conversions
- user.visible_message("[user] starts blessing [target] in the name of [SSticker.Bible_deity_name].", "You start blessing [target] in the name of [SSticker.Bible_deity_name].")
- if(do_after(user, 150, target = target))
- user.visible_message("[user] has blessed [target] in the name of [SSticker.Bible_deity_name].", "You have blessed [target] in the name of [SSticker.Bible_deity_name].")
- if(!target.mind.isblessed)
- target.mind.isblessed = TRUE
- user.mind.num_blessed++
-
diff --git a/code/datums/spells/charge.dm b/code/datums/spells/charge.dm
deleted file mode 100644
index 50b5f3a67e893..0000000000000
--- a/code/datums/spells/charge.dm
+++ /dev/null
@@ -1,103 +0,0 @@
-/datum/spell/charge
- name = "Charge"
- desc = "This spell can be used to recharge a variety of things in your hands, from magical artifacts to electrical components. A creative wizard can even use it to grant magical power to a fellow magic user."
- base_cooldown = 1 MINUTES
- clothes_req = FALSE
- invocation = "DIRI CEL"
- invocation_type = "whisper"
- cooldown_min = 400 //50 deciseconds reduction per rank
- action_icon_state = "charge"
-
-/datum/spell/charge/create_new_targeting()
- return new /datum/spell_targeting/self
-
-/datum/spell/charge/cast(list/targets, mob/user = usr)
- for(var/mob/living/L in targets)
- var/list/hand_items = list(L.get_active_hand(),L.get_inactive_hand())
- var/charged_item = null
- var/burnt_out = FALSE
-
- if(L.pulling && (isliving(L.pulling)))
- var/mob/living/M = L.pulling
- if(length(M.mob_spell_list) != 0 || (M.mind && length(M.mind.spell_list) != 0))
- for(var/datum/spell/S in M.mob_spell_list)
- S.cooldown_handler.revert_cast()
- if(M.mind)
- for(var/datum/spell/S in M.mind.spell_list)
- S.cooldown_handler.revert_cast()
- to_chat(M, "You feel raw magical energy flowing through you, it feels good!")
- else
- to_chat(M, "You feel very strange for a moment, but then it passes.")
- burnt_out = TRUE
- charged_item = M
- break
- for(var/obj/item in hand_items)
- if(istype(item, /obj/item/spellbook))
- if(istype(item, /obj/item/spellbook/oneuse))
- var/obj/item/spellbook/oneuse/I = item
- if(prob(80))
- L.visible_message("[I] catches fire!")
- qdel(I)
- else
- I.used = FALSE
- charged_item = I
- break
- else
- to_chat(L, "Glowing red letters appear on the front cover...")
- to_chat(L, "[pick("NICE TRY BUT NO!","CLEVER BUT NOT CLEVER ENOUGH!", "SUCH FLAGRANT CHEESING IS WHY WE ACCEPTED YOUR APPLICATION!", "CUTE!", "YOU DIDN'T THINK IT'D BE THAT EASY, DID YOU?")]")
- burnt_out = TRUE
- else if(istype(item, /obj/item/book/granter))
- var/obj/item/book/granter/I = item
- if(prob(80))
- L.visible_message("[I] catches fire!")
- qdel(I)
- else
- I.uses += 1
- charged_item = I
- break
-
- else if(istype(item, /obj/item/gun/magic))
- var/obj/item/gun/magic/I = item
- if(prob(80) && !I.can_charge)
- I.max_charges--
- if(I.max_charges <= 0)
- I.max_charges = 0
- burnt_out = TRUE
- I.charges = I.max_charges
- if(istype(item,/obj/item/gun/magic/wand) && I.max_charges != 0)
- var/obj/item/gun/magic/W = item
- W.icon_state = initial(W.icon_state)
- charged_item = I
- break
- else if(istype(item, /obj/item/stock_parts/cell/))
- var/obj/item/stock_parts/cell/C = item
- if(!C.self_recharge)
- if(prob(80))
- C.maxcharge -= 200
- if(C.maxcharge <= 1) //Div by 0 protection
- C.maxcharge = 1
- burnt_out = TRUE
- C.charge = C.maxcharge
- charged_item = C
- break
- else if(item.contents)
- var/obj/I = null
- for(I in item.contents)
- if(istype(I, /obj/item/stock_parts/cell/))
- var/obj/item/stock_parts/cell/C = I
- if(!C.self_recharge)
- if(prob(80))
- C.maxcharge -= 200
- if(C.maxcharge <= 1) //Div by 0 protection
- C.maxcharge = 1
- burnt_out = TRUE
- C.charge = C.maxcharge
- item.update_icon()
- charged_item = item
- break
- if(!charged_item)
- to_chat(L, "You feel magical power surging to your hands, but the feeling rapidly fades...")
- else if(burnt_out)
- to_chat(L, "[charged_item] doesn't seem to be reacting to the spell...")
- else
- to_chat(L, "[charged_item] suddenly feels very warm!")
diff --git a/code/datums/spells/chef.dm b/code/datums/spells/chef.dm
deleted file mode 100644
index 2e42e0b1be66c..0000000000000
--- a/code/datums/spells/chef.dm
+++ /dev/null
@@ -1,17 +0,0 @@
-/datum/spell/expert_chef
- name = "Expert Chef Knowledge"
- desc = "Find things you can cook with the items in reach."
- clothes_req = FALSE
- base_cooldown = 5 SECONDS
- human_req = TRUE
- antimagic_flags = NONE
- action_icon_state = "chef"
- action_background_icon_state = "bg_default"
- still_recharging_msg = "All this thinking makes your head hurt, wait a bit longer."
-
-/datum/spell/expert_chef/cast(list/targets, mob/user = usr)
- var/mob/living/carbon/human/H = targets[1]
- H.expert_chef_knowledge()
-
-/datum/spell/expert_chef/create_new_targeting()
- return new /datum/spell_targeting/self
diff --git a/code/datums/spells/cluwne.dm b/code/datums/spells/cluwne.dm
deleted file mode 100644
index 68e48c108ebaf..0000000000000
--- a/code/datums/spells/cluwne.dm
+++ /dev/null
@@ -1,78 +0,0 @@
-/datum/spell/touch/cluwne
- name = "Curse of the Cluwne"
- desc = "Turns the target into a fat and cursed monstrosity of a clown."
- hand_path = /obj/item/melee/touch_attack/cluwne
-
- base_cooldown = 1 MINUTES
- clothes_req = TRUE
- cooldown_min = 200 //100 deciseconds reduction per rank
-
- action_icon_state = "cluwne"
-
-/mob/living/carbon/human/proc/makeCluwne()
- if(istype(back, /obj/item/mod/control)) // Check if the target is wearing a modsuit
- var/obj/item/mod/control/modsuit_control = back
- if(istype(wear_suit, /obj/item/clothing/suit/mod)) // Check if the modsuit is deployed
- modsuit_control.active = FALSE // Instantly deactivate the modsuit - if it was activated
- modsuit_control.quick_deploy(src) // The modsuit is no longer deployed
- to_chat(src, "You feel funny.")
- if(!get_int_organ(/obj/item/organ/internal/brain/cluwne))
- var/obj/item/organ/internal/brain/cluwne/idiot_brain = new
- idiot_brain.insert(src, make_cluwne = 0)
- idiot_brain.dna = dna.Clone()
- setBrainLoss(80, use_brain_mod = FALSE)
- set_nutrition(9000)
- overeatduration = 9000
- Confused(60 SECONDS)
- if(mind)
- mind.assigned_role = "Cluwne"
-
-
- dna.SetSEState(GLOB.nervousblock, 1, 1)
- singlemutcheck(src, GLOB.nervousblock, MUTCHK_FORCED)
- rename_character(real_name, "cluwne")
-
- drop_item_to_ground(w_uniform, force = TRUE)
- drop_item_to_ground(shoes, force = TRUE)
- drop_item_to_ground(gloves, force = TRUE)
- var/obj/item/organ/internal/honktumor/cursed/tumor = new
- tumor.insert(src)
- if(!istype(wear_mask, /obj/item/clothing/mask/cursedclown)) //Infinite loops otherwise
- drop_item_to_ground(wear_mask, force = TRUE)
- equip_to_slot_if_possible(new /obj/item/clothing/under/cursedclown, ITEM_SLOT_JUMPSUIT, TRUE, TRUE)
- equip_to_slot_if_possible(new /obj/item/clothing/gloves/cursedclown, ITEM_SLOT_GLOVES, TRUE, TRUE)
- equip_to_slot_if_possible(new /obj/item/clothing/mask/cursedclown, ITEM_SLOT_MASK, TRUE, TRUE)
- equip_to_slot_if_possible(new /obj/item/clothing/shoes/cursedclown, ITEM_SLOT_SHOES, TRUE, TRUE)
-
-/mob/living/carbon/human/proc/makeAntiCluwne()
- to_chat(src, "You don't feel very funny.")
- adjustBrainLoss(-120)
- set_nutrition(NUTRITION_LEVEL_STARVING)
- overeatduration = 0
- SetConfused(0)
- SetJitter(0)
- if(mind)
- mind.assigned_role = "Internal Affairs Agent"
-
- var/obj/item/organ/internal/honktumor/cursed/tumor = get_int_organ(/obj/item/organ/internal/honktumor/cursed)
- if(tumor)
- tumor.remove(src)
- else
- dna.SetSEState(GLOB.clumsyblock, FALSE)
- dna.SetSEState(GLOB.comicblock, FALSE)
- singlemutcheck(src, GLOB.clumsyblock, MUTCHK_FORCED)
- singlemutcheck(src, GLOB.comicblock, MUTCHK_FORCED)
- dna.SetSEState(GLOB.nervousblock, FALSE)
- singlemutcheck(src, GLOB.nervousblock, MUTCHK_FORCED)
-
- qdel(w_uniform)
- qdel(shoes)
-
- if(istype(wear_mask, /obj/item/clothing/mask/cursedclown))
- qdel(wear_mask)
-
- if(istype(gloves, /obj/item/clothing/gloves/cursedclown))
- qdel(gloves)
-
- equip_to_slot_if_possible(new /obj/item/clothing/under/rank/procedure/iaa/formal/black, ITEM_SLOT_JUMPSUIT, TRUE, TRUE)
- equip_to_slot_if_possible(new /obj/item/clothing/shoes/black, ITEM_SLOT_SHOES, TRUE, TRUE)
diff --git a/code/datums/spells/conjure.dm b/code/datums/spells/conjure.dm
deleted file mode 100644
index c0bef3f0dcd2b..0000000000000
--- a/code/datums/spells/conjure.dm
+++ /dev/null
@@ -1,66 +0,0 @@
-/datum/spell/aoe/conjure
- desc = "This spell conjures objs of the specified types in range."
-
- var/list/summon_type = list() //determines what exactly will be summoned
- //should be text, like list("/mob/simple_animal/bot/ed209")
-
- var/summon_lifespan = 0 // 0=permanent, any other time in deciseconds
- var/summon_amt = 1 //amount of objects summoned
- var/summon_ignore_density = 0 //if set to 1, adds dense tiles to possible spawn places
- var/summon_ignore_prev_spawn_points = 0 //if set to 1, each new object is summoned on a new spawn point
-
- var/list/newVars = list() //vars of the summoned objects will be replaced with those where they meet
- //should have format of list("emagged" = 1,"name" = "Wizard's Justicebot"), for example
- var/delay = 1//Go Go Gadget Inheritance
-
- var/cast_sound = 'sound/items/welder.ogg'
-
-/datum/spell/aoe/conjure/create_new_targeting()
- var/datum/spell_targeting/aoe/turf/targeting = new()
- targeting.range = aoe_range
- return targeting
-
-/datum/spell/aoe/conjure/cast(list/targets,mob/living/user = usr)
- var/list/what_conjure_summoned = list()
- playsound(get_turf(user), cast_sound, 50, TRUE)
- for(var/turf/T in targets)
- if(T.density && !summon_ignore_density)
- targets -= T
- playsound(get_turf(src), cast_sound, 50, 1)
-
- if(delay <= 0 || do_after(user, delay, target = user))
- for(var/i=0,iYou start spinning in place and casting [src]...")
- if(do_after(H, 2 SECONDS, FALSE, H))
- finish_disguise(H)
- return TRUE
- else
- H.slip("your own foot", 1 SECONDS, 0, 0, 1, "trip")
- to_chat(H, "You must stand still to cast [src]!")
- return FALSE
-
-/datum/spell/disguise_self/proc/finish_disguise(mob/living/carbon/human/H)
- H.apply_status_effect(STATUS_EFFECT_MAGIC_DISGUISE)
- var/datum/effect_system/smoke_spread/smoke = new /datum/effect_system/smoke_spread
- smoke.set_up(4, FALSE, H.loc)
- smoke.start()
diff --git a/code/datums/spells/ethereal_jaunt.dm b/code/datums/spells/ethereal_jaunt.dm
deleted file mode 100644
index a6b1432969079..0000000000000
--- a/code/datums/spells/ethereal_jaunt.dm
+++ /dev/null
@@ -1,156 +0,0 @@
-/datum/spell/ethereal_jaunt
- name = "Ethereal Jaunt"
- desc = "This spell creates your ethereal form, temporarily making you invisible and able to pass through walls."
-
- base_cooldown = 300
- clothes_req = TRUE
- invocation = "none"
- invocation_type = "none"
- cooldown_min = 100 //50 deciseconds reduction per rank
- nonabstract_req = TRUE
- centcom_cancast = FALSE //Prevent people from getting to centcom
- var/sound1 = 'sound/magic/ethereal_enter.ogg'
- var/jaunt_duration = 50 //in deciseconds
- var/jaunt_in_time = 5
- var/jaunt_in_type = /obj/effect/temp_visual/wizard
- var/jaunt_out_type = /obj/effect/temp_visual/wizard/out
- var/jaunt_type_path = /obj/effect/dummy/spell_jaunt
- var/jaunt_water_effect = TRUE
-
- action_icon_state = "jaunt"
-
-/datum/spell/ethereal_jaunt/create_new_targeting()
- return new /datum/spell_targeting/self
-
-/datum/spell/ethereal_jaunt/cast(list/targets, mob/user = usr) //magnets, so mostly hardcoded
- playsound(get_turf(user), sound1, 50, TRUE, -1)
- for(var/mob/living/target in targets)
- if(SEND_SIGNAL(target, COMSIG_MOB_PRE_JAUNT, target) & COMPONENT_BLOCK_JAUNT)
- continue
- if(!target.can_safely_leave_loc()) // No more brainmobs hopping out of their brains
- to_chat(target, "You are somehow too bound to your current location to abandon it.")
- continue
- INVOKE_ASYNC(src, PROC_REF(do_jaunt), target)
-
-/datum/spell/ethereal_jaunt/proc/do_jaunt(mob/living/target)
- target.notransform = TRUE
- var/turf/mobloc = get_turf(target)
- var/obj/effect/dummy/spell_jaunt/holder = new jaunt_type_path(mobloc)
- new jaunt_out_type(mobloc, target.dir)
- target.ExtinguishMob()
- target.forceMove(holder)
- target.reset_perspective(holder)
- target.notransform = FALSE //mob is safely inside holder now, no need for protection.
- if(jaunt_water_effect)
- jaunt_steam(mobloc)
-
- sleep(jaunt_duration)
-
- if(target.loc != holder) //mob warped out of the warp
- qdel(holder)
- return
- mobloc = get_turf(target.loc)
- if(jaunt_water_effect)
- jaunt_steam(mobloc)
- ADD_TRAIT(target, TRAIT_IMMOBILIZED, "jaunt")
- holder.reappearing = 1
- playsound(get_turf(target), 'sound/magic/ethereal_exit.ogg', 50, TRUE, -1)
- sleep(jaunt_in_time * 4)
- new jaunt_in_type(mobloc, holder.dir)
- target.setDir(holder.dir)
- sleep(jaunt_in_time)
- qdel(holder)
- if(!QDELETED(target))
- if(is_blocked_turf(mobloc, TRUE))
- for(var/turf/T in orange(7))
- if(isspaceturf(T))
- continue
- if(target.Move(T))
- target.remove_CC()
- REMOVE_TRAIT(target, TRAIT_IMMOBILIZED, "jaunt")
- return
- for(var/turf/space/S in orange(7))
- if(target.Move(S))
- break
- REMOVE_TRAIT(target, TRAIT_IMMOBILIZED, "jaunt")
- target.remove_CC()
-
-/datum/spell/ethereal_jaunt/proc/jaunt_steam(mobloc)
- var/datum/effect_system/steam_spread/steam = new /datum/effect_system/steam_spread()
- steam.set_up(10, 0, mobloc)
- steam.start()
-
-/obj/effect/dummy/spell_jaunt
- name = "water"
- icon = 'icons/effects/effects.dmi'
- icon_state = "nothing"
- var/reappearing = 0
- var/movedelay = 0
- var/movespeed = 2
- density = FALSE
- anchored = TRUE
- invisibility = 60
- resistance_flags = LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF
-
-/obj/effect/dummy/spell_jaunt/Destroy()
- // Eject contents if deleted somehow
- for(var/atom/movable/AM in src)
- AM.forceMove(get_turf(src))
- return ..()
-
-/obj/effect/dummy/spell_jaunt/relaymove(mob/user, direction)
- if((movedelay > world.time) || reappearing || !direction)
- return
- var/turf/newLoc = get_step(src,direction)
- setDir(direction)
- movedelay = world.time + movespeed
- if(can_move(newLoc))
- forceMove(newLoc)
- return
- if(!IS_DIR_DIAGONAL(direction))
- to_chat(user, "Something is blocking the way!")
- return
- var/turf/possible_1 = get_step(src, turn(direction, 45))
- var/turf/possible_2 = get_step(src, turn(direction, -45))
- if(can_move(possible_1))
- forceMove(possible_1)
- return
- if(can_move(possible_2))
- forceMove(possible_2)
- return
- to_chat(user, "Something is blocking the way!")
-
-/obj/effect/dummy/spell_jaunt/proc/can_move(turf/T)
- return TRUE
-
-/obj/effect/dummy/spell_jaunt/ex_act(blah)
- return
-
-/obj/effect/dummy/spell_jaunt/bullet_act(blah)
- return
-
-/obj/effect/dummy/spell_jaunt/blood_pool
- name = "sanguine pool"
- desc = "a pool of living blood."
- movespeed = 1.5
-
-/obj/effect/dummy/spell_jaunt/blood_pool/relaymove(mob/user, direction)
- ..()
- new /obj/effect/decal/cleanable/blood(loc)
-
-
-/obj/effect/dummy/spell_jaunt/blood_pool/can_move(turf/T)
- if(isspaceturf(T) || T.density)
- return FALSE
- return TRUE
-
-/obj/effect/dummy/spell_jaunt/wraith
-
-/obj/effect/dummy/spell_jaunt/wraith/can_move(turf/T)
- if(!issimulatedturf(T))
- return TRUE
-
- var/turf/simulated/turf_to_move = T
- if(turf_to_move.flags & BLESSED_TILE)
- return FALSE
- return TRUE
diff --git a/code/datums/spells/fake_gib.dm b/code/datums/spells/fake_gib.dm
deleted file mode 100644
index 66c2597b5f64a..0000000000000
--- a/code/datums/spells/fake_gib.dm
+++ /dev/null
@@ -1,10 +0,0 @@
-/datum/spell/touch/fake_disintegrate
- name = "Disintegrate"
- desc = "This spell charges your hand with vile energy that can be used to violently explode victims."
- hand_path = "/obj/item/melee/touch_attack/fake_disintegrate"
-
- base_cooldown = 600
- clothes_req = FALSE
- cooldown_min = 200 //100 deciseconds reduction per rank
-
- action_icon_state = "gib"
diff --git a/code/datums/spells/horsemask.dm b/code/datums/spells/horsemask.dm
deleted file mode 100644
index 4e5e5e26ca121..0000000000000
--- a/code/datums/spells/horsemask.dm
+++ /dev/null
@@ -1,47 +0,0 @@
-/datum/spell/horsemask
- name = "Curse of the Horseman"
- desc = "This spell triggers a curse on a target, causing them to wield an unremovable horse head mask. They will speak like a horse! Any masks they are wearing will be disintegrated. This spell does not require robes."
- base_cooldown = 150
- clothes_req = FALSE
- stat_allowed = CONSCIOUS
- invocation = "KN'A FTAGHU, PUCK 'BTHNK!"
- invocation_type = "shout"
- cooldown_min = 30 //30 deciseconds reduction per rank
-
- selection_activated_message = "You start to quietly neigh an incantation. Click on or near a target to cast the spell."
- selection_deactivated_message = "You stop neighing to yourself."
-
- action_icon_state = "barn"
- sound = 'sound/magic/HorseHead_curse.ogg'
-
-/datum/spell/horsemask/create_new_targeting()
- var/datum/spell_targeting/click/T = new()
- T.selection_type = SPELL_SELECTION_RANGE
- return T
-
-
-/datum/spell/horsemask/cast(list/targets, mob/user = usr)
- if(!length(targets))
- to_chat(user, "No target found in range.")
- return
-
- var/mob/living/carbon/human/target = targets[1]
-
- if(target.can_block_magic(antimagic_flags))
- target.visible_message("[target]'s face bursts into flames, which instantly burst outward, leaving [target.p_them()] unharmed!",
- "Your face starts burning up, but the flames are repulsed by your anti-magic protection!",
- )
- to_chat(user, "The spell had no effect!")
- return FALSE
-
- var/obj/item/clothing/mask/horsehead/magichead = new /obj/item/clothing/mask/horsehead
- magichead.flags |= NODROP | DROPDEL //curses!
- magichead.flags_inv = null //so you can still see their face
- magichead.voicechange = TRUE //NEEEEIIGHH
- target.visible_message( "[target]'s face lights up in fire, and after the event a horse's head takes its place!", \
- "Your face burns up, and shortly after the fire you realise you have the face of a horse!")
- if(!target.drop_item_to_ground(target.wear_mask))
- qdel(target.wear_mask)
- target.equip_to_slot_if_possible(magichead, ITEM_SLOT_MASK, TRUE, TRUE)
-
- target.flash_eyes()
diff --git a/code/datums/spells/infinite_guns.dm b/code/datums/spells/infinite_guns.dm
deleted file mode 100644
index a7666232db5c3..0000000000000
--- a/code/datums/spells/infinite_guns.dm
+++ /dev/null
@@ -1,53 +0,0 @@
-/datum/spell/infinite_guns
- name = "Lesser Summon Guns"
- desc = "Why reload when you have infinite guns? Summons an unending stream of bolt action rifles. Requires both hands free to use."
- invocation_type = "none"
-
- base_cooldown = 600
- clothes_req = TRUE
- cooldown_min = 10 //Gun wizard
- action_icon_state = "bolt_action"
- var/gun_type = /obj/item/gun/projectile/shotgun/boltaction/enchanted
-
-/datum/spell/infinite_guns/create_new_targeting()
- return new /datum/spell_targeting/self
-
-/datum/spell/infinite_guns/cast(list/targets, mob/user = usr)
- for(var/mob/living/carbon/C in targets)
- C.drop_item()
- C.swap_hand()
- C.drop_item()
- var/obj/item/gun/projectile/shotgun/boltaction/enchanted/GUN = new gun_type()
- C.put_in_hands(GUN)
-
-/datum/spell/infinite_guns/fireball
- name = "Rapid-fire Fireball"
- desc = "Multiple Fireballs. Need I explain more? Requires both hands free to use."
-
- base_cooldown = 30 SECONDS
- clothes_req = FALSE
- invocation = "ONI SOMA-SOMA-SOMA"
- invocation_type = "shout"
- action_icon_state = "explosion"
- gun_type = /obj/item/gun/projectile/shotgun/boltaction/enchanted/arcane_barrage/fireball
-
-/obj/item/gun/projectile/shotgun/boltaction/enchanted/arcane_barrage/fireball
- name = "small ball of fire"
- desc = "A small flame, ready to launch from your hand."
- icon = 'icons/obj/cigarettes.dmi'
- icon_state = "match_unathi"
- item_state = "disintegrate"
- lefthand_file = 'icons/mob/inhands/items_lefthand.dmi'
- righthand_file = 'icons/mob/inhands/items_righthand.dmi'
- color = "#e1ff00" // red + yellow = orange
- guns_left = 20
- fire_sound = 'sound/magic/Fireball.ogg'
- mag_type = /obj/item/ammo_box/magazine/internal/boltaction/enchanted/arcane_barrage/fireball
- flags = NOBLUDGEON | DROPDEL
-
-/obj/item/ammo_box/magazine/internal/boltaction/enchanted/arcane_barrage/fireball
- ammo_type = /obj/item/ammo_casing/magic/arcane_barrage/fireball
-
-/obj/item/ammo_casing/magic/arcane_barrage/fireball
- projectile_type = /obj/item/projectile/magic/fireball
- muzzle_flash_effect = null
diff --git a/code/datums/spells/knock.dm b/code/datums/spells/knock.dm
deleted file mode 100644
index 816c529a0de6e..0000000000000
--- a/code/datums/spells/knock.dm
+++ /dev/null
@@ -1,67 +0,0 @@
-/datum/spell/aoe/knock
- name = "Knock"
- desc = "This spell opens nearby doors and does not require wizard garb."
-
- base_cooldown = 100
- clothes_req = FALSE
- invocation = "AULIE OXIN FIERA"
- invocation_type = "whisper"
- cooldown_min = 20 //20 deciseconds reduction per rank
-
- action_icon_state = "knock"
- sound = 'sound/magic/knock.ogg'
- aoe_range = 3
-
-/datum/spell/aoe/knock/create_new_targeting()
- var/datum/spell_targeting/aoe/turf/targeting = new()
- targeting.range = aoe_range
- return targeting
-
-/datum/spell/aoe/knock/cast(list/targets, mob/user = usr)
- for(var/turf/T in targets)
- for(var/obj/machinery/door/door in T.contents)
- INVOKE_ASYNC(src, PROC_REF(try_open_airlock), door)
- for(var/obj/structure/closet/C in T.contents)
- INVOKE_ASYNC(src, PROC_REF(try_open_closet), C)
-
-/datum/spell/aoe/knock/proc/try_open_airlock(obj/machinery/door/door)
- if(istype(door, /obj/machinery/door/airlock))
- var/obj/machinery/door/airlock/A = door
- A.unlock(TRUE) //forced because it's magic!
- door.open()
-
-/datum/spell/aoe/knock/proc/try_open_closet(obj/structure/closet/C)
- if(istype(C, /obj/structure/closet/secure_closet))
- var/obj/structure/closet/secure_closet/SC = C
- SC.locked = FALSE
- C.open()
-
-/datum/spell/aoe/knock/greater
- name = "Greater Knock"
- desc = "On first cast, will remove access restrictions on all airlocks on the station, and announce this spell's use to the station. On any further cast, will open all doors in sight. Cannot be refunded once bought!"
-
- base_cooldown = 200
- invocation = "MAIOR OXIN FIERA"
- invocation_type = "shout"
- level_max = 0 //Cannot be improved, quality of life since can't be refunded
- cooldown_min = 200
- var/used = FALSE
-
- action_icon_state = "greater_knock"
-
-/datum/spell/aoe/knock/greater/cast(list/targets, mob/user = usr)
- if(!used)
- used = TRUE
- for(var/obj/machinery/door/airlock/A in GLOB.airlocks)
- if(is_station_level(A.z))
- A.req_access = list()
- A.req_one_access = list()
- GLOB.major_announcement.Announce(
- message = "We have removed all access requirements on your station's airlocks. You can thank us later!",
- new_title = "Space Wizard Federation Message",
- new_subtitle = "Greetings!",
- new_sound = 'sound/misc/notice2.ogg'
- )
- else
- ..()
- return
diff --git a/code/datums/spells/lichdom.dm b/code/datums/spells/lichdom.dm
deleted file mode 100644
index 4df3537367fb7..0000000000000
--- a/code/datums/spells/lichdom.dm
+++ /dev/null
@@ -1,143 +0,0 @@
-/datum/spell/lichdom
- name = "Bind Soul"
- desc = "A dark necromantic pact that can forever bind your soul to an item of your choosing. So long as both your body and the item remain intact and on the same plane you can revive from death, though the time between reincarnations grows steadily with use."
- base_cooldown = 10
- clothes_req = FALSE
- centcom_cancast = FALSE
- invocation = "NECREM IMORTIUM!"
- invocation_type = "shout"
- level_max = 0 //cannot be improved
- cooldown_min = 10
-
- var/marked_item_uid
- var/mob/living/current_body
- var/resurrections = 0
-
- action_icon_state = "skeleton"
-
-/datum/spell/lichdom/create_new_targeting()
- return new /datum/spell_targeting/self
-
-/datum/spell/lichdom/cast(list/targets, mob/user = usr)
- for(var/mob/M in targets)
- if(stat_allowed)
- attempt_revive(M)
- else if(!marked_item_uid)
- attempt_mark_item(M)
-
-/datum/spell/lichdom/proc/attempt_revive(mob/user)
- // Can only cast when unconscious/dead
- if(user.stat == CONSCIOUS)
- to_chat(user, "You aren't dead enough to revive!")
- cooldown_handler.revert_cast()
- return
-
- // Body was destroyed
- if(QDELETED(current_body))
- to_chat(user, "Your body is gone!")
- return
-
- // Phylactery was destroyed
- var/obj/item/marked_item = locateUID(marked_item_uid)
- if(QDELETED(marked_item))
- to_chat(user, "Your phylactery is gone!")
- return
-
- // Wrong z-level
- var/turf/body_turf = get_turf(current_body)
- var/turf/item_turf = get_turf(marked_item)
- if(body_turf.z != item_turf.z)
- to_chat(user, "Your phylactery is out of range!")
- return
-
- if(isobserver(user))
- var/mob/dead/observer/O = user
- O.reenter_corpse()
-
- // Clean up the old body
- if(!QDELETED(current_body))
- if(iscarbon(current_body))
- var/mob/living/carbon/C = current_body
- for(var/obj/item/W in C)
- C.drop_item_to_ground(W)
-
- // Give a hint as to where the body is
- var/wheres_wizdo = dir2text(get_dir(body_turf, item_turf))
- if(wheres_wizdo)
- current_body.visible_message("Suddenly [current_body.name]'s corpse falls to pieces! You see a strange energy rise from the remains, and speed off towards the [wheres_wizdo]!")
- body_turf.Beam(item_turf, icon_state = "lichbeam", icon = 'icons/effects/effects.dmi', time = 10 + 10 * resurrections, maxdistance = INFINITY)
-
- UnregisterSignal(current_body, list(COMSIG_PARENT_QDELETING, COMSIG_MOVABLE_Z_CHANGED))
- current_body.dust()
-
- var/stun_time = (1 + resurrections++) * 20 SECONDS
-
- var/mob/living/carbon/human/lich = new /mob/living/carbon/human(item_turf)
- lich.set_species(/datum/species/skeleton/lich)
- lich.real_name = user.mind.name
- lich.Weaken(stun_time)
- user.mind.transfer_to(lich)
- equip_lich(lich)
-
- current_body = lich
- cooldown_handler.recharge_duration += 1 MINUTES
- to_chat(lich, "Your bones clatter and shudder as they're pulled back into this world!")
-
-/datum/spell/lichdom/proc/attempt_mark_item(mob/user)
- var/obj/item/target = user.get_active_hand()
- if(!target)
- to_chat(user, "You must hold an item you wish to make your phylactery!")
- return
-
- if(target.flags & (ABSTRACT|NODROP))
- to_chat(user, "[target] cannot be used as your phylactery!")
- return
-
- if(!do_after(user, 5 SECONDS, target = target))
- to_chat(user, "Your soul snaps back to your body as you drop [target]!")
- return
-
- name = "RISE!"
- desc = "Rise from the dead! You will reform at the location of your phylactery and your old body will crumble away."
- stat_allowed = UNCONSCIOUS
- cooldown_handler.recharge_duration = 3 MINUTES
- cooldown_handler.revert_cast()
- if(action)
- action.name = name
- action.desc = desc
- UpdateButtons()
-
- target.name = "ensouled [target.name]"
- target.desc += " A terrible aura surrounds this item, its very existence is offensive to life itself..."
- target.color = "#003300"
- marked_item_uid = target.UID()
-
- current_body = user.mind.current
- if(ishuman(user))
- var/mob/living/carbon/human/H = user
- H.set_species(/datum/species/skeleton/lich)
- H.drop_item_to_ground(H.wear_suit)
- H.drop_item_to_ground(H.head)
- H.drop_item_to_ground(H.shoes)
- H.drop_item_to_ground(H.head)
- equip_lich(H)
-
- to_chat(user, "With a hideous feeling of emptiness you watch in horrified fascination as skin sloughs off bone! Blood boils, nerves disintegrate, eyes boil in their sockets! As your organs crumble to dust in your fleshless chest you come to terms with your choice. You're a lich!")
-
-/datum/spell/lichdom/proc/is_revive_possible()
- var/obj/item/marked_item = locateUID(marked_item_uid)
- if(QDELETED(marked_item))
- return FALSE
- if(QDELETED(current_body))
- return FALSE
- var/turf/body_turf = get_turf(current_body)
- var/turf/item_turf = get_turf(marked_item)
- if(body_turf.z != item_turf.z)
- return FALSE
- return TRUE
-
-/datum/spell/lichdom/proc/equip_lich(mob/living/carbon/human/H)
- H.equip_to_slot_or_del(new /obj/item/clothing/suit/wizrobe/black(H), ITEM_SLOT_OUTER_SUIT)
- H.equip_to_slot_or_del(new /obj/item/clothing/head/wizard/black(H), ITEM_SLOT_HEAD)
- H.equip_to_slot_or_del(new /obj/item/clothing/shoes/sandal(H), ITEM_SLOT_SHOES)
- H.equip_to_slot_or_del(new /obj/item/clothing/under/color/black(H), ITEM_SLOT_JUMPSUIT)
diff --git a/code/datums/spells/lightning.dm b/code/datums/spells/lightning.dm
deleted file mode 100644
index c7e79e2e103d4..0000000000000
--- a/code/datums/spells/lightning.dm
+++ /dev/null
@@ -1,53 +0,0 @@
-/datum/spell/charge_up/bounce/lightning
- name = "Lightning Bolt"
- desc = "Throws a lightning bolt at your enemies. Classic. When clicked will start to charge in power. Then click on a mob to send the bolt before it overloads with power."
- base_cooldown = 30 SECONDS
- clothes_req = TRUE
- invocation = "UN'LTD P'WAH!"
- invocation_type = "shout"
- cooldown_min = 3 SECONDS
- action_icon_state = "lightning"
- charge_sound = new /sound('sound/magic/lightning_chargeup.ogg', channel = 7)
- max_charge_time = 10 SECONDS
- stop_charging_text = "You stop charging the lightning around you."
- stop_charging_fail_text = "The lightning around you is too strong to stop now!"
- start_charging_text = "You start gathering lightning around you."
- bounce_hit_sound = 'sound/magic/lightningshock.ogg'
- var/damaging = TRUE
-
-/datum/spell/charge_up/bounce/lightning/New()
- ..()
- charge_up_overlay = image(icon = 'icons/effects/effects.dmi', icon_state = "electricity", layer = EFFECTS_LAYER)
-
-/datum/spell/charge_up/bounce/lightning/lightnian
- clothes_req = FALSE
- invocation_type = "none"
- damaging = FALSE
-
-/datum/spell/charge_up/bounce/lightning/get_bounce_energy()
- if(damaging)
- return max(15, get_energy_charge() / 2)
- return 0
-
-/datum/spell/charge_up/bounce/lightning/get_bounce_amount()
- if(damaging)
- return 5
- return round(get_energy_charge() / 20)
-
-/datum/spell/charge_up/bounce/lightning/create_beam(mob/origin, mob/target)
- origin.Beam(target, icon_state = "lightning[rand(1, 12)]", icon = 'icons/effects/effects.dmi', time = 5)
-
-/datum/spell/charge_up/bounce/lightning/apply_bounce_effect(mob/origin, mob/living/target, energy, mob/user)
- if(target.can_block_magic(antimagic_flags))
- target.visible_message(
- "[target] absorbs the spell, remaining unharmed!",
- "You absorb the spell, remaining unharmed!"
- )
- return
- if(damaging)
- target.electrocute_act(energy, "Lightning Bolt", flags = SHOCK_NOGLOVES)
- else
- target.AdjustJitter(2000 SECONDS) //High numbers for violent convulsions
- target.AdjustStuttering(4 SECONDS)
- target.Slowed(6 SECONDS)
- addtimer(CALLBACK(target, TYPE_PROC_REF(/mob/living, AdjustJitter), -2000 SECONDS, 10), 2 SECONDS) //Still jittery, but vastly less
diff --git a/code/datums/spells/magnet.dm b/code/datums/spells/magnet.dm
deleted file mode 100644
index 55b03e5c7b1e9..0000000000000
--- a/code/datums/spells/magnet.dm
+++ /dev/null
@@ -1,56 +0,0 @@
-/datum/spell/charge_up/bounce/magnet
- name = "Magnetic Pull"
- desc = "Pulls metalic objects from enemies hands with the power of MAGNETS."
- action_icon_state = "magnet"
- base_cooldown = 30 SECONDS
- clothes_req = FALSE
- invocation = "UN'LTD P'WAH!"
- invocation_type = "none"
- cooldown_min = 3 SECONDS
- charge_sound = new /sound('sound/magic/lightning_chargeup.ogg', channel = 7)
- max_charge_time = 10 SECONDS
- stop_charging_text = "You stop charging the magnetism around you."
- stop_charging_fail_text = "The magnetism around you is too strong to stop now!"
- start_charging_text = "You start gathering magnetism around you."
- bounce_hit_sound = 'sound/machines/defib_zap.ogg'
-
-/datum/spell/charge_up/bounce/magnet/New()
- ..()
- charge_up_overlay = image(icon = 'icons/effects/effects.dmi', icon_state = "electricity", layer = EFFECTS_LAYER)
-
-/datum/spell/charge_up/bounce/magnet/get_bounce_energy()
- return get_energy_charge()
-
-/datum/spell/charge_up/bounce/magnet/get_bounce_amount()
- if(get_energy_charge() >= 75)
- return 5
- return 0
-
-/datum/spell/charge_up/bounce/magnet/create_beam(mob/origin, mob/target)
- origin.Beam(target, icon_state = "lightning[rand(1, 12)]", icon = 'icons/effects/effects.dmi', time = 5)
-
-/datum/spell/charge_up/bounce/magnet/apply_bounce_effect(mob/origin, mob/target, energy, mob/user)
- var/list/items_to_throw = list()
- switch(energy)
- if(0 to 25)
- if(prob(50))
- if(target.l_hand)
- items_to_throw += target.l_hand
- else
- if(target.r_hand)
- items_to_throw += target.r_hand
- if(25 to INFINITY)
- if(target.r_hand)
- items_to_throw += target.r_hand
- if(target.l_hand)
- items_to_throw += target.l_hand
- for(var/I in items_to_throw)
- try_throw_object(user, target, I)
-
-/datum/spell/charge_up/bounce/magnet/proc/try_throw_object(mob/user, mob/thrower, obj/item/to_throw)
- if(!(to_throw.flags & CONDUCT) || !thrower.drop_item_to_ground(to_throw, silent = TRUE))
- return FALSE
- thrower.visible_message("[to_throw] gets thrown out of [thrower] [thrower.p_their()] hands!",
- "[to_throw] suddenly gets thrown out of your hands!")
- to_throw.throw_at(user, to_throw.throw_range, 4)
- return TRUE
diff --git a/code/datums/spells/mime.dm b/code/datums/spells/mime.dm
deleted file mode 100644
index eda8b98224193..0000000000000
--- a/code/datums/spells/mime.dm
+++ /dev/null
@@ -1,171 +0,0 @@
-/datum/spell/aoe/conjure/build/mime_wall
- name = "Invisible Wall"
- desc = "The mime's performance transmutates into physical reality."
- summon_type = list(/obj/structure/forcefield/mime)
- invocation_type = "emote"
- invocation_emote_self = "You form a wall in front of yourself."
- summon_lifespan = 20 SECONDS
- base_cooldown = 30 SECONDS
- clothes_req = FALSE
- cast_sound = null
- human_req = TRUE
- antimagic_flags = NONE
-
- action_icon_state = "mime"
- action_background_icon_state = "bg_mime"
-
-/datum/spell/aoe/conjure/build/mime_wall/Click()
- if(usr && usr.mind)
- if(!usr.mind.miming)
- to_chat(usr, "You must dedicate yourself to silence first.")
- return
- invocation = "[usr.name] looks as if a wall is in front of [usr.p_them()]."
- else
- invocation_type ="none"
- ..()
-
-/datum/spell/mime/create_new_targeting()
- return new /datum/spell_targeting/self
-
-/datum/spell/mime/speak
- name = "Speech"
- desc = "Make or break a vow of silence."
- clothes_req = FALSE
- base_cooldown = 5 MINUTES
- human_req = TRUE
-
- action_icon_state = "mime_silence"
- action_background_icon_state = "bg_mime"
-
-/datum/spell/mime/speak/Click()
- if(!usr)
- return
- if(!ishuman(usr))
- return
- var/mob/living/carbon/human/H = usr
- if(H.mind.miming)
- still_recharging_msg = "You can't break your vow of silence that fast!"
- else
- still_recharging_msg = "You'll have to wait before you can give your vow of silence again!"
- ..()
-
-/datum/spell/mime/speak/cast(list/targets,mob/user = usr)
- for(var/mob/living/carbon/human/H in targets)
- H.mind.miming=!H.mind.miming
- if(H.mind.miming)
- to_chat(H, "You make a vow of silence.")
- else
- to_chat(H, "You break your vow of silence.")
-
-//Advanced Mimery traitor item spells
-
-/datum/spell/forcewall/mime
- name = "Invisible Greater Wall"
- desc = "Form an invisible three tile wide blockade."
- wall_type = /obj/effect/forcefield/mime
- invocation_type = "emote"
- invocation_emote_self = "You form a blockade in front of yourself."
- base_cooldown = 60 SECONDS
- sound = null
- clothes_req = FALSE
-
- action_icon_state = "mime_bigwall"
- action_background_icon_state = "bg_mime"
-
-/datum/spell/forcewall/mime/Click()
- if(usr && usr.mind)
- if(!usr.mind.miming)
- to_chat(usr, "You must dedicate yourself to silence first.")
- return
- invocation = "[usr.name] looks as if a blockade is in front of [usr.p_them()]."
- else
- invocation_type ="none"
- ..()
-
-/datum/spell/mime/fingergun
- name = "Finger Gun"
- desc = "Shoot lethal, silencing bullets out of your fingers! 3 bullets available per cast. Use your fingers to holster them manually."
- clothes_req = FALSE
- base_cooldown = 30 SECONDS
- human_req = TRUE
- antimagic_flags = NONE
-
- action_icon_state = "fingergun"
- action_background_icon_state = "bg_mime"
- var/gun = /obj/item/gun/projectile/revolver/fingergun
- var/obj/item/gun/projectile/revolver/fingergun/current_gun
-
-/datum/spell/mime/fingergun/cast(list/targets, mob/user = usr)
- for(var/mob/living/carbon/human/C in targets)
- if(!current_gun)
- to_chat(user, "You draw your fingers!")
- current_gun = new gun(get_turf(user), src)
- C.drop_item()
- C.put_in_hands(current_gun)
- RegisterSignal(C, COMSIG_MOB_WILLINGLY_DROP, PROC_REF(holster_hand))
- else
- holster_hand(user, TRUE)
- revert_cast(user)
-
-
-/datum/spell/mime/fingergun/Destroy()
- current_gun = null
- return ..()
-
-/datum/spell/mime/fingergun/proc/holster_hand(atom/target, any=FALSE)
- SIGNAL_HANDLER
- if(!current_gun || !any && action.owner.get_active_hand() != current_gun)
- return
- to_chat(action.owner, "You holster your fingers. Another time perhaps...")
- QDEL_NULL(current_gun)
-
-/datum/spell/mime/fingergun/fake
- desc = "Pretend you're shooting bullets out of your fingers! 3 bullets available per cast. Use your fingers to holster them manually."
- gun = /obj/item/gun/projectile/revolver/fingergun/fake
-
-// Mime Spellbooks
-
-/obj/item/spellbook/oneuse/mime
- spell = /datum/spell/aoe/conjure/build/mime_wall
- spellname = "Invisible Wall"
- name = "Miming Manual : "
- desc = "It contains various pictures of mimes mid-performance, aswell as some illustrated tutorials."
- icon_state = "bookmime"
-
-/obj/item/spellbook/oneuse/mime/attack_self__legacy__attackchain(mob/user)
- var/datum/spell/S = new spell
- for(var/datum/spell/knownspell in user.mind.spell_list)
- if(knownspell.type == S.type)
- if(user.mind)
- to_chat(user, "You've already read this one.")
- return
- if(used)
- recoil(user)
- else
- user.mind.AddSpell(S)
- to_chat(user, "You flip through the pages. Your understanding of the boundaries of reality increases. You can cast [spellname]!")
- user.create_log(MISC_LOG, "learned the spell [spellname] ([S])")
- user.create_attack_log("[key_name(user)] learned the spell [spellname] ([S]).")
- onlearned(user)
-
-/obj/item/spellbook/oneuse/mime/recoil(mob/user)
- to_chat(user, "You flip through the pages. Nothing of interest to you.")
-
-/obj/item/spellbook/oneuse/mime/onlearned(mob/user)
- used = TRUE
- if(!locate(/datum/spell/mime/speak) in user.mind.spell_list) //add vow of silence if not known by user
- user.mind.AddSpell(new /datum/spell/mime/speak)
- to_chat(user, "You have learned how to use silence to improve your performance.")
-
-/obj/item/spellbook/oneuse/mime/fingergun
- spell = /datum/spell/mime/fingergun
- spellname = "Finger Gun"
- desc = "It contains illustrations of guns and how to mime them."
-
-/obj/item/spellbook/oneuse/mime/fingergun/fake
- spell = /datum/spell/mime/fingergun/fake
-
-/obj/item/spellbook/oneuse/mime/greaterwall
- spell = /datum/spell/forcewall/mime
- spellname = "Invisible Greater Wall"
- desc = "It contains illustrations of the great walls of human history."
diff --git a/code/datums/spells/mime_malaise.dm b/code/datums/spells/mime_malaise.dm
deleted file mode 100644
index 48ea74fda890d..0000000000000
--- a/code/datums/spells/mime_malaise.dm
+++ /dev/null
@@ -1,54 +0,0 @@
-/datum/spell/touch/mime_malaise
- name = "Mime Malaise"
- desc = "A spell popular with theater nerd wizards and contrarian pranksters, this spell will put on a mime costume on the target, \
- stun them so that they may contemplate Art, and silence them. \
- Warning : Effects are permanent on non-wizards."
- hand_path = /obj/item/melee/touch_attack/mime_malaise
-
- base_cooldown = 300
- clothes_req = TRUE
- cooldown_min = 100 //50 deciseconds reduction per rank
- action_icon_state = "mime_curse"
-
-/obj/item/melee/touch_attack/mime_malaise
- name = "mime hand"
- desc = "..."
- catchphrase = null
- on_use_sound = null
- icon_state = "fleshtostone"
- item_state = "fleshtostone"
-
-/obj/item/melee/touch_attack/mime_malaise/after_attack(atom/target, mob/user, proximity_flag, click_parameters)
- . = ..()
-
- if(!proximity_flag || target == user || blocked_by_antimagic || !ishuman(target) || !iscarbon(user) || HAS_TRAIT(user, TRAIT_HANDS_BLOCKED))
- return
-
- var/datum/effect_system/smoke_spread/s = new
- s.set_up(5, FALSE, target)
- s.start()
-
- var/mob/living/carbon/human/H = target
- H.mimetouched()
- handle_delete(user)
-
-/mob/living/carbon/human/proc/mimetouched()
- Weaken(14 SECONDS)
- if(iswizard(src) || (mind && mind.special_role == SPECIAL_ROLE_WIZARD_APPRENTICE)) //Wizards get non-cursed mime outfit. Replace with mime robes if we add those.
- drop_item_to_ground(wear_mask, force = TRUE)
- drop_item_to_ground(w_uniform, force = TRUE)
- drop_item_to_ground(wear_suit, force = TRUE)
- equip_to_slot_if_possible(new /obj/item/clothing/mask/gas/mime, ITEM_SLOT_MASK, TRUE, TRUE)
- equip_to_slot_if_possible(new /obj/item/clothing/under/rank/civilian/mime, ITEM_SLOT_JUMPSUIT, TRUE, TRUE)
- equip_to_slot_if_possible(new /obj/item/clothing/suit/suspenders, ITEM_SLOT_OUTER_SUIT, TRUE, TRUE)
- Silence(14 SECONDS)
- else
- qdel(wear_mask)
- qdel(w_uniform)
- qdel(wear_suit)
- equip_to_slot_if_possible(new /obj/item/clothing/mask/gas/mime/nodrop, ITEM_SLOT_MASK, TRUE, TRUE)
- equip_to_slot_if_possible(new /obj/item/clothing/under/rank/civilian/mime/nodrop, ITEM_SLOT_JUMPSUIT, TRUE, TRUE)
- equip_to_slot_if_possible(new /obj/item/clothing/suit/suspenders/nodrop, ITEM_SLOT_OUTER_SUIT, TRUE, TRUE)
- dna.SetSEState(GLOB.muteblock , TRUE, TRUE)
- singlemutcheck(src, GLOB.muteblock, MUTCHK_FORCED)
- dna.default_blocks.Add(GLOB.muteblock)
diff --git a/code/datums/spells/mind_transfer.dm b/code/datums/spells/mind_transfer.dm
deleted file mode 100644
index 2e3bad262cf08..0000000000000
--- a/code/datums/spells/mind_transfer.dm
+++ /dev/null
@@ -1,89 +0,0 @@
-/datum/spell/mind_transfer
- name = "Mind Transfer"
- desc = "This spell allows the user to switch bodies with a target."
-
- base_cooldown = 600
- clothes_req = FALSE
- invocation = "GIN'YU CAPAN"
- invocation_type = "none" // shh
- selection_activated_message = "You prepare to transfer your mind. Click on a target to cast the spell."
- selection_deactivated_message = "You decide that your current form is good enough."
- cooldown_min = 200 //100 deciseconds reduction per rank
- antimagic_flags = MAGIC_RESISTANCE|MAGIC_RESISTANCE_MIND
- var/list/protected_roles = list(SPECIAL_ROLE_WIZARD, SPECIAL_ROLE_CHANGELING, SPECIAL_ROLE_CULTIST) //which roles are immune to the spell
- var/paralysis_amount_caster = 40 SECONDS //how much the caster is paralysed for after the spell
- var/paralysis_amount_victim = 40 SECONDS //how much the victim is paralysed for after the spell
- action_icon_state = "mindswap"
- sound = 'sound/magic/mindswap.ogg'
-
-/datum/spell/mind_transfer/create_new_targeting()
- var/datum/spell_targeting/click/T = new()
- T.allowed_type = /mob/living
- T.range = 3
- T.click_radius = 0
- // you can use it on yourself as a fake-out
- T.include_user = TRUE
- return T
-
-/datum/spell/mind_transfer/valid_target(mob/living/target, mob/user)
- return target.stat != DEAD && target.key && target.mind
-
-/*
-Urist: I don't feel like figuring out how you store object spells so I'm leaving this for you to do.
-Make sure spells that are removed from spell_list are actually removed and deleted when mind transfering.
-Also, you never added distance checking after target is selected. I've went ahead and did that.
-*/
-/datum/spell/mind_transfer/cast(list/targets, mob/user = usr)
-
- var/mob/living/target = targets[1]
-
- if(user.suiciding)
- to_chat(user, "You're killing yourself! You can't concentrate enough to do this!")
- return
-
- if((target.mind.special_role in protected_roles) && target != user)
- to_chat(user, "Their mind is resisting your spell.")
- return
-
- if(issilicon(target))
- to_chat(user, "You feel this enslaved being is just as dead as its cold, hard exoskeleton.")
- return
- if(target.can_block_magic(antimagic_flags))
- to_chat(user, "Their mind is resisting your spell.")
- return
-
- var/mob/living/victim = target//The target of the spell whos body will be transferred to.
- var/mob/living/caster = user//The wizard/whomever doing the body transferring.
-
- if(victim != caster)
- //MIND TRANSFER BEGIN
- if(length(caster.mind.special_verbs))//If the caster had any special verbs, remove them from the mob verb list.
- for(var/V in caster.mind.special_verbs)//Since the caster is using an object spell system, this is mostly moot.
- remove_verb(caster, V) //But a safety nontheless.
-
- if(length(victim.mind.special_verbs))//Now remove all of the victim's verbs.
- for(var/V in victim.mind.special_verbs)
- remove_verb(victim, V)
-
- var/mob/dead/observer/ghost = victim.ghostize(0)
- caster.mind.transfer_to(victim)
-
- if(length(victim.mind.special_verbs))//To add all the special verbs for the original caster.
- for(var/V in caster.mind.special_verbs)//Not too important but could come into play.
- add_verb(caster, V)
-
- ghost.mind.transfer_to(caster)
- if(ghost.key)
- GLOB.non_respawnable_keys -= ghost.ckey
- caster.key = ghost.key //have to transfer the key since the mind was not active
- qdel(ghost)
-
- if(length(caster.mind.special_verbs))//If they had any special verbs, we add them here.
- for(var/V in caster.mind.special_verbs)
- add_verb(caster, V)
- //MIND TRANSFER END
-
- for(var/mob/new_or_formal_wizard in list(victim, caster))
- for(var/mob/living/L in range(3, new_or_formal_wizard))
- L.SetSilence(15 SECONDS)
-
diff --git a/code/datums/spells/night_vision.dm b/code/datums/spells/night_vision.dm
deleted file mode 100644
index ae5c94f97610e..0000000000000
--- a/code/datums/spells/night_vision.dm
+++ /dev/null
@@ -1,29 +0,0 @@
-/datum/spell/night_vision
- name = "Toggle Nightvision"
- desc = "Toggle your nightvision mode."
-
- base_cooldown = 10
- clothes_req = FALSE
- antimagic_flags = NONE
-
- message = "You toggle your night vision!"
-
-/datum/spell/night_vision/create_new_targeting()
- return new /datum/spell_targeting/self
-
-/datum/spell/night_vision/cast(list/targets, mob/user = usr)
- for(var/mob/living/target in targets)
- switch(target.lighting_alpha)
- if(LIGHTING_PLANE_ALPHA_VISIBLE)
- target.lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE
- name = "Toggle Nightvision \[More]"
- if(LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE)
- target.lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE
- name = "Toggle Nightvision \[Full]"
- if(LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE)
- target.lighting_alpha = LIGHTING_PLANE_ALPHA_INVISIBLE
- name = "Toggle Nightvision \[OFF]"
- else
- target.lighting_alpha = LIGHTING_PLANE_ALPHA_VISIBLE
- name = "Toggle Nightvision \[ON]"
- target.update_sight()
diff --git a/code/datums/spells/rathens.dm b/code/datums/spells/rathens.dm
deleted file mode 100644
index 22969027c4668..0000000000000
--- a/code/datums/spells/rathens.dm
+++ /dev/null
@@ -1,48 +0,0 @@
-/datum/spell/rathens
- name = "Rathen's Secret"
- desc = "Summons a powerful shockwave around you that tears the appendix out of enemies, and occasionally removes their limbs."
- base_cooldown = 50 SECONDS
- clothes_req = TRUE
- invocation = "APPEN NATH!"
- invocation_type = "shout"
- cooldown_min = 20 SECONDS
- action_icon_state = "lungpunch"
-
-/datum/spell/rathens/create_new_targeting()
- var/datum/spell_targeting/targeted/T = new()
- T.max_targets = INFINITY
- return T
-
-/datum/spell/rathens/cast(list/targets, mob/user = usr)
- for(var/mob/living/carbon/human/H in targets)
- if(H.can_block_magic(antimagic_flags))
- continue
- var/datum/effect_system/smoke_spread/s = new
- s.set_up(5, FALSE, H)
- s.start()
- var/obj/item/organ/internal/appendix/A = H.get_int_organ(/obj/item/organ/internal/appendix)
- if(A)
- A.remove(H)
- A.forceMove(get_turf(H))
- spawn()
- A.throw_at(get_edge_target_turf(H, pick(GLOB.alldirs)), rand(1, 10), 5)
- H.visible_message("[H]'s [A.name] flies out of their body in a magical explosion!",\
- "Your [A.name] flies out of your body in a magical explosion!")
- H.KnockDown(4 SECONDS)
- else
- var/obj/effect/decal/cleanable/blood/gibs/G = new/obj/effect/decal/cleanable/blood/gibs(get_turf(H))
- spawn()
- G.throw_at(get_edge_target_turf(H, pick(GLOB.alldirs)), rand(1, 10), 5)
- H.apply_damage(10, BRUTE, "chest")
- to_chat(H, "You have no appendix, but something had to give! Holy shit, what was that?")
- H.KnockDown(6 SECONDS)
- for(var/obj/item/organ/external/E in H.bodyparts)
- if(istype(E, /obj/item/organ/external/head))
- continue
- if(istype(E, /obj/item/organ/external/chest))
- continue
- if(istype(E, /obj/item/organ/external/groin))
- continue
- if(prob(7))
- to_chat(H, "Your [E] was severed by the explosion!")
- E.droplimb(1, DROPLIMB_SHARP, 0, 1)
diff --git a/code/datums/spells/sentient_sword_lunge.dm b/code/datums/spells/sentient_sword_lunge.dm
deleted file mode 100644
index 630ffd1b6bacb..0000000000000
--- a/code/datums/spells/sentient_sword_lunge.dm
+++ /dev/null
@@ -1,29 +0,0 @@
-/datum/spell/sentient_sword_lunge
- name = "Lunge"
- desc = "Lunge at something in your view range."
- clothes_req = FALSE
- base_cooldown = 15 SECONDS
- invocation = "EN GARDE!"
- invocation_type = "shout"
- sound = 'sound/magic/repulse.ogg'
- action_icon_state = "lunge"
-
-/datum/spell/sentient_sword_lunge/create_new_targeting()
- return new /datum/spell_targeting/clicked_atom
-
-/datum/spell/sentient_sword_lunge/cast(list/targets, mob/user = usr)
- if(!istype(user.loc, /obj/item))
- to_chat(user, "You cannot use this ability if you're outside a blade!")
- return
- var/obj/item/nullrod/scythe/talking/user_sword = user.loc
- if(ishuman(user_sword.loc))
- var/mob/living/carbon/holder = user_sword.loc
- holder.drop_item_to_ground(user_sword)
- else if(isstorage(user_sword.loc))
- if(prob(50))
- to_chat(user, "You fail to break out of [user_sword.loc]!")
- return
- var/turf/our_turf = get_turf(user_sword.loc)
- our_turf.visible_message("[user_sword] leaps out of [user_sword.loc]!")
- user_sword.forceMove(our_turf)
- user_sword.throw_at(targets[1], 10, 3, user)
diff --git a/code/datums/spells/spacetime_dist.dm b/code/datums/spells/spacetime_dist.dm
deleted file mode 100644
index 9d7b09c162984..0000000000000
--- a/code/datums/spells/spacetime_dist.dm
+++ /dev/null
@@ -1,139 +0,0 @@
-// This could probably be an aoe spell but it's a little cursed, so I'm not touching it
-/datum/spell/spacetime_dist
- name = "Spacetime Distortion"
- desc = "Entangle the strings of space-time in an area around you, \
- randomizing the layout and making proper movement impossible. The strings vibrate... \
- Upgrading the spell increases range, it does not lower cooldown."
- sound = 'sound/magic/strings.ogg'
- action_icon_state = "spacetime"
-
- base_cooldown = 30 SECONDS
- clothes_req = TRUE
- invocation = "none"
- centcom_cancast = FALSE //Prevent people from getting to centcom
- cooldown_min = 30 SECONDS //No reduction, just more range.
- level_max = 3
- create_attack_logs = FALSE //no please god no do not log range^2 turfs being targeted
-
- /// Whether we're ready to cast again yet or not. In the event someone lowers their cooldown with charge.
- var/ready = TRUE
- /// The radius of the scramble around the caster. Increased by 3 * spell_level
- var/scramble_radius = 7
- /// The duration of the scramble
- var/duration = 15 SECONDS
- /// A lazylist of all scramble effects this spell has created.
- var/list/effects
-
-/datum/spell/spacetime_dist/Destroy()
- QDEL_LIST_CONTENTS(effects)
- return ..()
-
-/datum/spell/spacetime_dist/create_new_targeting()
- var/datum/spell_targeting/spiral/targeting = new()
- targeting.range = scramble_radius + 3 * spell_level
- return targeting
-
-/datum/spell/spacetime_dist/on_purchase_upgrade()
- . = ..()
- targeting = create_new_targeting()
-
-/datum/spell/spacetime_dist/cast_check(charge_check = TRUE, start_recharge = TRUE, mob/user = usr)
- return ..() && ready
-
-/datum/spell/spacetime_dist/cast(list/targets, mob/user = usr)
- . = ..()
- var/list/turf/to_switcharoo = targets
- if(!length(to_switcharoo))
- to_chat(user, "For whatever reason, the strings nearby aren't keen on being tangled.")
- return
-
- ready = FALSE
- effects = list()
-
- for(var/turf/swap_a as anything in to_switcharoo)
- var/turf/swap_b = to_switcharoo[swap_a]
- var/obj/effect/cross_action/spacetime_dist/effect_a = new /obj/effect/cross_action/spacetime_dist(swap_a)
- var/obj/effect/cross_action/spacetime_dist/effect_b = new /obj/effect/cross_action/spacetime_dist(swap_b)
- effect_a.linked_dist = effect_b
- effect_a.add_overlay(swap_b.photograph())
- effect_b.linked_dist = effect_a
- effect_b.add_overlay(swap_a.photograph())
- effect_b.set_light(4, 30, "#c9fff5")
- effects += effect_a
- effects += effect_b
-
-
-/datum/spell/spacetime_dist/after_cast(list/targets, mob/user)
- . = ..()
- addtimer(CALLBACK(src, PROC_REF(clean_turfs)), duration)
-
-/// Callback which cleans up our effects list after the duration expires.
-/datum/spell/spacetime_dist/proc/clean_turfs()
- QDEL_LIST_CONTENTS(effects)
- ready = TRUE
-
-/obj/effect/cross_action
- name = "cross me"
- desc = "for crossing."
- anchored = TRUE
-
-/obj/effect/cross_action/spacetime_dist
- name = "spacetime distortion"
- desc = "A distortion in spacetime. You can hear faint music..."
- icon_state = "nothing"
- /// A flags which save people from being thrown about
- var/antimagic_flags = MAGIC_RESISTANCE
- /// A flags which save people from being thrown about
- var/obj/effect/cross_action/spacetime_dist/linked_dist
- /// Used to prevent an infinite loop in the space tiime continuum
- var/cant_teleport = FALSE
- var/walks_left = 50 //prevents the game from hanging in extreme cases
-
-/obj/effect/cross_action/singularity_act()
- return
-
-/obj/effect/cross_action/singularity_pull()
- return
-
-/obj/effect/cross_action/spacetime_dist/Initialize(mapload)
- . = ..()
- var/static/list/loc_connections = list(
- COMSIG_ATOM_ENTERED = PROC_REF(on_atom_entered),
- )
- AddElement(/datum/element/connect_loc, loc_connections)
-
-/obj/effect/cross_action/spacetime_dist/proc/walk_link(atom/movable/AM)
- if(ismob(AM))
- var/mob/M = AM
- if(M.can_block_magic(antimagic_flags, charge_cost = 0))
- return
- if(linked_dist && walks_left > 0)
- flick("purplesparkles", src)
- linked_dist.get_walker(AM)
- walks_left--
-
-/obj/effect/cross_action/spacetime_dist/proc/get_walker(atom/movable/AM)
- cant_teleport = TRUE
- flick("purplesparkles", src)
- AM.forceMove(get_turf(src))
- cant_teleport = FALSE
-
-/obj/effect/cross_action/spacetime_dist/proc/on_atom_entered(atom/source, atom/movable/entered, turf/old_loc)
- if(!cant_teleport)
- walk_link(entered)
-
-/obj/effect/cross_action/spacetime_dist/attackby__legacy__attackchain(obj/item/W, mob/user, params)
- if(user.drop_item(W))
- walk_link(W)
- else
- walk_link(user)
-
-//ATTACK HAND IGNORING PARENT RETURN VALUE
-/obj/effect/cross_action/spacetime_dist/attack_hand(mob/user, list/modifiers)
- walk_link(user)
-
-/obj/effect/cross_action/spacetime_dist/Destroy()
- cant_teleport = TRUE
- linked_dist = null
- RemoveElement(/datum/element/connect_loc)
- return ..()
diff --git a/code/datums/spells/summonitem.dm b/code/datums/spells/summonitem.dm
deleted file mode 100644
index 2c4bd719ff32a..0000000000000
--- a/code/datums/spells/summonitem.dm
+++ /dev/null
@@ -1,148 +0,0 @@
-/datum/spell/summonitem
- name = "Instant Summons"
- desc = "This spell can be used to recall a previously marked item to your hand from anywhere in the universe."
- base_cooldown = 100
- clothes_req = FALSE
- invocation = "GAR YOK"
- invocation_type = "whisper"
- level_max = 0 //cannot be improved
- cooldown_min = 100
-
- var/obj/marked_item
- /// List of objects which will result in the spell stopping with the recursion search
- var/static/list/blacklisted_summons = list(/obj/machinery/computer/cryopod = TRUE, /obj/machinery/atmospherics = TRUE, /obj/structure/disposalholder = TRUE, /obj/machinery/disposal = TRUE)
- action_icon_state = "summons"
-
-/datum/spell/summonitem/create_new_targeting()
- return new /datum/spell_targeting/self
-
-/datum/spell/summonitem/cast(list/targets, mob/user = usr)
- for(var/mob/living/target in targets)
- var/list/hand_items = list(target.get_active_hand(),target.get_inactive_hand())
- var/butterfingers = FALSE
- var/message
-
- if(!marked_item) //linking item to the spell
- message = ""
- for(var/obj/item in hand_items)
- if(istype(item, /obj/item/organ/internal/brain)) //Yeah, sadly this doesn't work due to the organ system.
- break
- if(istype(item, /obj/item/disk/nuclear)) //Let's not make nukies suffer with this bullshit.
- to_chat(user, "[item] has some built in protections against such summoning magic.")
- break
- if(ABSTRACT in item.flags)
- continue
- if(NODROP in item.flags)
- message += "This feels very redundant, but you go through with it anyway. "
- marked_item = item
- message += "You mark [item] for recall."
- name = "Recall [item]"
- break
-
- if(!marked_item)
- if(hand_items)
- message = "You aren't holding anything that can be marked for recall."
- else
- message = "You must hold the desired item in your hands to mark it for recall."
-
- else if(marked_item && (marked_item in hand_items)) //unlinking item to the spell
- message = "You remove the mark on [marked_item] to use elsewhere."
- name = "Instant Summons"
- marked_item = null
-
- else if(marked_item && !marked_item.loc) //the item was destroyed at some point
- message = "You sense your marked item has been destroyed!"
- name = "Instant Summons"
- marked_item = null
-
- else //Getting previously marked item
- var/obj/item_to_retrieve = marked_item
- var/visible_item = TRUE //Items that silently disappear will have the message suppressed
- var/infinite_recursion = 0 //I don't want to know how someone could put something inside itself but these are wizards so let's be safe
-
- while(!isturf(item_to_retrieve.loc) && infinite_recursion < 10) //if it's in something you get the whole thing.
- if(istype(item_to_retrieve.loc, /obj/item/organ/internal/headpocket))
- var/obj/item/organ/internal/headpocket/pocket = item_to_retrieve.loc
- if(pocket.owner)
- to_chat(pocket.owner, "Your [pocket.name] suddenly feels lighter. How strange!")
- visible_item = FALSE
- break
- if(istype(item_to_retrieve.loc, /obj/item/storage/hidden_implant)) //The implant should be left alone
- var/obj/item/storage/S = item_to_retrieve.loc
- for(var/mob/M in S.mobs_viewing)
- to_chat(M, "[item_to_retrieve] suddenly disappears!")
- visible_item = FALSE
- break
- if(ismob(item_to_retrieve.loc)) //If its on someone, properly drop it
- var/mob/M = item_to_retrieve.loc
-
- if(issilicon(M) || !M.drop_item_to_ground(item_to_retrieve)) //Items in silicons warp the whole silicon
- M.visible_message("[M] suddenly disappears!", "A force suddenly pulls you away!")
- M.forceMove(target.loc)
- M.loc.visible_message("[M] suddenly appears!")
- item_to_retrieve = null
- break
-
- if(ishuman(M)) //Edge case housekeeping
- var/mob/living/carbon/human/C = M
- for(var/X in C.bodyparts)
- var/obj/item/organ/external/part = X
- if(item_to_retrieve in part.embedded_objects)
- part.remove_embedded_object(item_to_retrieve)
- to_chat(C, "[item_to_retrieve] that was embedded in your [part] has mysteriously vanished. How fortunate!")
- if(!C.has_embedded_objects())
- C.clear_alert("embeddedobject")
- break
- if(item_to_retrieve == part.hidden)
- visible_item = FALSE
- part.hidden = null
- to_chat(C, "Your [part.name] suddenly feels emptier. How weird!")
- break
-
- else
- if(istype(item_to_retrieve.loc, /obj/machinery/atmospherics/portable/)) //Edge cases for moved machinery
- var/obj/machinery/atmospherics/portable/P = item_to_retrieve.loc
- P.disconnect()
- P.update_icon()
- if(is_type_in_typecache(item_to_retrieve.loc, blacklisted_summons))
- break
- item_to_retrieve = item_to_retrieve.loc
- if(istype(item_to_retrieve, /obj/item/storage/backpack/modstorage))
- var/obj/item/storage/backpack/modstorage/bag = item_to_retrieve
- if(bag.source && bag.source.mod)
- item_to_retrieve = bag.source.mod //Grab the modsuit.
-
- infinite_recursion += 1
-
- if(!item_to_retrieve)
- return
-
- if(!isturf(target.loc))
- to_chat(target, "You attempt to cast the spell, but it fails! Perhaps you aren't available?")
- return
- if(visible_item)
- item_to_retrieve.loc.visible_message("[item_to_retrieve] suddenly disappears!")
- var/list/heres_disky = item_to_retrieve.search_contents_for(/obj/item/disk/nuclear)
- heres_disky += item_to_retrieve.loc.search_contents_for(/obj/item/disk/nuclear) //So if you mark another item in a bag, we don't pull
- for(var/obj/item/disk/nuclear/N in heres_disky)
- N.forceMove(get_turf(item_to_retrieve))
- N.visible_message("As [item_to_retrieve] vanishes, [N] remains behind!")
- break //If you have 2 nads, well, congrats? Keeps message from doubling up
- if(target.hand) //left active hand
- if(!target.equip_to_slot_if_possible(item_to_retrieve, ITEM_SLOT_LEFT_HAND, FALSE, TRUE))
- if(!target.equip_to_slot_if_possible(item_to_retrieve, ITEM_SLOT_RIGHT_HAND, FALSE, TRUE))
- butterfingers = TRUE
- else //right active hand
- if(!target.equip_to_slot_if_possible(item_to_retrieve, ITEM_SLOT_RIGHT_HAND, FALSE, TRUE))
- if(!target.equip_to_slot_if_possible(item_to_retrieve, ITEM_SLOT_LEFT_HAND, FALSE, TRUE))
- butterfingers = TRUE
- if(butterfingers)
- item_to_retrieve.loc = target.loc
- item_to_retrieve.loc.visible_message("[item_to_retrieve] suddenly appears!")
- playsound(get_turf(target),'sound/magic/summonitems_generic.ogg', 50, 1)
- else
- item_to_retrieve.loc.visible_message("[item_to_retrieve] suddenly appears in [target]'s hand!")
- playsound(get_turf(target),'sound/magic/summonitems_generic.ogg', 50, 1)
-
- if(message)
- to_chat(target, message)
diff --git a/code/datums/spells/touch_attacks.dm b/code/datums/spells/touch_attacks.dm
deleted file mode 100644
index f3aca8ff3fc0a..0000000000000
--- a/code/datums/spells/touch_attacks.dm
+++ /dev/null
@@ -1,82 +0,0 @@
-/datum/spell/touch
- var/hand_path = /obj/item/melee/touch_attack
- var/obj/item/melee/touch_attack/attached_hand = null
- var/on_remove_message = TRUE
- invocation_type = "none" //you scream on connecting, not summoning
-
-/datum/spell/touch/create_new_targeting()
- return new /datum/spell_targeting/self
-
-/datum/spell/touch/Click(mob/user = usr)
- if(attached_hand)
- discharge_hand(user, TRUE)
- return FALSE
- charge_hand(user)
-
-/datum/spell/touch/proc/charge_hand(mob/living/carbon/user)
- var/hand_handled = 1
- attached_hand = new hand_path(src)
- RegisterSignal(user, COMSIG_MOB_WILLINGLY_DROP, PROC_REF(discharge_hand))
- if(isalien(user))
- user.put_in_hands(attached_hand)
- return
- if(user.hand) //left active hand
- if(!user.equip_to_slot_if_possible(attached_hand, ITEM_SLOT_LEFT_HAND, FALSE, TRUE))
- if(!user.equip_to_slot_if_possible(attached_hand, ITEM_SLOT_RIGHT_HAND, FALSE, TRUE))
- hand_handled = 0
- else //right active hand
- if(!user.equip_to_slot_if_possible(attached_hand, ITEM_SLOT_RIGHT_HAND, FALSE, TRUE))
- if(!user.equip_to_slot_if_possible(attached_hand, ITEM_SLOT_LEFT_HAND, FALSE, TRUE))
- hand_handled = 0
- if(!hand_handled)
- qdel(attached_hand)
- attached_hand = null
- to_chat(user, "Your hands are full!")
- return 0
- to_chat(user, "You channel the power of the spell to your hand.")
- return 1
-
-/datum/spell/touch/proc/discharge_hand(atom/target, any = FALSE)
- SIGNAL_HANDLER
- var/mob/living/carbon/user = action.owner
- if(!istype(attached_hand))
- return
- if(!any && attached_hand != user.get_active_hand())
- return
- QDEL_NULL(attached_hand)
- if(on_remove_message)
- to_chat(user, "You draw the power out of your hand.")
-
-
-/datum/spell/touch/disintegrate
- name = "Disintegrate"
- desc = "This spell charges your hand with vile energy that can be used to violently explode victims."
- hand_path = /obj/item/melee/touch_attack/disintegrate
-
- base_cooldown = 600
- clothes_req = TRUE
- cooldown_min = 200 //100 deciseconds reduction per rank
-
- action_icon_state = "gib"
-
-/datum/spell/touch/flesh_to_stone
- name = "Flesh to Stone"
- desc = "This spell charges your hand with the power to turn victims into inert statues for a long period of time."
- hand_path = /obj/item/melee/touch_attack/fleshtostone
-
- base_cooldown = 600
- clothes_req = TRUE
- cooldown_min = 200 //100 deciseconds reduction per rank
-
- action_icon_state = "statue"
-
-/datum/spell/touch/plushify
- name = "Plushify"
- desc = "This spell charges your hand with the power to turn your victims into marketable plushies!"
- hand_path = /obj/item/melee/touch_attack/plushify
-
- base_cooldown = 600
- clothes_req = TRUE
- cooldown_min = 200 //100 deciseconds reduction per rank
-
- action_icon_state = "plush"
diff --git a/code/datums/spells/wizard_spells.dm b/code/datums/spells/wizard_spells.dm
deleted file mode 100644
index c62f868f40186..0000000000000
--- a/code/datums/spells/wizard_spells.dm
+++ /dev/null
@@ -1,517 +0,0 @@
-/datum/spell/projectile/magic_missile
- name = "Magic Missile"
- desc = "This spell fires several, slow moving, magic projectiles at nearby targets."
-
- base_cooldown = 200
- clothes_req = TRUE
- invocation = "FORTI GY AMA"
- invocation_type = "shout"
- cooldown_min = 60 //35 deciseconds reduction per rank
-
- proj_icon_state = "magicm"
- proj_name = "a magic missile"
- proj_lingering = 1
- proj_type = /obj/item/projectile/magic/magic_missile
-
- proj_lifespan = 20
- proj_step_delay = 2
-
- proj_trail = 1
- proj_trail_lifespan = 5
- proj_trail_icon_state = "magicmd"
-
- action_icon_state = "magicm"
-
- sound = 'sound/magic/magic_missile.ogg'
-
-/datum/spell/projectile/magic_missile/create_new_targeting()
- var/datum/spell_targeting/targeted/T = new()
- T.allowed_type = /mob/living
- T.max_targets = INFINITY
- return T
-
-/obj/item/projectile/magic/magic_missile
- name = "Magic Missile"
- hitsound = 'sound/magic/mm_hit.ogg'
- weaken = 6 SECONDS
-
-/datum/spell/projectile/honk_missile
- name = "Honk Missile"
- desc = "This spell fires several, slow moving, magic bikehorns at nearby targets."
-
- base_cooldown = 6 SECONDS
- clothes_req = FALSE
- invocation = "HONK GY AMA"
- invocation_type = "shout"
- cooldown_min = 6 SECONDS
-
- proj_icon = 'icons/obj/items.dmi'
- proj_icon_state = "bike_horn"
- proj_name = "A bike horn"
- proj_lingering = 1
- proj_type = /obj/item/projectile/magic/magic_missile/honk_missile
-
- proj_lifespan = 20
- proj_step_delay = 5
-
- proj_trail_icon = 'icons/obj/items.dmi'
- proj_trail = 1
- proj_trail_lifespan = 5
- proj_trail_icon_state = "bike_horn"
-
- action_icon_state = "magicm"
-
- sound = 'sound/items/bikehorn.ogg'
-
-/datum/spell/projectile/honk_missile/create_new_targeting()
- var/datum/spell_targeting/targeted/T = new()
- T.allowed_type = /mob/living
- T.max_targets = INFINITY
- return T
-
-/obj/item/projectile/magic/magic_missile/honk_missile
- name = "Funny Missile"
- hitsound = 'sound/items/bikehorn.ogg'
-
-/datum/spell/noclothes
- name = "No Clothes"
- desc = "This always-on spell allows you to cast magic without your garments."
- action_icon_state = "no_clothes"
-
-/datum/spell/noclothes/create_new_targeting()
- return new /datum/spell_targeting/self // Dummy value
-
-/datum/spell/genetic/mutate
- name = "Mutate"
- desc = "This spell causes you to turn into a hulk and gain laser vision for a short while."
-
- base_cooldown = 400
- clothes_req = TRUE
- invocation = "BIRUZ BENNAR"
- invocation_type = "shout"
- message = "You feel strong! You feel a pressure building behind your eyes!"
- centcom_cancast = FALSE
-
- traits = list(TRAIT_LASEREYES)
- duration = 300
- cooldown_min = 300 //25 deciseconds reduction per rank
-
- action_icon_state = "mutate"
- sound = 'sound/magic/mutate.ogg'
-
-/datum/spell/genetic/mutate/New()
- ..()
- mutations = list(GLOB.hulkblock)
-
-/datum/spell/genetic/mutate/create_new_targeting()
- return new /datum/spell_targeting/self
-
-/datum/spell/smoke
- name = "Smoke"
- desc = "This spell spawns a cloud of choking smoke at your location and does not require wizard garb."
-
- base_cooldown = 120
- clothes_req = FALSE
- invocation = "none"
- invocation_type = "none"
- cooldown_min = 20 //25 deciseconds reduction per rank
-
- smoke_type = SMOKE_COUGHING
- smoke_amt = 10
-
- action_icon_state = "smoke"
-
-/datum/spell/smoke/create_new_targeting()
- return new /datum/spell_targeting/self
-
-/datum/spell/emplosion/disable_tech
- name = "Disable Tech"
- desc = "This spell disables all weapons, cameras and most other technology in range."
- base_cooldown = 40 SECONDS
- clothes_req = TRUE
- invocation = "NEC CANTIO"
- invocation_type = "shout"
- cooldown_min = 200 //50 deciseconds reduction per rank
-
- emp_heavy = 6
- emp_light = 10
-
- sound = 'sound/magic/disable_tech.ogg'
-
-/datum/spell/turf_teleport/blink
- name = "Blink"
- desc = "This spell randomly teleports you a short distance."
-
- base_cooldown = 20
- clothes_req = TRUE
- invocation = "none"
- invocation_type = "none"
- cooldown_min = 5 //4 deciseconds reduction per rank
-
-
- smoke_type = SMOKE_HARMLESS
- smoke_amt = 1
-
- inner_tele_radius = 0
- outer_tele_radius = 6
-
- centcom_cancast = FALSE //prevent people from getting to centcom
-
- action_icon_state = "blink"
-
- sound1 = 'sound/magic/blink.ogg'
- sound2 = 'sound/magic/blink.ogg'
-
-/datum/spell/turf_teleport/blink/create_new_targeting()
- return new /datum/spell_targeting/self
-
-/datum/spell/area_teleport/teleport
- name = "Teleport"
- desc = "This spell teleports you to a type of area of your selection."
-
- base_cooldown = 600
- clothes_req = TRUE
- invocation = "SCYAR NILA"
- invocation_type = "shout"
- cooldown_min = 200 //100 deciseconds reduction per rank
-
- smoke_amt = 5
- action_icon_state = "spell_teleport"
-
- sound1 = 'sound/magic/teleport_diss.ogg'
- sound2 = 'sound/magic/teleport_app.ogg'
-
-/datum/spell/area_teleport/teleport/create_new_targeting()
- return new /datum/spell_targeting/self
-
-/datum/spell/return_to_teacher
- name = "Return to Teacher"
- desc = "This spell teleports you back to your teacher."
-
- base_cooldown = 30 SECONDS
- clothes_req = TRUE
- invocation = "SCYAR TESO"
- invocation_type = "shout"
- cooldown_min = 10 SECONDS
-
- action_icon_state = "spell_teleport"
- var/datum/mind/teacher
-
-/datum/spell/return_to_teacher/create_new_targeting()
- return new /datum/spell_targeting/self
-
-/datum/spell/return_to_teacher/cast(list/targets, mob/living/user = usr)
- if(!(teacher && teacher.current))
- to_chat(user, "The link to your teacher is broken!")
- return
- do_teleport(user, teacher.current, 1, sound_in = 'sound/magic/blink.ogg', sound_out = 'sound/magic/blink.ogg', safe_turf_pick = TRUE)
-
-/datum/spell/forcewall
- name = "Force Wall"
- desc = "This spell creates a 3 tile wide unbreakable wall that only you can pass through, and does not need wizard garb. Lasts 30 seconds."
-
- base_cooldown = 15 SECONDS
- clothes_req = FALSE
- invocation = "TARCOL MINTI ZHERI"
- invocation_type = "whisper"
- sound = 'sound/magic/forcewall.ogg'
- action_icon_state = "shield"
- cooldown_min = 5 SECONDS //25 deciseconds reduction per rank
- var/wall_type = /obj/effect/forcefield/wizard
-
-/datum/spell/forcewall/create_new_targeting()
- return new /datum/spell_targeting/self
-
-/datum/spell/forcewall/cast(list/targets, mob/user = usr)
- new wall_type(get_turf(user), user)
- if(user.dir == SOUTH || user.dir == NORTH)
- new wall_type(get_step(user, EAST), user)
- new wall_type(get_step(user, WEST), user)
- else
- new wall_type(get_step(user, NORTH), user)
- new wall_type(get_step(user, SOUTH), user)
-
-/datum/spell/aoe/conjure/timestop
- name = "Stop Time"
- desc = "This spell stops time for everyone except for you, allowing you to move freely while your enemies and even projectiles are frozen."
- base_cooldown = 50 SECONDS
- clothes_req = TRUE
- invocation = "TOKI WO TOMARE"
- invocation_type = "shout"
- cooldown_min = 100
- summon_amt = 1
- delay = 0
- action_icon_state = "time"
-
- summon_type = list(/obj/effect/timestop/wizard)
- aoe_range = 0
-
-
-/datum/spell/aoe/conjure/carp
- name = "Summon Carp"
- desc = "This spell conjures a simple carp."
-
- base_cooldown = 1200
- clothes_req = TRUE
- invocation = "NOUK FHUNMM SACP RISSKA"
- invocation_type = "shout"
-
- summon_type = list(/mob/living/simple_animal/hostile/carp)
-
- cast_sound = 'sound/magic/summon_karp.ogg'
- aoe_range = 1
-
-/datum/spell/aoe/conjure/construct
- name = "Artificer"
- desc = "This spell conjures a construct which may be controlled by Shades."
-
- base_cooldown = 600
- clothes_req = FALSE
- invocation = "none"
- invocation_type = "none"
-
- summon_type = list(/obj/structure/constructshell)
-
- action_icon_state = "artificer"
- cast_sound = 'sound/magic/summonitems_generic.ogg'
- aoe_range = 0
-
-/datum/spell/aoe/conjure/creature
- name = "Summon Creature Swarm"
- desc = "This spell tears the fabric of reality, allowing horrific daemons to spill forth."
-
- base_cooldown = 1200
- clothes_req = FALSE
- invocation = "IA IA"
- invocation_type = "shout"
- summon_amt = 10
-
- summon_type = list(/mob/living/simple_animal/hostile/creature)
- cast_sound = 'sound/magic/summonitems_generic.ogg'
- aoe_range = 3
-
-/datum/spell/blind
- name = "Blind"
- desc = "This spell temporarily blinds a single person and does not require wizard garb."
- base_cooldown = 15 SECONDS
- clothes_req = FALSE
- invocation = "STI KALY"
- invocation_type = "whisper"
- message = "Your eyes cry out in pain!"
- cooldown_min = 2 SECONDS
- sound = 'sound/magic/blind.ogg'
-
-/datum/spell/blind/create_new_targeting()
- var/datum/spell_targeting/click/C = new()
- C.selection_type = SPELL_SELECTION_RANGE
- C.allowed_type = /mob/living
- return C
-
-/datum/spell/blind/cast(list/targets, mob/living/user)
- if(!length(targets))
- to_chat(user, "No target found in range.")
- return
-
- var/mob/living/target = targets[1]
- if(target.can_block_magic(antimagic_flags))
- to_chat(target, "Your eye itches, but it passes momentarily.")
- to_chat(user, "The spell had no effect!")
- return FALSE
- target.EyeBlurry(40 SECONDS)
- target.EyeBlind(30 SECONDS)
-
- SEND_SOUND(target, sound('sound/magic/blind.ogg'))
- return TRUE
-
-/datum/spell/fireball
- name = "Fireball"
- desc = "This spell fires a fireball at a target and does not require wizard garb."
-
- base_cooldown = 60
- clothes_req = FALSE
- invocation = "ONI SOMA"
- invocation_type = "shout"
- cooldown_min = 20 //10 deciseconds reduction per rank
-
- selection_activated_message = "You prepare to cast your fireball spell! Left-click to cast at a target!"
- selection_deactivated_message = "You extinguish your fireball...for now."
-
- var/fireball_type = /obj/item/projectile/magic/fireball
- action_icon_state = "fireball0"
- sound = 'sound/magic/fireball.ogg'
-
- active = FALSE
-
-/datum/spell/fireball/apprentice
- centcom_cancast = FALSE
-
-/datum/spell/fireball/create_new_targeting()
- var/datum/spell_targeting/clicked_atom/external/C = new()
- C.range = 20
- return C
-
-/datum/spell/fireball/update_spell_icon()
- if(!action)
- return
- action.button_overlay_icon_state = "fireball[active]"
- action.UpdateButtons()
-
-/datum/spell/fireball/cast(list/targets, mob/living/user = usr)
- var/target = targets[1] //There is only ever one target for fireball
- var/turf/T = user.loc
- var/turf/U = get_step(user, user.dir) // Get the tile infront of the move, based on their direction
- if(!isturf(U) || !isturf(T))
- return FALSE
-
- var/obj/item/projectile/magic/fireball/FB = new fireball_type(user.loc)
- FB.current = get_turf(user)
- FB.original = target
- FB.firer = user
- FB.preparePixelProjectile(target, user)
- FB.fire()
- user.newtonian_move(get_dir(U, T))
-
- return TRUE
-
-/datum/spell/fireball/toolbox
- name = "Homing Toolbox"
- desc = "This spell summons and throws a magical homing toolbox at your opponent."
- sound = 'sound/weapons/smash.ogg'
- fireball_type = /obj/item/projectile/homing/magic/toolbox
- invocation = "ROBUSTIO!"
-
- selection_activated_message = "You prepare to cast your homing toolbox! Left-click to cast at a target!"
- selection_deactivated_message = "You unrobust your toolbox...for now."
-
-/datum/spell/fireball/homing
- name = "Greater Homing Fireball"
- desc = "This spell fires a strong homing fireball at a target."
- invocation = "ZI-ONI SOMA"
- fireball_type = /obj/item/projectile/homing/magic/homing_fireball
-
- selection_activated_message = "You prepare to cast your greater homing fireball spell! Left-click to cast at a target!"
- base_cooldown = 6 SECONDS
-
-/datum/spell/aoe/repulse
- name = "Repulse"
- desc = "This spell throws everything around the user away."
- base_cooldown = 40 SECONDS
- clothes_req = TRUE
- invocation = "GITTAH WEIGH"
- invocation_type = "shout"
- cooldown_min = 150
- sound = 'sound/magic/repulse.ogg'
- var/maxthrow = 5
- var/sparkle_path = /obj/effect/temp_visual/gravpush
- action_icon_state = "repulse"
- aoe_range = 5
-
-/datum/spell/aoe/repulse/create_new_targeting()
- var/datum/spell_targeting/aoe/turf/targeting = new()
- targeting.range = aoe_range
- return targeting
-
-/datum/spell/aoe/repulse/cast(list/targets, mob/user = usr, stun_amt = 4 SECONDS)
- var/list/thrownatoms = list()
- var/atom/throwtarget
- var/distfromcaster
- playMagSound()
- for(var/turf/T in targets) //Done this way so things don't get thrown all around hilariously.
- for(var/atom/movable/AM in T)
- if(ismob(AM))
- var/mob/victim_mob = AM
- if(victim_mob.can_block_magic(antimagic_flags))
- continue
- thrownatoms += AM
-
- for(var/am in thrownatoms)
- var/atom/movable/AM = am
- if(AM == user || AM.anchored || AM.move_resist == INFINITY)
- continue
-
- throwtarget = get_edge_target_turf(user, get_dir(user, get_step_away(AM, user)))
- distfromcaster = get_dist(user, AM)
- if(distfromcaster == 0)
- if(isliving(AM))
- var/mob/living/M = AM
- M.Weaken(10 SECONDS)
- M.adjustBruteLoss(5)
- to_chat(M, "You're slammed into the floor by a mystical force!")
- else
- new sparkle_path(get_turf(AM), get_dir(user, AM)) //created sparkles will disappear on their own
- if(isliving(AM))
- var/mob/living/M = AM
- M.Weaken(stun_amt)
- to_chat(M, "You're thrown back by a mystical force!")
- spawn(0)
- AM.throw_at(throwtarget, ((clamp((maxthrow - (clamp(distfromcaster - 2, 0, distfromcaster))), 3, maxthrow))), 1)//So stuff gets tossed around at the same time.
-
-/datum/spell/sacred_flame
- name = "Sacred Flame"
- desc = "Makes everyone around you more flammable, and lights yourself on fire."
- base_cooldown = 6 SECONDS
- clothes_req = FALSE
- invocation = "FI'RAN DADISKO"
- invocation_type = "shout"
- action_icon_state = "sacredflame"
- sound = 'sound/magic/fireball.ogg'
-
-/datum/spell/sacred_flame/create_new_targeting()
- var/datum/spell_targeting/aoe/A = new()
- A.include_user = TRUE
- A.range = 6
- A.allowed_type = /mob/living
- return A
-
-/datum/spell/sacred_flame/cast(list/targets, mob/user = usr)
- for(var/mob/living/L in targets)
- if(L.can_block_magic(antimagic_flags))
- continue
- L.adjust_fire_stacks(20)
- if(isliving(user))
- var/mob/living/U = user
- U.IgniteMob()
-
-/datum/spell/corpse_explosion
- name = "Corpse Explosion"
- desc = "Fills a corpse with energy, causing it to explode violently."
- base_cooldown = 5 SECONDS
- clothes_req = TRUE
- invocation = "JAH ITH BER"
- invocation_type = "whisper"
- selection_activated_message = "You prepare to detonate a corpse. Click on a target to cast the spell."
- selection_deactivated_message = "You cancel the spell."
- action_icon_state = "corpse_explosion"
-
-/datum/spell/corpse_explosion/create_new_targeting()
- var/datum/spell_targeting/click/T = new
- T.click_radius = 0
- T.try_auto_target = FALSE
- T.allowed_type = /mob/living
- return T
-
-/datum/spell/corpse_explosion/cast(list/targets, mob/user)
- var/mob/living/target = targets[1]
- if(!target || target.stat != DEAD)
- return
- var/turf/corpse_turf = get_turf(target)
- new /obj/effect/temp_visual/corpse_explosion(get_turf(target))
- target.gib()
- explosion(corpse_turf, 0, 0, 0, 0, silent = TRUE, breach = FALSE)
- for(var/mob/living/M in range(4, corpse_turf))
- if(M == user)
- continue
- var/range = get_dist_euclidian(M, corpse_turf)
- range = max(1, range)
- M.apply_damage(100 / range, BRUTE)
- if(issilicon(M))
- to_chat(M, "Your sensors are disabled, and your carapace is ripped apart by the violent dark magic!")
- M.Weaken(6 SECONDS / range)
- continue
-
- to_chat(M, "You are eviscerated by the violent dark magic!")
- if(ishuman(M))
- if(range < 4)
- M.KnockDown(4 SECONDS / range)
- M.EyeBlurry(40 SECONDS / range)
- M.AdjustConfused(6 SECONDS / range)
diff --git a/code/datums/station_traits/neutral_traits.dm b/code/datums/station_traits/neutral_traits.dm
deleted file mode 100644
index 03cac88b537fe..0000000000000
--- a/code/datums/station_traits/neutral_traits.dm
+++ /dev/null
@@ -1,102 +0,0 @@
-/datum/station_trait/bananium_shipment
- name = "Bananium Shipment"
- trait_type = STATION_TRAIT_NEUTRAL
- weight = 5
- report_message = "An unidentified benefactor has dispatched a mysterious shipment to your station's clown. It was reported to smell faintly of bananas."
- trait_to_give = STATION_TRAIT_BANANIUM_SHIPMENTS
-
-/datum/station_trait/tranquilite_shipment
- name = "Tranquilite Shipment"
- trait_type = STATION_TRAIT_NEUTRAL
- weight = 5
- report_message = "Shipping records show an unmarked crate being delivered to your station's mime."
- trait_to_give = STATION_TRAIT_TRANQUILITE_SHIPMENTS
-
-/datum/station_trait/unique_ai
- name = "Unique AI"
- trait_type = STATION_TRAIT_NEUTRAL
- weight = 15
- show_in_report = TRUE
- report_message = "For experimental purposes, this station AI might show divergence from default lawset. Do not meddle with this experiment, we've removed \
- access to your set of alternative upload modules because we know you're already thinking about meddling with this experiment. If the lawset proves \
- dangerous, or impedes station efficiency, fax or message Central Command to request permission to alter it."
- trait_to_give = STATION_TRAIT_UNIQUE_AI
- blacklist = list(/datum/station_trait/random_event_weight_modifier/ion_storms)
-
-/datum/station_trait/glitched_pdas
- name = "PDA glitch"
- trait_type = STATION_TRAIT_NEUTRAL
- weight = 15
- show_in_report = TRUE
- report_message = "Something seems to be wrong with the PDAs issued to you all this shift. Nothing too bad though."
- trait_to_give = STATION_TRAIT_PDA_GLITCHED
-
-/datum/station_trait/late_arrivals
- name = "Late Arrivals"
- trait_type = STATION_TRAIT_NEUTRAL
- weight = 5
- show_in_report = TRUE
- report_message = "Sorry for that, we didn't expect to fly into that vomiting goose while bringing you to your new station."
- trait_to_give = STATION_TRAIT_LATE_ARRIVALS
- blacklist = list(/datum/station_trait/hangover)
-
-/datum/station_trait/late_arrivals/New()
- . = ..()
- SSjobs.late_arrivals_spawning = TRUE
-
-/datum/station_trait/late_arrivals/revert()
- . = ..()
- SSjobs.late_arrivals_spawning = FALSE
-
-
-/datum/station_trait/hangover
- name = "Hangover"
- trait_type = STATION_TRAIT_NEUTRAL
- weight = 5
- show_in_report = TRUE
- report_message = "Ohh....Man....That mandatory office party from last shift...God that was awesome..I woke up in some random toilet 3 sectors away..."
- trait_to_give = STATION_TRAIT_HANGOVER
- blacklist = list(/datum/station_trait/late_arrivals)
-
-/datum/station_trait/hangover/New()
- . = ..()
- SSjobs.drunken_spawning = TRUE
-
-/datum/station_trait/hangover/revert()
- . = ..()
- SSjobs.drunken_spawning = FALSE
-
-/datum/station_trait/triple_ai
- name = "AI Triumvirate"
- trait_type = STATION_TRAIT_NEUTRAL
- weight = 1
- show_in_report = TRUE
- report_message = "As part of Operation Magi, your station has been equipped with three Nanotrasen Artificial Intelligence models. Please try not to break them."
- trait_to_give = STATION_TRAIT_TRIAI
-
-/datum/station_trait/triple_ai/New()
- . = ..()
- SSticker.triai = TRUE
-
-/datum/station_trait/triple_ai/revert()
- . = ..()
- SSticker.triai = FALSE
-
-/datum/station_trait/rave
- name = "Rave"
- trait_type = STATION_TRAIT_NEUTRAL
- weight = 5
- show_in_report = TRUE
- report_message = "Our workers have installed new 'Motivational' lighting for you."
-
-/datum/station_trait/rave/on_round_start()
- . = ..()
- for(var/obj/machinery/light/light in GLOB.machines)
- var/turf/our_turf = get_turf(light)
- var/area/our_area = get_area(light)
- if(is_station_level(our_turf.z) || istype(our_area, /area/mine/outpost) || istype(our_area, /area/mine/laborcamp))
- var/list/rgb = hsl2rgb(rand(0, 255) / 255, rand((0.4 * 255), 255) / 255, rand((0.5 * 255), (0.8 * 255)) / 255)
- var/new_color = "#[num2hex(rgb[1], 2)][num2hex(rgb[2], 2)][num2hex(rgb[3], 2)]"
- light.color = new_color
- light.brightness_color = new_color
- light.update(FALSE, TRUE, FALSE)
diff --git a/code/datums/status_effects/debuffs.dm b/code/datums/status_effects/debuffs.dm
deleted file mode 100644
index 2c5acaf69cc07..0000000000000
--- a/code/datums/status_effects/debuffs.dm
+++ /dev/null
@@ -1,1510 +0,0 @@
-//OTHER DEBUFFS
-
-/// does minor damage over time unless holding His Grace
-/datum/status_effect/his_wrath
- id = "his_wrath"
- duration = -1
- tick_interval = 4
- alert_type = /atom/movable/screen/alert/status_effect/his_wrath
-
-/atom/movable/screen/alert/status_effect/his_wrath
- name = "His Wrath"
- desc = "You fled from His Grace instead of feeding Him, and now you suffer."
- icon_state = "his_grace"
- alerttooltipstyle = "hisgrace"
-
-/datum/status_effect/his_wrath/tick()
- var/list/held_items = list()
- held_items += owner.l_hand
- held_items += owner.r_hand
- for(var/obj/item/his_grace/HG in held_items)
- qdel(src)
- return
- owner.adjustBruteLoss(0.1)
- owner.adjustFireLoss(0.1)
- owner.adjustToxLoss(0.2)
-
-/// is a cult ghost and can't use manifest runes, can see ghosts and dies if too far from summoner
-/datum/status_effect/cultghost
- id = "cult_ghost"
- duration = -1
- alert_type = null
- var/damage = 7.5
- var/source_UID
-
-/datum/status_effect/cultghost/on_creation(mob/living/new_owner, mob/living/source)
- . = ..()
- source_UID = source.UID()
-
-/datum/status_effect/cultghost/tick()
- if(owner.reagents)
- owner.reagents.del_reagent("holywater") //can't be deconverted
- var/mob/living/summoner = locateUID(source_UID)
- if(get_dist_euclidian(summoner, owner) < 21)
- return
- owner.adjustBruteLoss(damage)
- to_chat(owner, "You are too far away from the summoner!")
-
-/datum/status_effect/crusher_mark
- id = "crusher_mark"
- duration = 300 //if you leave for 30 seconds you lose the mark, deal with it
- status_type = STATUS_EFFECT_REPLACE
- alert_type = null
- var/mutable_appearance/marked_underlay
- var/obj/item/kinetic_crusher/hammer_synced
-
-/datum/status_effect/crusher_mark/on_creation(mob/living/new_owner, obj/item/kinetic_crusher/new_hammer_synced)
- . = ..()
- if(.)
- hammer_synced = new_hammer_synced
-
-/datum/status_effect/crusher_mark/on_apply()
- if(owner.mob_size >= MOB_SIZE_LARGE)
- marked_underlay = mutable_appearance('icons/effects/effects.dmi', "shield2")
- marked_underlay.pixel_x = -owner.pixel_x
- marked_underlay.pixel_y = -owner.pixel_y
- owner.underlays += marked_underlay
- return TRUE
- return FALSE
-
-/datum/status_effect/crusher_mark/Destroy()
- hammer_synced = null
- if(owner)
- owner.underlays -= marked_underlay
- QDEL_NULL(marked_underlay)
- return ..()
-
-/datum/status_effect/crusher_mark/be_replaced()
- owner.underlays -= marked_underlay //if this is being called, we should have an owner at this point.
- ..()
-
-/datum/status_effect/saw_bleed
- id = "saw_bleed"
- duration = -1 //removed under specific conditions
- tick_interval = 6
- alert_type = null
- var/mutable_appearance/bleed_overlay
- var/mutable_appearance/bleed_underlay
- var/bleed_amount = 3
- var/bleed_buildup = 3
- var/delay_before_decay = 5
- var/bleed_damage = 200
- var/needs_to_bleed = FALSE
- var/bleed_cap = 10
-
-/datum/status_effect/saw_bleed/Destroy()
- if(owner)
- owner.cut_overlay(bleed_overlay)
- owner.underlays -= bleed_underlay
- QDEL_NULL(bleed_overlay)
- return ..()
-
-/datum/status_effect/saw_bleed/on_apply()
- if(owner.stat == DEAD)
- return FALSE
- bleed_overlay = mutable_appearance('icons/effects/bleed.dmi', "bleed[bleed_amount]")
- bleed_underlay = mutable_appearance('icons/effects/bleed.dmi', "bleed[bleed_amount]")
- var/icon/I = icon(owner.icon, owner.icon_state, owner.dir)
- var/icon_height = I.Height()
- bleed_overlay.pixel_x = -owner.pixel_x
- bleed_overlay.pixel_y = FLOOR(icon_height * 0.25, 1)
- bleed_overlay.transform = matrix() * (icon_height/world.icon_size) //scale the bleed overlay's size based on the target's icon size
- bleed_underlay.pixel_x = -owner.pixel_x
- bleed_underlay.transform = matrix() * (icon_height/world.icon_size) * 3
- bleed_underlay.alpha = 40
- owner.add_overlay(bleed_overlay)
- owner.underlays += bleed_underlay
- return ..()
-
-/datum/status_effect/saw_bleed/tick()
- if(owner.stat == DEAD)
- qdel(src)
- else
- add_bleed(-1)
-
-/datum/status_effect/saw_bleed/proc/add_bleed(amount)
- owner.cut_overlay(bleed_overlay)
- owner.underlays -= bleed_underlay
- bleed_amount += amount
- if(bleed_amount)
- if(bleed_amount >= bleed_cap)
- needs_to_bleed = TRUE
- qdel(src)
- else
- if(amount > 0)
- tick_interval += delay_before_decay
- bleed_overlay.icon_state = "bleed[bleed_amount]"
- bleed_underlay.icon_state = "bleed[bleed_amount]"
- owner.add_overlay(bleed_overlay)
- owner.underlays += bleed_underlay
- else
- qdel(src)
-
-/datum/status_effect/saw_bleed/on_remove()
- if(needs_to_bleed)
- var/turf/T = get_turf(owner)
- new /obj/effect/temp_visual/bleed/explode(T)
- for(var/d in GLOB.alldirs)
- new /obj/effect/temp_visual/dir_setting/bloodsplatter(T, d)
- playsound(T, "desceration", 200, TRUE, -1)
- owner.adjustBruteLoss(bleed_damage)
- else
- new /obj/effect/temp_visual/bleed(get_turf(owner))
-
-/datum/status_effect/saw_bleed/bloodletting
- id = "bloodletting"
- bleed_cap = 7
- bleed_damage = 25 //Seems weak (it is) but it also works on humans and bypasses armor SOOOO
- bleed_amount = 6
-
-/datum/status_effect/stacking/ground_pound
- id = "ground_pound"
- tick_interval = 5 SECONDS
- stack_threshold = 3
- max_stacks = 3
- reset_ticks_on_stack = TRUE
- var/mob/living/simple_animal/hostile/asteroid/big_legion/latest_attacker
-
-/datum/status_effect/stacking/ground_pound/on_creation(mob/living/new_owner, stacks_to_apply, mob/living/attacker)
- . = ..()
- if(.)
- latest_attacker = attacker
-
-/datum/status_effect/stacking/ground_pound/add_stacks(stacks_added, mob/living/attacker)
- . = ..()
- if(.)
- latest_attacker = attacker
- if(stacks != stack_threshold)
- return TRUE
-
-/datum/status_effect/stacking/ground_pound/stacks_consumed_effect()
- flick("legion-smash", latest_attacker)
- addtimer(CALLBACK(latest_attacker, TYPE_PROC_REF(/mob/living/simple_animal/hostile/asteroid/big_legion, throw_mobs)), 1 SECONDS)
-
-/datum/status_effect/stacking/ground_pound/on_remove()
- latest_attacker = null
-
-/datum/status_effect/teleport_sickness
- id = "teleportation sickness"
- duration = 30 SECONDS
- status_type = STATUS_EFFECT_REFRESH
- alert_type = /atom/movable/screen/alert/status_effect/teleport_sickness
- var/teleports = 1
-
-/atom/movable/screen/alert/status_effect/teleport_sickness
- name = "Teleportation sickness"
- desc = "You feel like you are going to throw up with all this teleporting."
- icon_state = "bluespace"
-
-/datum/status_effect/teleport_sickness/refresh()
- . = ..()
- if(ishuman(owner))
- var/mob/living/carbon/human/M = owner
- teleports++
- if(teleports < 3)
- return
- if(teleports < 6)
- to_chat(M, "You feel a bit sick!")
- M.vomit(lost_nutrition = 15, blood = 0, should_confuse = FALSE, distance = 0, message = 1)
- M.Weaken(2 SECONDS)
- else
- to_chat(M, "You feel really sick!")
- M.adjustBruteLoss(rand(0, teleports * 2))
- M.vomit(lost_nutrition = 30, blood = 0, should_confuse = FALSE, distance = 0, message = 1)
- M.Weaken(6 SECONDS)
-
-/datum/status_effect/pacifism
- id = "pacifism_debuff"
- alert_type = null
- duration = 40 SECONDS
-
-/datum/status_effect/pacifism/on_apply()
- ADD_TRAIT(owner, TRAIT_PACIFISM, id)
- return ..()
-
-/datum/status_effect/pacifism/on_remove()
- REMOVE_TRAIT(owner, TRAIT_PACIFISM, id)
-
-/datum/status_effect/pacifism/batterer
- id = "pacifism_debuff_batterer"
- alert_type = null
- duration = 10 SECONDS
-
-// used to track if hitting someone with a cult dagger/sword should stamina crit.
-/datum/status_effect/cult_stun_mark
- id = "cult_stun"
- duration = 10 SECONDS // when the knockdown ends, the mark disappears.
- alert_type = null
- var/mutable_appearance/overlay
-
-/datum/status_effect/cult_stun_mark/on_apply()
- . = ..()
- if(!isliving(owner))
- return
- overlay = mutable_appearance('icons/effects/cult_effects.dmi', "cult-mark", ABOVE_MOB_LAYER)
- owner.add_overlay(overlay)
-
-/datum/status_effect/cult_stun_mark/on_remove()
- owner.cut_overlay(overlay)
-
-/datum/status_effect/cult_stun_mark/proc/trigger()
- owner.apply_damage(60, STAMINA)
- owner.Silence(6 SECONDS) // refresh the silence
- qdel(src)
-
-/datum/status_effect/bluespace_slowdown
- id = "bluespace_slowdown"
- alert_type = null
- duration = 15 SECONDS
-
-/datum/status_effect/bluespace_slowdown/on_apply()
- owner.next_move_modifier *= 2
- return ..()
-
-/datum/status_effect/bluespace_slowdown/on_remove()
- owner.next_move_modifier /= 2
-
-/datum/status_effect/shadow_boxing
- id = "shadow barrage"
- alert_type = null
- duration = 10 SECONDS
- tick_interval = 0.4 SECONDS
- var/damage = 8
- var/source_UID
-
-/datum/status_effect/shadow_boxing/on_creation(mob/living/new_owner, mob/living/source)
- . = ..()
- source_UID = source.UID()
-
-/datum/status_effect/shadow_boxing/tick()
- var/mob/living/attacker = locateUID(source_UID)
- if(attacker in view(owner, 2))
- attacker.do_attack_animation(owner, ATTACK_EFFECT_PUNCH)
- owner.apply_damage(damage, BRUTE)
- shadow_to_animation(get_turf(attacker), get_turf(owner), attacker)
-
-/datum/status_effect/cling_tentacle
- id = "cling_tentacle"
- alert_type = null
- duration = 3 SECONDS
-
-/datum/status_effect/cling_tentacle/on_apply()
- ADD_TRAIT(owner, TRAIT_IMMOBILIZED, "[id]")
- return ..()
-
-/datum/status_effect/cling_tentacle/on_remove()
- REMOVE_TRAIT(owner, TRAIT_IMMOBILIZED, "[id]")
-
-// start of `living` level status procs.
-
-/**
- * # Confusion
- *
- * Prevents moving straight, sometimes changing movement direction at random.
- * Decays at a rate of 1 per second.
- */
-/datum/status_effect/transient/confusion
- id = "confusion"
- var/image/overlay
-
-/datum/status_effect/transient/confusion/tick()
- . = ..()
- if(!.)
- return
- if(!owner.stat) //add or remove the overlay if they are alive or unconscious/dead
- add_overlay()
- else if(overlay)
- owner.cut_overlay(overlay)
- overlay = null
-
-/datum/status_effect/transient/confusion/proc/add_overlay()
- if(overlay)
- return
- var/matrix/M = matrix()
- M.Scale(0.6)
- overlay = image('icons/effects/effects.dmi', "confusion", pixel_y = 20)
- overlay.transform = M
- owner.add_overlay(overlay)
-
-/datum/status_effect/transient/confusion/on_remove()
- owner.cut_overlay(overlay)
- overlay = null
- return ..()
-
-/**
- * # Dizziness
- *
- * Slightly offsets the client's screen randomly every tick.
- * Decays at a rate of 1 per second, or 5 when resting.
- */
-/datum/status_effect/transient/dizziness
- id = "dizziness"
- var/px_diff = 0
- var/py_diff = 0
-
-/datum/status_effect/transient/dizziness/on_remove()
- if(owner.client)
- // smoothly back to normal
- animate(owner.client, 0.2 SECONDS, pixel_x = -px_diff, pixel_y = -py_diff, flags = ANIMATION_PARALLEL)
- return ..()
-
-/datum/status_effect/transient/dizziness/tick()
- . = ..()
- if(!.)
- return
- var/dir = sin(world.time)
- var/amplitude = min(strength * 0.003, 32)
- px_diff = cos(world.time * 3) * amplitude * dir
- py_diff = sin(world.time * 3) * amplitude * dir
- owner.client?.pixel_x = px_diff
- owner.client?.pixel_y = py_diff
-
-/datum/status_effect/transient/dizziness/calc_decay()
- return (-0.2 + (IS_HORIZONTAL(owner) ? -0.8 : 0)) SECONDS
-
-/**
- * # Drowsiness
- *
- * Slows down and causes eye blur, with a 5% chance of falling asleep for a short time.
- * Decays at a rate of 1 per second, or 5 when resting.
- */
-/datum/status_effect/transient/drowsiness
- id = "drowsiness"
-
-/datum/status_effect/transient/drowsiness/tick()
- . = ..()
- if(!.)
- return
- owner.EyeBlurry(4 SECONDS)
- if(prob(0.5))
- owner.AdjustSleeping(2 SECONDS)
- owner.Paralyse(10 SECONDS)
-
-/datum/status_effect/transient/drowsiness/calc_decay()
- return (-0.2 + (IS_HORIZONTAL(owner) ? -0.8 : 0)) SECONDS
-
-/datum/status_effect/pepper_spray
- id = "pepperspray"
- duration = 10 SECONDS
- status_type = STATUS_EFFECT_REFRESH
- tick_interval = -1
- alert_type = null
-
-/datum/status_effect/pepper_spray/on_apply()
- . = ..()
- to_chat(owner, "Your throat burns!")
- owner.AdjustConfused(12 SECONDS, bound_upper = 20 SECONDS)
- owner.Slowed(4 SECONDS)
- owner.apply_damage(40, STAMINA)
-
-/datum/status_effect/pepper_spray/refresh()
- . = ..()
- owner.AdjustConfused(12 SECONDS, bound_upper = 20 SECONDS)
- owner.Slowed(4 SECONDS)
- owner.apply_damage(20, STAMINA)
-
-/**
- * # Drukenness
- *
- * Causes a myriad of status effects and other afflictions the stronger it is.
- * Decays at a rate of 1 per second if no alcohol remains inside.
- */
-/datum/status_effect/transient/drunkenness
- id = "drunkenness"
- var/alert_thrown = FALSE
-
-// the number of seconds of the status effect required for each effect to kick in.
-#define THRESHOLD_SLUR 60 SECONDS
-#define THRESHOLD_BRAWLING 60 SECONDS
-#define THRESHOLD_CONFUSION 80 SECONDS
-#define THRESHOLD_SPARK 100 SECONDS
-#define THRESHOLD_VOMIT 120 SECONDS
-#define THRESHOLD_BLUR 150 SECONDS
-#define THRESHOLD_COLLAPSE 150 SECONDS
-#define THRESHOLD_FAINT 180 SECONDS
-#define THRESHOLD_BRAIN_DAMAGE 240 SECONDS
-#define DRUNK_BRAWLING /datum/martial_art/drunk_brawling
-
-/datum/status_effect/transient/drunkenness/on_remove()
- if(alert_thrown)
- alert_thrown = FALSE
- owner.clear_alert("drunk")
- owner.sound_environment_override = SOUND_ENVIRONMENT_NONE
- if(owner.mind && istype(owner.mind.martial_art, DRUNK_BRAWLING))
- owner.mind.martial_art.remove(owner)
- return ..()
-
-/datum/status_effect/transient/drunkenness/tick()
- . = ..()
- if(!.)
- return
-
- // Adjust actual drunkenness based on trait and organ presence
- var/alcohol_resistance = 1
- var/actual_strength = strength
- var/datum/mind/M = owner.mind
- var/is_robot = ismachineperson(owner) || issilicon(owner)
-
- if(HAS_TRAIT(owner, TRAIT_ALCOHOL_TOLERANCE))
- alcohol_resistance = 2
-
- actual_strength /= alcohol_resistance
-
- var/obj/item/organ/internal/liver/L
- if(!is_robot)
- L = owner.get_int_organ(/obj/item/organ/internal/liver)
- var/liver_multiplier = 5 // no liver? get shitfaced
- if(L)
- liver_multiplier = L.alcohol_intensity
- actual_strength *= liver_multiplier
-
- // THRESHOLD_SLUR (60 SECONDS)
- if(actual_strength >= THRESHOLD_SLUR)
- owner.Slur(actual_strength)
- if(!alert_thrown)
- alert_thrown = TRUE
- owner.throw_alert("drunk", /atom/movable/screen/alert/drunk)
- owner.sound_environment_override = SOUND_ENVIRONMENT_PSYCHOTIC
- // THRESHOLD_BRAWLING (60 SECONDS)
- if(M)
- if(actual_strength >= THRESHOLD_BRAWLING)
- if(!istype(M.martial_art, DRUNK_BRAWLING))
- var/datum/martial_art/drunk_brawling/MA = new
- MA.teach(owner, TRUE)
- else if(istype(M.martial_art, DRUNK_BRAWLING))
- M.martial_art.remove(owner)
- // THRESHOLD_CONFUSION (80 SECONDS)
- if(actual_strength >= THRESHOLD_CONFUSION && prob(0.33))
- owner.AdjustConfused(6 SECONDS / alcohol_resistance, bound_lower = 2 SECONDS, bound_upper = 1 MINUTES)
- // THRESHOLD_SPARK (100 SECONDS)
- if(is_robot && actual_strength >= THRESHOLD_SPARK && prob(0.25))
- do_sparks(3, 1, owner)
- // THRESHOLD_VOMIT (120 SECONDS)
- if(!is_robot && actual_strength >= THRESHOLD_VOMIT && prob(0.08))
- owner.fakevomit()
- // THRESHOLD_BLUR (150 SECONDS)
- if(actual_strength >= THRESHOLD_BLUR)
- owner.EyeBlurry(20 SECONDS / alcohol_resistance)
- // THRESHOLD_COLLAPSE (150 SECONDS)
- if(actual_strength >= THRESHOLD_COLLAPSE && prob(0.1))
- owner.emote("collapse")
- do_sparks(3, 1, src)
- // THRESHOLD_FAINT (180 SECONDS)
- if(actual_strength >= THRESHOLD_FAINT && prob(0.1))
- owner.Paralyse(10 SECONDS / alcohol_resistance)
- owner.Drowsy(60 SECONDS / alcohol_resistance)
- if(L)
- L.receive_damage(1, TRUE)
- if(!is_robot)
- owner.adjustToxLoss(1)
- // THRESHOLD_BRAIN_DAMAGE (240 SECONDS)
- if(actual_strength >= THRESHOLD_BRAIN_DAMAGE && prob(0.1))
- owner.adjustBrainLoss(1)
-
-#undef THRESHOLD_SLUR
-#undef THRESHOLD_BRAWLING
-#undef THRESHOLD_CONFUSION
-#undef THRESHOLD_SPARK
-#undef THRESHOLD_VOMIT
-#undef THRESHOLD_BLUR
-#undef THRESHOLD_COLLAPSE
-#undef THRESHOLD_FAINT
-#undef THRESHOLD_BRAIN_DAMAGE
-#undef DRUNK_BRAWLING
-
-/datum/status_effect/transient/drunkenness/calc_decay()
- if(ishuman(owner))
- var/mob/living/carbon/human/H = owner
- if(H.has_booze())
- return 0
- return -0.2 SECONDS
-
-/datum/status_effect/transient/cult_slurring
- id = "cult_slurring"
-
-/datum/status_effect/incapacitating
- id = "incapacitating"
- tick_interval = 0
- status_type = STATUS_EFFECT_REPLACE
- alert_type = null
- var/needs_update_stat = FALSE
-
-/datum/status_effect/incapacitating/on_creation(mob/living/new_owner, set_duration)
- if(isnum(set_duration))
- if(ishuman(new_owner))
- var/mob/living/carbon/human/H = new_owner
- set_duration = H.dna.species.spec_stun(H, set_duration)
- duration = set_duration
- if(!duration)
- return FALSE
- . = ..()
- if(. && (needs_update_stat || issilicon(owner)))
- owner.update_stat()
-
-
-/datum/status_effect/incapacitating/on_remove()
- if(needs_update_stat || issilicon(owner)) //silicons need stat updates
- owner.update_stat()
- return ..()
-
-//FLOORED - forces the victim prone.
-/datum/status_effect/incapacitating/floored
- id = "floored"
-
-/datum/status_effect/incapacitating/floored/on_apply()
- . = ..()
- if(!.)
- return
- ADD_TRAIT(owner, TRAIT_FLOORED, "[id]")
-
-/datum/status_effect/incapacitating/floored/on_remove()
- REMOVE_TRAIT(owner, TRAIT_FLOORED, "[id]")
- return ..()
-
-
-//STUN - prevents movement and actions, victim stays standing
-/datum/status_effect/incapacitating/stun
- id = "stun"
-
-/datum/status_effect/incapacitating/stun/on_apply()
- . = ..()
- if(!.)
- return
- ADD_TRAIT(owner, TRAIT_IMMOBILIZED, "[id]")
- ADD_TRAIT(owner, TRAIT_HANDS_BLOCKED, "[id]")
-
-/datum/status_effect/incapacitating/stun/on_remove()
- REMOVE_TRAIT(owner, TRAIT_IMMOBILIZED, "[id]")
- REMOVE_TRAIT(owner, TRAIT_HANDS_BLOCKED, "[id]")
- return ..()
-
-//IMMOBILIZED - prevents movement, victim can still stand and act
-/datum/status_effect/incapacitating/immobilized
- id = "immobilized"
-
-/datum/status_effect/incapacitating/immobilized/on_apply()
- . = ..()
- if(!.)
- return
- ADD_TRAIT(owner, TRAIT_IMMOBILIZED, "[id]")
-
-/datum/status_effect/incapacitating/immobilized/on_remove()
- REMOVE_TRAIT(owner, TRAIT_IMMOBILIZED, "[id]")
- return ..()
-
-//WEAKENED - prevents movement and action, victim falls over
-/datum/status_effect/incapacitating/weakened
- id = "weakened"
-
-/datum/status_effect/incapacitating/weakened/on_apply()
- . = ..()
- if(!.)
- return
- ADD_TRAIT(owner, TRAIT_IMMOBILIZED, "[id]")
- ADD_TRAIT(owner, TRAIT_FLOORED, "[id]")
- ADD_TRAIT(owner, TRAIT_HANDS_BLOCKED, "[id]")
-
-/datum/status_effect/incapacitating/weakened/on_remove()
- REMOVE_TRAIT(owner, TRAIT_IMMOBILIZED, "[id]")
- REMOVE_TRAIT(owner, TRAIT_FLOORED, "[id]")
- REMOVE_TRAIT(owner, TRAIT_HANDS_BLOCKED, "[id]")
- return ..()
-
-//PARALYZED - prevents movement and action, victim falls over, victim cannot hear or see.
-/datum/status_effect/incapacitating/paralyzed
- id = "paralyzed"
- needs_update_stat = TRUE
-
-/datum/status_effect/incapacitating/paralyzed/on_apply()
- . = ..()
- if(!.)
- return
- ADD_TRAIT(owner, TRAIT_KNOCKEDOUT, "[id]")
-
-/datum/status_effect/incapacitating/paralyzed/on_remove()
- REMOVE_TRAIT(owner, TRAIT_KNOCKEDOUT, "[id]")
- return ..()
-
-//SLEEPING - victim falls over, cannot act, cannot see or hear, heals under certain conditions.
-/datum/status_effect/incapacitating/sleeping
- id = "sleeping"
- tick_interval = 2 SECONDS
- needs_update_stat = TRUE
- /// Whether we decided to take a nap on our own.
- /// As opposed to being hard knocked out with N2O or similar.
- var/voluntary = FALSE
-
-/datum/status_effect/incapacitating/sleeping/on_creation(mob/living/new_owner, set_duration, voluntary = FALSE)
- ..()
- src.voluntary = voluntary
-
-/datum/status_effect/incapacitating/sleeping/on_apply()
- . = ..()
- if(!.)
- return
- ADD_TRAIT(owner, TRAIT_KNOCKEDOUT, "[id]")
-
-/datum/status_effect/incapacitating/sleeping/on_remove()
- REMOVE_TRAIT(owner, TRAIT_KNOCKEDOUT, "[id]")
- return ..()
-
-/datum/status_effect/incapacitating/sleeping/tick()
- if(!iscarbon(owner))
- return
-
- var/mob/living/carbon/dreamer = owner
-
- if(dreamer.mind?.has_antag_datum(/datum/antagonist/vampire))
- var/mob/living/carbon/human/V = owner
- if(istype(V.loc, /obj/structure/closet/coffin))
- V.adjustBruteLoss(-1)
- V.adjustFireLoss(-1)
- V.adjustToxLoss(-1)
- V.adjustOxyLoss(-1)
- V.adjustCloneLoss(-0.5)
- if(V.HasDisease(/datum/disease/critical/heart_failure) && prob(25))
- for(var/datum/disease/critical/heart_failure/HF in V.viruses)
- HF.cure()
- dreamer.handle_dreams()
- dreamer.adjustStaminaLoss(-10)
- var/comfort = 1
- if(istype(dreamer.buckled, /obj/structure/bed))
- var/obj/structure/bed/bed = dreamer.buckled
- comfort += bed.comfort
- for(var/obj/item/bedsheet/bedsheet in range(dreamer.loc,0))
- if(bedsheet.loc != dreamer.loc) //bedsheets in your backpack/neck don't give you comfort
- continue
- comfort += bedsheet.comfort
- break //Only count the first bedsheet
- if(dreamer.get_drunkenness() > 0)
- comfort += 1 //Aren't naps SO much better when drunk?
- dreamer.AdjustDrunk(-0.4 SECONDS * comfort) //reduce drunkenness while sleeping.
- if(comfort > 1 && prob(3))//You don't heal if you're just sleeping on the floor without a blanket.
- dreamer.adjustBruteLoss(-1 * comfort, FALSE)
- dreamer.adjustFireLoss(-1 * comfort)
- if(prob(10) && dreamer.health && dreamer.health_hud_override != HEALTH_HUD_OVERRIDE_CRIT)
- dreamer.emote("snore")
-
-
-//SLOWED - slows down the victim for a duration and a given slowdown value.
-/datum/status_effect/incapacitating/slowed
- id = "slowed"
- var/slowdown_value = 10 // defaults to this value if none is specified
-
-/datum/status_effect/incapacitating/slowed/on_creation(mob/living/new_owner, set_duration, _slowdown_value)
- . = ..()
- if(isnum(_slowdown_value))
- slowdown_value = _slowdown_value
-
-// Directional slow - Like slowed, but only if you're moving in a certain direction.
-/datum/status_effect/incapacitating/directional_slow
- id = "directional_slow"
- var/direction
- var/slowdown_value = 10 // defaults to this value if none is specified
-
-/datum/status_effect/incapacitating/directional_slow/on_creation(mob/living/new_owner, set_duration, _direction, _slowdown_value)
- . = ..()
- direction = _direction
- if(isnum(_slowdown_value))
- slowdown_value = _slowdown_value
-
-/datum/status_effect/transient/silence
- id = "silenced"
-
-/datum/status_effect/transient/silence/on_apply()
- . = ..()
- ADD_TRAIT(owner, TRAIT_MUTE, id)
-
-/datum/status_effect/transient/silence/on_remove()
- . = ..()
- REMOVE_TRAIT(owner, TRAIT_MUTE, id)
-
-/// this one will mute all emote sounds including gasps
-/datum/status_effect/transient/silence/absolute
- id = "abssilenced"
-
-/datum/status_effect/transient/deaf
- id = "deafened"
-
-/datum/status_effect/transient/deaf/on_apply()
- . = ..()
- ADD_TRAIT(owner, TRAIT_DEAF, EAR_DAMAGE)
-
-/datum/status_effect/transient/deaf/on_remove()
- . = ..()
- REMOVE_TRAIT(owner, TRAIT_DEAF, EAR_DAMAGE)
-
-/datum/status_effect/transient/no_oxy_heal
- id = "no_oxy_heal"
-
-/datum/status_effect/transient/jittery
- id = "jittering"
-
-/datum/status_effect/transient/jittery/on_apply()
- . = ..()
- owner.do_jitter_animation(strength / 20, 1)
-
-/datum/status_effect/transient/jittery/tick()
- . = ..()
- if(!.)
- return
- owner.do_jitter_animation(strength / 20, 1)
-
-/datum/status_effect/transient/jittery/calc_decay()
- return (-0.2 + (IS_HORIZONTAL(owner) ? -0.8 : 0)) SECONDS
-
-/datum/status_effect/transient/stammering
- id = "stammer"
-
-/datum/status_effect/transient/slurring
- id = "slurring"
-
-/datum/status_effect/transient/lose_breath
- id = "lose_breath"
-
-#define HALLUCINATE_COOLDOWN_MIN 20 SECONDS
-#define HALLUCINATE_COOLDOWN_MAX 50 SECONDS
-/// This is multiplied with [/datum/status_effect/transient/var/strength] to determine the final cooldown. A higher hallucination value means shorter cooldown.
-#define HALLUCINATE_COOLDOWN_FACTOR 0.003
-/// Percentage defining the chance at which an hallucination may spawn past the cooldown.
-#define HALLUCINATE_CHANCE 80
-// Severity weights, should sum up to 100!
-#define HALLUCINATE_MINOR_WEIGHT 60
-#define HALLUCINATE_MODERATE_WEIGHT 30
-#define HALLUCINATE_MAJOR_WEIGHT 10
-
-/datum/status_effect/transient/hallucination
- id = "hallucination"
- var/next_hallucination = 0
-
-/datum/status_effect/transient/hallucination/tick()
- . = ..()
- if(!.)
- return
-
- if(next_hallucination > world.time)
- return
-
- next_hallucination = world.time + rand(HALLUCINATE_COOLDOWN_MIN, HALLUCINATE_COOLDOWN_MAX) / (strength * HALLUCINATE_COOLDOWN_FACTOR)
- if(!prob(HALLUCINATE_CHANCE))
- return
-
- // Pick a severity
- var/severity = HALLUCINATE_MINOR
- switch(rand(100))
- if(0 to HALLUCINATE_MINOR_WEIGHT)
- severity = HALLUCINATE_MINOR
- if((HALLUCINATE_MINOR_WEIGHT + 1) to (HALLUCINATE_MINOR_WEIGHT + HALLUCINATE_MODERATE_WEIGHT))
- severity = HALLUCINATE_MODERATE
- if((HALLUCINATE_MINOR_WEIGHT + HALLUCINATE_MODERATE_WEIGHT + 1) to 100)
- severity = HALLUCINATE_MAJOR
-
- hallucinate(pickweight(GLOB.hallucinations[severity]))
-
-
-/**
- * Spawns an hallucination for the mob.
- *
- * Arguments:
- * * H - The type path of the hallucination to spawn.
- */
-/datum/status_effect/transient/hallucination/proc/hallucinate(hallucination_type)
- ASSERT(ispath(hallucination_type))
- if(owner.ckey)
- add_attack_logs(null, owner, "Received hallucination [hallucination_type]", ATKLOG_ALL)
- return new hallucination_type(get_turf(owner), owner)
-
-#undef HALLUCINATE_COOLDOWN_MIN
-#undef HALLUCINATE_COOLDOWN_MAX
-#undef HALLUCINATE_COOLDOWN_FACTOR
-#undef HALLUCINATE_CHANCE
-#undef HALLUCINATE_MINOR_WEIGHT
-#undef HALLUCINATE_MODERATE_WEIGHT
-#undef HALLUCINATE_MAJOR_WEIGHT
-
-/datum/status_effect/transient/eye_blurry
- id = "eye_blurry"
-
-/datum/status_effect/transient/eye_blurry/on_apply()
- owner.update_blurry_effects()
- . = ..()
-
-/datum/status_effect/transient/eye_blurry/on_remove()
- owner.update_blurry_effects()
-
-/datum/status_effect/transient/eye_blurry/calc_decay()
- if(ishuman(owner))
- var/mob/living/carbon/human/H = owner
-
- if(isnull(H.dna.species.vision_organ)) //species has no eyes
- return ..()
-
- var/obj/item/organ/vision = H.get_int_organ(H.dna.species.vision_organ)
-
- if(!vision || vision.is_bruised() || vision.is_broken()) // doesn't decay if you have damaged eyesight.
- return 0
-
- if(istype(H.glasses, /obj/item/clothing/glasses/sunglasses/blindfold)) // decays faster if you rest your eyes with a blindfold.
- return -1 SECONDS
- return ..() //default decay rate
-
-
-/datum/status_effect/transient/blindness
- id = "blindness"
-
-/datum/status_effect/transient/blindness/on_apply()
- . = ..()
- owner.update_blind_effects()
-
-/datum/status_effect/transient/blindness/on_remove()
- owner.update_blind_effects()
-
-/datum/status_effect/transient/blindness/calc_decay()
- if(ishuman(owner))
- var/mob/living/carbon/human/H = owner
- if(HAS_TRAIT(owner, TRAIT_BLIND))
- return 0
-
- if(isnull(H.dna.species.vision_organ)) // species that have no eyes
- return ..()
-
- var/obj/item/organ/vision = H.get_int_organ(H.dna.species.vision_organ)
-
- if(!vision || vision.is_broken() || vision.is_bruised()) //got no eyes or broken eyes
- return 0
-
- return ..() //default decay rate
-
-/datum/status_effect/transient/drugged
- id = "drugged"
-
-/datum/status_effect/transient/drugged/on_apply()
- . = ..()
- owner.update_druggy_effects()
-
-/datum/status_effect/transient/drugged/on_remove()
- owner.update_druggy_effects()
-
-#define FAKE_COLD 1
-#define FAKE_FOOD_POISONING 2
-#define FAKE_RETRO_VIRUS 3
-#define FAKE_TURBERCULOSIS 4
-#define FAKE_BRAINROT 5
-
-/datum/status_effect/fake_virus
- id = "fake_virus"
- duration = 3 MINUTES
- status_type = STATUS_EFFECT_REPLACE
- tick_interval = 2
- alert_type = null
- /// So you dont get the most intense messages immediately
- var/msg_stage = 0
- /// Which disease we are going to fake?
- var/current_fake_disease
- /// Fake virus messages by with three stages
- var/list/fake_msg
- /// Fake virus emotes with three stages
- var/list/fake_emote
-
-/datum/status_effect/fake_virus/on_creation()
- current_fake_disease = pick(FAKE_COLD, FAKE_FOOD_POISONING, FAKE_RETRO_VIRUS, FAKE_TURBERCULOSIS, FAKE_BRAINROT)
- switch(current_fake_disease)
- if(FAKE_COLD)
- fake_msg = list(
- list("Your throat feels sore.", "Mucous runs down the back of your throat."),
- list("Your muscles ache.", "Your stomach hurts."),
- list("Your muscles ache.", "Your stomach hurts.")
- )
- fake_emote = list(
- list("sneeze", "cough"),
- list("sneeze", "cough"),
- list("sneeze", "cough")
- )
- if(FAKE_FOOD_POISONING)
- fake_msg = list(
- list("Your stomach feels weird.", "You feel queasy."),
- list("Your stomach aches.", "You feel nauseous."),
- list("Your stomach hurts.", "You feel sick.")
- )
- fake_emote = list(
- list(),
- list("groan"),
- list("groan", "moan")
- )
- if(FAKE_RETRO_VIRUS)
- fake_msg = list(
- list("Your head hurts.", "You feel a tingling sensation in your chest.", "You feel angry."),
- list("Your skin feels loose.", "You feel very strange.", "You feel a stabbing pain in your head!", "Your stomach churns."),
- list("Your entire body vibrates.")
- )
- fake_emote = list(
- list(),
- list(),
- list()
- )
- if(FAKE_TURBERCULOSIS)
- fake_msg = list(
- list("Your chest hurts.", "Your stomach violently rumbles!", "You feel a cold sweat form."),
- list("You feel a sharp pain from your lower chest!", "You feel air escape from your lungs painfully."),
- list("You feel uncomfortably hot...", "You feel like unzipping your jumpsuit", "You feel like taking off some clothes...")
- )
- fake_emote = list(
- list("cough"),
- list("gasp"),
- list()
- )
- else // FAKE_BRAINROT
- fake_msg = list(
- list("You don't feel like yourself."),
- list("Your try to remember something important...but can't."),
- list("Strange buzzing fills your head, removing all thoughts.")
- )
- fake_emote = list(
- list("blink", "yawn"),
- list("stare", "drool"),
- list("stare", "drool")
- )
- . = ..()
-
-/datum/status_effect/fake_virus/tick()
- var/selected_fake_msg
- var/selected_fake_emote
- switch(msg_stage)
- if(0 to 300)
- if(prob(1)) // First stage starts slow, stage 2 and 3 trigger fake msgs/emotes twice as often
- if(prob(50) || !length(fake_emote[1])) // 50% chance to trigger either a msg or emote, 100% if it doesnt have an emote
- selected_fake_msg = safepick(fake_msg[1])
- else
- selected_fake_emote = safepick(fake_emote[1])
- if(301 to 600)
- if(prob(2))
- if(prob(50) || !length(fake_emote[2]))
- selected_fake_msg = safepick(fake_msg[2])
- else
- selected_fake_emote = safepick(fake_emote[2])
- else
- if(prob(2))
- if(prob(50) || !length(fake_emote[3]))
- selected_fake_msg = safepick(fake_msg[3])
- else
- selected_fake_emote = safepick(fake_emote[3])
-
- if(selected_fake_msg)
- to_chat(owner, selected_fake_msg)
- else if(selected_fake_emote)
- owner.emote(selected_fake_emote)
- msg_stage++
-
-#undef FAKE_COLD
-#undef FAKE_FOOD_POISONING
-#undef FAKE_RETRO_VIRUS
-#undef FAKE_TURBERCULOSIS
-#undef FAKE_BRAINROT
-
-/datum/status_effect/cryo_beam
- id = "cryo beam"
- alert_type = null
- duration = -1 //Kill it, get out of sight, or be killed. Jump boots are *required*
- tick_interval = 0.5 SECONDS
- var/damage = 0.75
- var/source_UID
-
-/datum/status_effect/cryo_beam/on_creation(mob/living/new_owner, mob/living/source)
- . = ..()
- source_UID = source.UID()
-
-/datum/status_effect/cryo_beam/tick()
- var/mob/living/simple_animal/hostile/megafauna/ancient_robot/attacker = locateUID(source_UID)
- if(!(owner in view(attacker, 8)))
- qdel(src)
- return
-
- owner.apply_damage(damage, BURN)
- owner.bodytemperature = max(0, owner.bodytemperature - 20)
- owner.Beam(attacker.beam, icon_state = "medbeam", time = 0.5 SECONDS)
- for(var/datum/reagent/R in owner.reagents.reagent_list)
- owner.reagents.remove_reagent(R.id, 0.75)
- if(prob(10))
- to_chat(owner, "Your blood freezes in your veins, get away!")
-
-/datum/status_effect/bubblegum_curse
- id = "bubblegum curse"
- alert_type = /atom/movable/screen/alert/status_effect/bubblegum_curse
- duration = -1 //Kill it. There is no other option.
- tick_interval = 1 SECONDS
- /// The damage the status effect does per tick.
- var/damage = 0.75
- var/source_UID
- /// Are we starting the process to check if the person has still gotten out of range of bubble / crossed zlvls.
- var/coward_checking = FALSE
-
-/datum/status_effect/bubblegum_curse/on_creation(mob/living/new_owner, mob/living/source)
- . = ..()
- source_UID = source.UID()
- owner.overlay_fullscreen("Bubblegum", /atom/movable/screen/fullscreen/stretch/fog, 1)
-
-/datum/status_effect/bubblegum_curse/tick()
- var/mob/living/simple_animal/hostile/megafauna/bubblegum/attacker = locateUID(source_UID)
- if(!attacker || attacker.loc == null)
- qdel(src)
- if(attacker.health <= attacker.maxHealth / 2)
- owner.clear_fullscreen("Bubblegum")
- owner.overlay_fullscreen("Bubblegum", /atom/movable/screen/fullscreen/stretch/fog, 2)
- if(!coward_checking)
- if(owner.z != attacker.z)
- addtimer(CALLBACK(src, PROC_REF(onstation_coward_callback)), 12 SECONDS)
- coward_checking = TRUE
- else if(get_dist(attacker, owner) >= 25)
- addtimer(CALLBACK(src, PROC_REF(runaway_coward_callback)), 12 SECONDS)
- coward_checking = TRUE
-
- owner.apply_damage(damage, BRUTE)
- if(ishuman(owner))
- var/mob/living/carbon/human/H = owner
- H.bleed(0.33)
- if(prob(5))
- to_chat(owner, "[pick("You feel your sins crawling on your back.", "You felt your sins weighing on your neck.", "You feel your blood pulsing inside you.", "YOU'LL NEVER ESCAPE ME", "YOU'LL DIE FOR INSULTING ME LIKE THIS")]")
-
-/datum/status_effect/bubblegum_curse/on_remove()
- owner.clear_fullscreen("Bubblegum")
-
-/datum/status_effect/bubblegum_curse/proc/onstation_coward_callback()
- coward_checking = FALSE
- var/mob/living/simple_animal/hostile/megafauna/bubblegum/attacker = locateUID(source_UID)
- if(owner.z != attacker.z)
- to_chat(owner, "YOU CHALLENGE ME LIKE THIS... AND YOU RUN WITH YOUR FALSE MAGICS?")
- else
- return
- SLEEP_CHECK_QDEL(2 SECONDS)
- to_chat(owner, "REALLY?")
- SLEEP_CHECK_QDEL(2 SECONDS)
- to_chat(owner, "SUCH INSOLENCE!")
- SLEEP_CHECK_QDEL(2 SECONDS)
- to_chat(owner, "SO PATHETIC...")
- SLEEP_CHECK_QDEL(2 SECONDS)
- to_chat(owner, "...SO FOOLISH!")
- get_over_here()
-
-/datum/status_effect/bubblegum_curse/proc/runaway_coward_callback()
- coward_checking = FALSE
- var/mob/living/simple_animal/hostile/megafauna/bubblegum/attacker = locateUID(source_UID)
- if(get_dist(attacker, owner) >= 25)
- to_chat(owner, "My my, you can run FAST.")
- else
- return
- SLEEP_CHECK_QDEL(2 SECONDS)
- to_chat(owner, "I thought you wanted a true fight?")
- SLEEP_CHECK_QDEL(2 SECONDS)
- to_chat(owner, "Perhaps I was mistaken.")
- SLEEP_CHECK_QDEL(2 SECONDS)
- to_chat(owner, "You are a coward who does not want a fight...")
- SLEEP_CHECK_QDEL(2 SECONDS)
- to_chat(owner, "...BUT I WANT YOU DEAD!")
- get_over_here()
-
-/datum/status_effect/bubblegum_curse/proc/get_over_here()
- var/mob/living/simple_animal/hostile/megafauna/bubblegum/attacker = locateUID(source_UID)
- if(!attacker)
- return //Let's not nullspace
- if(attacker.loc == null)
- return //Extra emergency safety.
- var/turf/TA = get_turf(owner)
- owner.Immobilize(3 SECONDS)
- new /obj/effect/decal/cleanable/blood/bubblegum(TA)
- new /obj/effect/temp_visual/bubblegum_hands/rightsmack(TA)
- sleep(6)
- var/turf/TB = get_turf(owner)
- to_chat(owner, "[attacker] rends you!")
- playsound(TB, attacker.attack_sound, 100, TRUE, -1)
- owner.adjustBruteLoss(10)
- new /obj/effect/decal/cleanable/blood/bubblegum(TB)
- new /obj/effect/temp_visual/bubblegum_hands/leftsmack(TB)
- sleep(6)
- var/turf/TC = get_turf(owner)
- to_chat(owner, "[attacker] rends you!")
- playsound(TC, attacker.attack_sound, 100, TRUE, -1)
- owner.adjustBruteLoss(10)
- new /obj/effect/decal/cleanable/blood/bubblegum(TC)
- new /obj/effect/temp_visual/bubblegum_hands/rightsmack(TC)
- sleep(6)
- var/turf/TD = get_turf(owner)
- to_chat(owner, "[attacker] rends you!")
- playsound(TD, attacker.attack_sound, 100, TRUE, -1)
- owner.adjustBruteLoss(10)
- new /obj/effect/temp_visual/bubblegum_hands/leftpaw(TD)
- new /obj/effect/temp_visual/bubblegum_hands/leftthumb(TD)
- sleep(8)
- to_chat(owner, "[attacker] drags you through the blood!")
- playsound(TD, 'sound/misc/enter_blood.ogg', 100, TRUE, -1)
- var/turf/targetturf = get_step(attacker, attacker.dir)
- owner.forceMove(targetturf)
- playsound(targetturf, 'sound/misc/exit_blood.ogg', 100, TRUE, -1)
- addtimer(CALLBACK(attacker, TYPE_PROC_REF(/mob/living/simple_animal/hostile/megafauna/bubblegum, FindTarget), list(owner), 1), 2)
-
-/atom/movable/screen/alert/status_effect/bubblegum_curse
- name = "I SEE YOU"
- desc = "YOUR SOUL WILL BE MINE FOR YOUR INSOLENCE."
- icon_state = "bubblegumjumpscare"
-
-/atom/movable/screen/alert/status_effect/bubblegum_curse/Initialize(mapload)
- . = ..()
- START_PROCESSING(SSobj, src)
-
-/atom/movable/screen/alert/status_effect/bubblegum_curse/Destroy()
- STOP_PROCESSING(SSobj, src)
- return ..()
-
-/atom/movable/screen/alert/status_effect/bubblegum_curse/process()
- var/new_filter = isnull(get_filter("ray"))
- ray_filter_helper(1, 40,"#ce3030", 6, 20)
- if(new_filter)
- animate(get_filter("ray"), offset = 10, time = 10 SECONDS, loop = -1)
- animate(offset = 0, time = 10 SECONDS)
-
-
-/datum/status_effect/abductor_cooldown
- id = "abductor_cooldown"
- alert_type = /atom/movable/screen/alert/status_effect/abductor_cooldown
- duration = 10 SECONDS
-
-/atom/movable/screen/alert/status_effect/abductor_cooldown
- name = "Teleportation cooldown"
- desc = "Per article A-113, all experimentors must wait 10000 milliseconds between teleports in order to ensure no long term genetic or mental damage happens to experimentor or test subjects."
- icon_state = "bluespace"
-
-#define DEFAULT_MAX_CURSE_COUNT 5
-
-/// Status effect that gives the target miscellanous debuffs while throwing a status alert and causing them to smoke from the damage they're incurring.
-/// Purposebuilt for cursed slot machines.
-/datum/status_effect/cursed
- id = "cursed"
- alert_type = /atom/movable/screen/alert/status_effect/cursed
- /// The max number of curses a target can incur with this status effect.
- var/max_curse_count = DEFAULT_MAX_CURSE_COUNT
- /// The amount of times we have been "applied" to the target.
- var/curse_count = 0
- /// Raw probability we have to deal damage this tick.
- var/damage_chance = 10
- /// Are we currently in the process of sending a monologue?
- var/monologuing = FALSE
- /// The hand we are branded to.
- var/obj/item/organ/external/branded_hand = null
-
-/datum/status_effect/cursed/on_apply()
- RegisterSignal(owner, COMSIG_MOB_STATCHANGE, PROC_REF(on_stat_changed))
- RegisterSignal(owner, COMSIG_MOB_DEATH, PROC_REF(on_death))
- RegisterSignal(owner, COMSIG_CURSED_SLOT_MACHINE_USE, PROC_REF(check_curses))
- RegisterSignal(owner, COMSIG_CURSED_SLOT_MACHINE_LOST, PROC_REF(update_curse_count))
- RegisterSignal(SSdcs, COMSIG_GLOB_CURSED_SLOT_MACHINE_WON, PROC_REF(clear_curses))
- return ..()
-
-/datum/status_effect/cursed/Destroy()
- UnregisterSignal(SSdcs, COMSIG_GLOB_CURSED_SLOT_MACHINE_WON)
- branded_hand = null
- return ..()
-
-/// Checks the number of curses we have and returns information back to the slot machine. `max_curse_amount` is set by the slot machine itself.
-/datum/status_effect/cursed/proc/check_curses(mob/user, max_curse_amount)
- SIGNAL_HANDLER
- if(curse_count >= max_curse_amount)
- return SLOT_MACHINE_USE_CANCEL
-
- if(monologuing)
- to_chat(owner, "Your arm is resisting your attempts to pull the lever!") // listening to kitschy monologues to postpone your powergaming is the true curse here.
- return SLOT_MACHINE_USE_POSTPONE
-
-/// Handles the debuffs of this status effect and incrementing the number of curses we have.
-/datum/status_effect/cursed/proc/update_curse_count()
- SIGNAL_HANDLER
- curse_count++
-
- linked_alert?.update_appearance() // we may have not initialized it yet
-
- addtimer(CALLBACK(src, PROC_REF(handle_after_effects), 1 SECONDS)) // give it a second to let the failure sink in before we exact our toll
-
-/// Makes a nice lorey message about the curse level we're at. I think it's nice
-/datum/status_effect/cursed/proc/handle_after_effects()
- if(QDELETED(src))
- return
-
- monologuing = TRUE
- var/list/messages = list()
- switch(curse_count)
- if(1) // basically your first is a "freebie" that will still require urgent medical attention and will leave you smoking forever but could be worse tbh
- if(ishuman(owner))
- var/mob/living/carbon/human/human_owner = owner
- playsound(human_owner, 'sound/weapons/sear.ogg', 50, TRUE)
- var/obj/item/organ/external/affecting = human_owner.get_active_hand()
- branded_hand = affecting
-
- messages += "Your hand burns, and you quickly let go of the lever! You feel a little sick as the nerves deaden in your hand..."
- messages += "Some smoke appears to be coming out of your hand now, but it's not too bad..."
- messages += "Fucking stupid machine."
-
- if(2)
- messages += "The machine didn't burn you this time, it must be some arcane work of the brand recognizing a source..."
- messages += "Blisters and boils start to appear over your skin. Each one hissing searing hot steam out of its own pocket..."
- messages += "You understand that the machine tortures you with its simplistic allure. It can kill you at any moment, but it derives a sick satisfaction at forcing you to keep going."
- messages += "If you could get away from here, you might be able to live with some medical supplies. Is it too late to stop now?"
- messages += "As you shut your eyes to dwell on this conundrum, the brand surges in pain. You shudder to think what might happen if you go unconscious."
-
- if(3)
- owner.emote("cough")
- messages += "Your throat becomes to feel like it's slowly caking up with sand and dust. You eject the contents of the back of your throat onto your sleeve."
- messages += "It is sand. Crimson red. You've never felt so thirsty in your life, yet you don't trust your own hand to carry the glass to your lips."
- messages += "You get the sneaking feeling that if someone else were to win, that it might clear your curse too. Saving your life is a noble cause."
- messages += "Of course, you might have to not speak on the nature of this machine, in case they scamper off to leave you to die."
- messages += "Is it truly worth it to condemn someone to this fate to cure the manifestation of your own hedonistic urges? You'll have to decide quickly."
-
- if(4)
- messages += "A migraine swells over your head as your thoughts become hazy. Your hand desperately inches closer towards the slot machine for one final pull..."
- messages += "The ultimate test of mind over matter. You can jerk your own muscle back in order to prevent a terrible fate, but your life already is worth so little now."
- messages += "This is what they want, is it not? To witness your failure against itself? The compulsion carries you forward as a sinking feeling of dread fills your stomach."
- messages += "Paradoxically, where there is hopelessness, there is elation. Elation at the fact that there's still enough power in you for one more pull."
- messages += "Your legs desperately wish to jolt away on the thought of running away from this wretched machination, but your own arm remains complacent in the thought of seeing spinning wheels."
- messages += "The toll has already been exacted. There is no longer death on 'your' terms. Is your dignity worth more than your life?"
-
- if(5 to INFINITY)
- if(max_curse_count != DEFAULT_MAX_CURSE_COUNT) // this probably will only happen through admin schenanigans letting people stack up infinite curses or something
- to_chat(owner, "Do you still think you're in control?")
- return
-
- to_chat(owner, "Why couldn't I get one more try?!")
- owner.gib()
- qdel(src)
- return
- for(var/message in messages)
- to_chat(owner, message)
- sleep(3 SECONDS) // yes yes a bit fast but it can be a lot of text and i want the whole thing to send before the cooldown on the slot machine might expire
- monologuing = FALSE
-
-/// Cleans ourselves up and removes our curses. Meant to be done in a "positive" way, when the curse is broken. Directly use qdel otherwise.
-/datum/status_effect/cursed/proc/clear_curses()
- SIGNAL_HANDLER
-
- owner.visible_message(
- "The smoke slowly clears from [owner.name]...",
- "Your skin finally settles down and your throat no longer feels as dry... The brand disappearing confirms that the curse has been lifted.",)
- qdel(src)
-
-/// If our owner's stat changes, rapidly surge the damage chance.
-/datum/status_effect/cursed/proc/on_stat_changed()
- SIGNAL_HANDLER
- if(owner.stat == CONSCIOUS || owner.stat == DEAD) // reset on these two states
- damage_chance = initial(damage_chance)
- return
-
- to_chat(owner, "As your body crumbles, you feel the curse of the slot machine surge through your body!")
- damage_chance += 75 //ruh roh raggy
-
-/// If our owner dies without getting gibbed, we gib them, because fuck you baltamore
-/datum/status_effect/cursed/proc/on_death(mob/living/source, gibbed)
- SIGNAL_HANDLER
- if(!ishuman(owner))
- return
- if(gibbed)
- return
- var/mob/living/carbon/human/H = owner
- INVOKE_ASYNC(H, TYPE_PROC_REF(/mob, gib))
-
-/datum/status_effect/cursed/tick()
- if(curse_count <= 1)
- return // you get one "freebie" (single damage) to nudge you into thinking this is a bad idea before the house begins to win.
-
- // the house won.
- var/ticked_coefficient = rand(15, 40) * 2 / 100
- var/effective_percentile_chance = ((curse_count == 2 ? 1 : curse_count) * damage_chance * ticked_coefficient)
-
- if(prob(effective_percentile_chance))
- owner.apply_damages(
- brute = (curse_count * ticked_coefficient),
- burn = (curse_count * ticked_coefficient),
- oxy = (curse_count * ticked_coefficient),
- )
-
-/atom/movable/screen/alert/status_effect/cursed
- name = "Cursed!"
- desc = "The brand on your hand reminds you of your greed, yet you seem to be okay otherwise."
- icon_state = "cursed_by_slots"
-
-/atom/movable/screen/alert/status_effect/cursed/update_desc()
- . = ..()
- var/datum/status_effect/cursed/linked_effect = attached_effect
- var/curses = linked_effect.curse_count
- switch(curses)
- if(2)
- desc = "Your greed is catching up to you..."
- if(3)
- desc = "You really don't feel good right now... But why stop now?"
- if(4 to INFINITY)
- desc = "Real winners quit before they reach the ultimate prize."
-
-#undef DEFAULT_MAX_CURSE_COUNT
-
-/datum/status_effect/reversed_high_priestess
- id = "reversed_high_priestess"
- duration = 1 MINUTES
- status_type = STATUS_EFFECT_REFRESH
- tick_interval = 6 SECONDS
- alert_type = /atom/movable/screen/alert/status_effect/bubblegum_curse
-
-/datum/status_effect/reversed_high_priestess/tick()
- . = ..()
- new /obj/effect/bubblegum_warning(get_turf(owner))
-
-/obj/effect/bubblegum_warning
- name = "bloody rift"
- desc = "You feel like even being *near* this is a bad idea."
- icon = 'icons/obj/biomass.dmi'
- icon_state = "rift"
- color = "red"
-
-/obj/effect/bubblegum_warning/Initialize(mapload)
- . = ..()
- addtimer(CALLBACK(src, PROC_REF(slap_someone)), 2.5 SECONDS) //A chance to run away
-
-/obj/effect/bubblegum_warning/proc/slap_someone()
- new /obj/effect/abstract/bubblegum_rend_helper(get_turf(src), null, 10)
- qdel(src)
-
-/// The mob has been pushed by airflow recently, and won't automatically grab nearby objects to stop drifting.
-/datum/status_effect/unbalanced
- id = "unbalanced"
- duration = 1 SECONDS
- status_type = STATUS_EFFECT_REFRESH
- alert_type = /atom/movable/screen/alert/status_effect/unbalanced
-
-/atom/movable/screen/alert/status_effect/unbalanced
- name = "Unbalanced"
- desc = "You're being shoved around by airflow! You can resist this by moving, but moving against the wind will be slow."
- icon_state = "unbalanced"
-
-/datum/status_effect/c_foamed
- id = "c_foamed up"
- duration = 1 MINUTES
- status_type = STATUS_EFFECT_REFRESH
- tick_interval = 10 SECONDS
- var/foam_level = 1
- var/mutable_appearance/foam_overlay
-
-/datum/status_effect/c_foamed/on_apply()
- . = ..()
- foam_overlay = mutable_appearance('icons/obj/foam_blobs.dmi', "foamed_1")
- owner.add_overlay(foam_overlay)
- owner.next_move_modifier *= 1.5
- owner.Slowed(10 SECONDS, 1.5)
-
-/datum/status_effect/c_foamed/Destroy()
- if(owner)
- owner.cut_overlay(foam_overlay)
- owner.next_move_modifier /= 1.5
-
- QDEL_NULL(foam_overlay)
- return ..()
-
-/datum/status_effect/c_foamed/tick()
- . = ..()
- if(--foam_level <= 0)
- qdel(src)
- refresh_overlay()
-
-/datum/status_effect/c_foamed/refresh()
- . = ..()
- // Our max slow is 50 seconds
- foam_level = min(foam_level + 1, 5)
-
- refresh_overlay()
-
- if(foam_level == 5)
- owner.Paralyse(4 SECONDS)
-
-/datum/status_effect/c_foamed/proc/refresh_overlay()
- // Refresh overlay
- owner.cut_overlay(foam_overlay)
- QDEL_NULL(foam_overlay)
- foam_overlay = mutable_appearance('icons/obj/foam_blobs.dmi', "foamed_[foam_level]")
- owner.add_overlay(foam_overlay)
-
-/datum/status_effect/judo_armbar
- id = "armbar"
- duration = 5 SECONDS
- alert_type = null
- status_type = STATUS_EFFECT_REPLACE
-
-/datum/status_effect/rust_corruption
- alert_type = null
- id = "rust_turf_effects"
- tick_interval = 2 SECONDS
-
-/datum/status_effect/rust_corruption/tick()
- . = ..()
- if(issilicon(owner))
- owner.adjustBruteLoss(10)
- return
- //We don't have disgust, so...
- if(ishuman(owner))
- owner.adjustBrainLoss(2.5)
- owner.reagents?.remove_all(0.75)
- else
- owner.adjustBruteLoss(3) //Weaker than borgs but still constant.
-
-/// This is the threshold where the attack will stun on the last hit. Why? Because it is cool, that's why.
-#define FINISHER_THRESHOLD 7
-
-/datum/status_effect/temporal_slash
- id = "temporal_slash"
- duration = 3 SECONDS
- status_type = STATUS_EFFECT_REFRESH
- alert_type = null
- /// How many times the user has been cut. Each cut adds a damage value below
- var/cuts = 1
- /// How much damage the blade will do each slice
- var/damage_per_cut = 20
-
-/datum/status_effect/temporal_slash/on_creation(mob/living/new_owner, cut_damage = 20)
- . = ..()
- damage_per_cut = cut_damage
-
-/datum/status_effect/temporal_slash/refresh()
- cuts++
- return ..()
-
-/datum/status_effect/temporal_slash/on_remove()
- owner.apply_status_effect(STATUS_EFFECT_TEMPORAL_SLASH_FINISHER, cuts, damage_per_cut) //We apply this to a new status effect, to avoid refreshing while on_remove happens.
-
-/datum/status_effect/temporal_slash_finisher
- id = "temporal_slash_finisher"
- status_type = STATUS_EFFECT_UNIQUE
- alert_type = null
- tick_interval = 0.25 SECONDS
- /// How many times the user has been cut. Each cut adds a damage value below
- var/cuts = 1
- /// How much damage the blade will do each slice
- var/damage_per_cut = 20
- /// Have we done enough damage to trigger the finisher?
- var/finishing_cuts = FALSE
-
-/datum/status_effect/temporal_slash_finisher/on_creation(mob/living/new_owner, final_cuts = 1, cut_damage = 20)
- . = ..()
- cuts = final_cuts
- damage_per_cut = cut_damage
- if(ismegafauna(owner))
- damage_per_cut *= 4 //This will deal 40 damage bonus per cut on megafauna as a miner, and 80 as a wizard. To kill a megafauna, you need to hit it 48 times. You don't get the buffs of a crusher though. Also you already killed bubblegum, so, you know.
- if(cuts >= FINISHER_THRESHOLD)
- finishing_cuts = TRUE
- new /obj/effect/temp_visual/temporal_slash(get_turf(owner), owner)
-
-/datum/status_effect/temporal_slash_finisher/tick()
- . = ..()
- owner.visible_message("[owner] gets slashed by a cut through spacetime!", "You get slashed by a cut through spacetime!")
- playsound(owner, 'sound/weapons/rapierhit.ogg', 50, TRUE)
- owner.apply_damage(damage_per_cut, BRUTE, pick(BODY_ZONE_CHEST, BODY_ZONE_HEAD, BODY_ZONE_L_ARM, BODY_ZONE_L_LEG, BODY_ZONE_R_ARM, BODY_ZONE_R_LEG), 0, TRUE, null, FALSE)
- cuts--
- if(cuts <= 0)
- if(finishing_cuts)
- owner.Weaken(7 SECONDS)
- qdel(src)
- else
- new /obj/effect/temp_visual/temporal_slash(get_turf(owner), owner)
-
-#undef FINISHER_THRESHOLD
diff --git a/code/datums/status_effects/magic_disguise.dm b/code/datums/status_effects/magic_disguise.dm
deleted file mode 100644
index 16f0fb6591169..0000000000000
--- a/code/datums/status_effects/magic_disguise.dm
+++ /dev/null
@@ -1,89 +0,0 @@
-/datum/status_effect/magic_disguise
- id = "magic_disguise"
- duration = -1
- tick_interval = -1
- alert_type = /atom/movable/screen/alert/status_effect/magic_disguise
- status_type = STATUS_EFFECT_REPLACE
- var/mob/living/disguise_mob
- var/datum/icon_snapshot/disguise
-
-/atom/movable/screen/alert/status_effect/magic_disguise
- name = "Disguised"
- desc = "You are disguised as a crewmember."
- icon = 'icons/mob/actions/actions.dmi'
- icon_state = "chameleon_outfit"
-
-/datum/status_effect/magic_disguise/on_creation(mob/living/new_owner, mob/living/_disguise_mob)
- disguise_mob = _disguise_mob
- . = ..()
-
-/datum/status_effect/magic_disguise/on_apply()
- . = ..()
- if(!ishuman(owner))
- return FALSE
- if(!disguise_mob)
- disguise_mob = select_disguise()
- if(ishuman(disguise_mob))
- create_disguise(disguise_mob)
- if(disguise)
- apply_disguise(owner)
- else
- to_chat(owner, "Your spell fails to find a disguise!")
- return FALSE
-
- RegisterSignal(owner, list(COMSIG_MOB_APPLY_DAMAGE, COMSIG_HUMAN_ATTACKED, COMSIG_SPECIES_HITBY), PROC_REF(remove_disguise))
- return TRUE
-
-/datum/status_effect/magic_disguise/on_remove()
- owner.regenerate_icons()
- ..()
-
-/datum/status_effect/magic_disguise/proc/select_disguise()
- var/obj/machinery/door/airlock/AL
- var/area/caster_area
-
- caster_area = get_area(owner)
- for(var/obj/machinery/door/airlock/tmp in view(owner))
- if(get_area(tmp) == caster_area && (length(tmp.req_access) || length(tmp.req_one_access))) //Ignore airlocks that arent in area or are public airlocks
- AL = tmp
- break
- for(var/mob/living/carbon/human/disguise_source in shuffle(GLOB.player_list)) //Pick a random crewmember with access to this room
- if((ACCESS_CAPTAIN in disguise_source.get_access()) || (ACCESS_HOP in disguise_source.get_access()) || (ACCESS_CLOWN in disguise_source.get_access()))
- continue //We don't want the cap, HOP or clown as a disguise, too remarkable. If you're spotted by the Cap or HOP in their own office, disguising as them wont help you either
- if((!AL || AL.allowed(disguise_source)) && !disguise_source.mind.offstation_role && disguise_source != owner)
- return disguise_source
- for(var/mob/living/carbon/human/backup_source in shuffle(GLOB.player_list)) //Pick a random crewmember if there's no one with access to the current room
- if((ACCESS_CAPTAIN in backup_source.get_access()) || (ACCESS_HOP in backup_source.get_access()) || (ACCESS_CLOWN in backup_source.get_access()))
- continue //ditto
- if(!backup_source.mind.offstation_role && backup_source != owner)
- return backup_source
- return
-
-/datum/status_effect/magic_disguise/proc/create_disguise(mob/living/carbon/human/disguise_source)
- var/datum/icon_snapshot/temp = new
- temp.name = disguise_source.name
- temp.icon = disguise_source.icon
- temp.icon_state = disguise_source.icon_state
- temp.overlays = disguise_source.get_overlays_copy(list(L_HAND_LAYER, R_HAND_LAYER))
- disguise = temp
-
-/datum/status_effect/magic_disguise/proc/apply_disguise(mob/living/carbon/human/H)
- H.name_override = disguise.name
- H.icon = disguise.icon
- H.icon_state = disguise.icon_state
- H.overlays = disguise.overlays
- H.update_inv_r_hand()
- H.update_inv_l_hand()
- H.sec_hud_set_ID()
- SEND_SIGNAL(H, COMSIG_CARBON_REGENERATE_ICONS)
- to_chat(H, "You disguise yourself as [disguise.name].")
-
-/datum/status_effect/magic_disguise/proc/remove_disguise()
- SIGNAL_HANDLER // COMSIG_MOB_APPLY_DAMAGE + COMSIG_HUMAN_ATTACKED + COMSIG_SPECIES_HITBY
- if(!ishuman(owner))
- return
- var/mob/living/carbon/human/H = owner
- H.name_override = null
- H.overlays.Cut()
- H.sec_hud_set_ID()
- qdel(src)
diff --git a/code/datums/uplink_items/uplink_nuclear.dm b/code/datums/uplink_items/uplink_nuclear.dm
deleted file mode 100644
index d4ee796582803..0000000000000
--- a/code/datums/uplink_items/uplink_nuclear.dm
+++ /dev/null
@@ -1,725 +0,0 @@
-// NUCLEAR AGENT ONLY GEAR
-
-////////////////////////////////////////
-// MARK: DANGEROUS WEAPONS
-////////////////////////////////////////
-
-/datum/uplink_item/dangerous/aps
- name = "Stechkin APS Pistol"
- reference = "APS"
- desc = "The automatic machine pistol version of the FK-69 'Stechkin' chambered in 10mm Auto with a detachable 20-round box magazine. Perfect for dual wielding or as backup."
- item = /obj/item/gun/projectile/automatic/pistol/aps
- cost = 40
- uplinktypes = list(UPLINK_TYPE_NUCLEAR, UPLINK_TYPE_SST)
-
-/datum/uplink_item/dangerous/smg
- name = "C-20r Submachine Gun"
- reference = "SMG"
- desc = "A fully-loaded Scarborough Arms bullpup submachine gun that fires .45 rounds with a 20-round magazine and is compatible with suppressors."
- item = /obj/item/gun/projectile/automatic/c20r
- cost = 70
- uplinktypes = list(UPLINK_TYPE_NUCLEAR, UPLINK_TYPE_SST)
- surplus = 40
-
-/datum/uplink_item/dangerous/carbine
- name = "M-90gl Carbine"
- desc = "A fully-loaded three-round burst carbine that uses 30-round 5.56mm magazines with a togglable underslung 40mm grenade launcher."
- reference = "AR"
- item = /obj/item/gun/projectile/automatic/m90
- cost = 90
- uplinktypes = list(UPLINK_TYPE_NUCLEAR, UPLINK_TYPE_SST)
- surplus = 50
-
-/datum/uplink_item/dangerous/machinegun
- name = "L6 Squad Automatic Weapon"
- desc = "A fully-loaded Aussec Armory belt-fed machine gun. This deadly weapon has a massive 50-round magazine of devastating 7.62x51mm ammunition."
- reference = "LMG"
- item = /obj/item/gun/projectile/automatic/l6_saw
- cost = 200
- uplinktypes = list(UPLINK_TYPE_NUCLEAR, UPLINK_TYPE_SST)
- surplus = 0
-
-/datum/uplink_item/dangerous/sniper
- name = "Sniper Rifle"
- desc = "Ranged fury, Syndicate style. guaranteed to cause shock and awe or your TC back!"
- reference = "SSR"
- item = /obj/item/gun/projectile/automatic/sniper_rifle/syndicate
- cost = 80
- surplus = 25
- uplinktypes = list(UPLINK_TYPE_NUCLEAR, UPLINK_TYPE_SST)
-
-/datum/uplink_item/dangerous/rocket_launcher
- name = "Rocket Launcher"
- desc = "Not many things can survive a direct hit from this. (Ammunition sold separately, keep away from children.)"
- reference = "RL"
- item = /obj/item/gun/rocketlauncher
- cost = 40
- uplinktypes = list(UPLINK_TYPE_NUCLEAR, UPLINK_TYPE_SST)
-
-/datum/uplink_item/dangerous/flamethrower
- name = "Flamethrower"
- desc = "A chemical flamethrower, fueled by a volatile mix of fuel and napalm. Comes prefilled with two canisters. Do not use with caution."
- reference = "CHEM_THROWER"
- item = /obj/item/chemical_flamethrower/extended/nuclear
- cost = 40 // In contrary to the gas flamethrower, this one is a very strong area denial tool that can't be countered by an AI
- uplinktypes = list(UPLINK_TYPE_NUCLEAR, UPLINK_TYPE_SST)
-
-/datum/uplink_item/dangerous/combat_defib
- name = "Combat Defibrillator Module"
- desc = "A lifesaving device turned dangerous weapon. Click on someone with the paddles on harm intent to instantly stop their heart. Can be used as a regular defib as well. Installs in a modsuit."
- reference = "CD"
- item = /obj/item/mod/module/defibrillator/combat
- cost = 60
- uplinktypes = list(UPLINK_TYPE_NUCLEAR, UPLINK_TYPE_SST)
-
-/datum/uplink_item/dangerous/foamsmg
- name = "Toy Submachine Gun"
- desc = "A fully-loaded Donksoft bullpup submachine gun that fires riot grade rounds with a 20-round magazine."
- reference = "FSMG"
- item = /obj/item/gun/projectile/automatic/c20r/toy
- cost = 25
- uplinktypes = list(UPLINK_TYPE_NUCLEAR, UPLINK_TYPE_SST)
- surplus = 0
-
-/datum/uplink_item/dangerous/foammachinegun
- name = "Toy Machine Gun"
- desc = "A fully-loaded Donksoft belt-fed machine gun. This weapon has a massive 50-round magazine of devastating riot grade darts, that can briefly incapacitate someone in just one volley."
- reference = "FLMG"
- item = /obj/item/gun/projectile/automatic/l6_saw/toy
- cost = 50
- uplinktypes = list(UPLINK_TYPE_NUCLEAR, UPLINK_TYPE_SST)
- surplus = 0
-
-/datum/uplink_item/dangerous/bulldog
- name = "Bulldog Shotgun"
- desc = "Lean and mean: Optimized for people that want to get up close and personal. Extra Ammo sold separately."
- reference = "BULD"
- item = /obj/item/gun/projectile/automatic/shotgun/bulldog
- cost = 15
- uplinktypes = list(UPLINK_TYPE_NUCLEAR, UPLINK_TYPE_SST)
-
-////////////////////////////////////////
-// MARK: SUPPORT AND MECHAS
-////////////////////////////////////////
-
-/datum/uplink_item/support
- category = "Support and Mechanized Exosuits"
- surplus = 0
- uplinktypes = list(UPLINK_TYPE_NUCLEAR, UPLINK_TYPE_SST)
-
-/datum/uplink_item/support/gygax
- name = "Gygax Exosuit"
- desc = "A lightweight exosuit, painted in a dark scheme. Its speed and equipment selection make it excellent for hit-and-run style attacks."
- reference = "GE"
- item = /obj/mecha/combat/gygax/dark/loaded
- cost = 350
-
-/datum/uplink_item/support/mauler
- name = "Mauler Exosuit"
- desc = "A massive and incredibly deadly Syndicate exosuit. Features long-range targeting, thrust vectoring, and deployable smoke."
- reference = "ME"
- item = /obj/mecha/combat/marauder/mauler/loaded
- cost = 599 // Today only 599 TC! Get yours today!
-
-/datum/uplink_item/support/reinforcement
- name = "Reinforcement"
- desc = "Call in an additional team member. They won't come with any gear, so you'll have to save some telecrystals \
- to arm them as well."
- reference = "REINF"
- item = /obj/item/antag_spawner/nuke_ops
- refund_path = /obj/item/antag_spawner/nuke_ops
- cost = 100
- refundable = TRUE
- can_discount = FALSE
-
-/datum/uplink_item/support/reinforcement/assault_borg
- name = "Syndicate Assault Cyborg"
- desc = "A cyborg designed and programmed for systematic extermination of non-Syndicate personnel. \
- Comes equipped with a self-resupplying LMG, a grenade launcher, energy sword, emag, pinpointer, flash and crowbar."
- reference = "SAC"
- item = /obj/item/antag_spawner/nuke_ops/borg_tele/assault
- refund_path = /obj/item/antag_spawner/nuke_ops/borg_tele/assault
- cost = 250
-
-/datum/uplink_item/support/reinforcement/medical_borg
- name = "Syndicate Medical Cyborg"
- desc = "A combat medical cyborg. Has limited offensive potential, but makes more than up for it with its support capabilities. \
- It comes equipped with a nanite hypospray, a medical beamgun, combat defibrillator, full surgical kit including an energy saw, an emag, pinpointer and flash. \
- Thanks to its organ storage bag, it can perform surgery as well as any humanoid."
- reference = "SMC"
- item = /obj/item/antag_spawner/nuke_ops/borg_tele/medical
- refund_path = /obj/item/antag_spawner/nuke_ops/borg_tele/medical
- cost = 175
-
-/datum/uplink_item/support/reinforcement/saboteur_borg
- name = "Syndicate Saboteur Cyborg"
- desc = "A streamlined engineering cyborg, equipped with covert modules and engineering equipment. Also incapable of leaving the welder in the shuttle. \
- Its chameleon projector lets it disguise itself as a Nanotrasen cyborg, on top it has thermal vision and a pinpointer."
- reference = "SSC"
- item = /obj/item/antag_spawner/nuke_ops/borg_tele/saboteur
- refund_path = /obj/item/antag_spawner/nuke_ops/borg_tele/saboteur
- cost = 125
-
-////////////////////////////////////////
-// MARK: AMMUNITION
-////////////////////////////////////////
-
-/datum/uplink_item/ammo/aps
- name = "Stechkin APS - 10mm Magazine"
- desc = "An additional 20-round 10mm magazine for use in the Stechkin APS machine pistol, loaded with rounds that are cheap but around half as effective as .357"
- reference = "10MMAPS"
- item = /obj/item/ammo_box/magazine/apsm10mm
- cost = 10
- uplinktypes = list(UPLINK_TYPE_NUCLEAR, UPLINK_TYPE_SST)
-
-/datum/uplink_item/ammo/apsap
- name = "Stechkin APS - 10mm Armour Piercing Magazine"
- desc = "An additional 20-round 10mm magazine for use in the Stechkin APS machine pistol, loaded with rounds that are less effective at injuring the target but penetrate protective gear."
- reference = "10MMAPSAP"
- item = /obj/item/ammo_box/magazine/apsm10mm/ap
- cost = 15
- uplinktypes = list(UPLINK_TYPE_NUCLEAR, UPLINK_TYPE_SST)
-
-/datum/uplink_item/ammo/apsfire
- name = "Stechkin APS - 10mm Incendiary Magazine"
- desc = "An additional 20-round 10mm magazine for use in the Stechkin APS machine pistol, loaded with incendiary rounds which ignite the target."
- reference = "10MMAPSFIRE"
- item = /obj/item/ammo_box/magazine/apsm10mm/fire
- cost = 15
- uplinktypes = list(UPLINK_TYPE_NUCLEAR, UPLINK_TYPE_SST)
-
-/datum/uplink_item/ammo/apshp
- name = "Stechkin APS - 10mm Hollow Point Magazine"
- desc = "An additional 20-round 10mm magazine for use in the Stechkin APS machine pistol, loaded with rounds which are more damaging but ineffective against armour."
- reference = "10MMAPSHP"
- item = /obj/item/ammo_box/magazine/apsm10mm/hp
- cost = 20
- uplinktypes = list(UPLINK_TYPE_NUCLEAR, UPLINK_TYPE_SST)
-
-/datum/uplink_item/ammo/bullslug
- name = "Bulldog - 12g Slug Magazine"
- desc = "An additional 8-round slug magazine for use in the Bulldog shotgun. Now 8 times less likely to shoot your pals."
- reference = "12BSG"
- item = /obj/item/ammo_box/magazine/m12g
- cost = 10
- uplinktypes = list(UPLINK_TYPE_NUCLEAR, UPLINK_TYPE_SST)
-
-/datum/uplink_item/ammo/bullbuck
- name = "Bulldog - 12g Buckshot Magazine"
- desc = "An additional 8-round buckshot magazine for use in the Bulldog shotgun. Front towards enemy."
- reference = "12BS"
- item = /obj/item/ammo_box/magazine/m12g/buckshot
- cost = 10
- uplinktypes = list(UPLINK_TYPE_NUCLEAR, UPLINK_TYPE_SST)
-
-/datum/uplink_item/ammo/bullmeteor
- name = "12g Meteorslug Shells"
- desc = "An alternative 8-round meteorslug magazine for use in the Bulldog shotgun. Great for blasting airlocks off their frames and knocking down enemies."
- reference = "12MS"
- item = /obj/item/ammo_box/magazine/m12g/meteor
- cost = 10
- uplinktypes = list(UPLINK_TYPE_NUCLEAR, UPLINK_TYPE_SST)
-
-/datum/uplink_item/ammo/bulldragon
- name = "Bulldog - 12g Dragon's Breath Magazine"
- desc = "An alternative 8-round dragon's breath magazine for use in the Bulldog shotgun. I'm a fire starter, twisted fire starter!"
- reference = "12DB"
- item = /obj/item/ammo_box/magazine/m12g/dragon
- cost = 10
- uplinktypes = list(UPLINK_TYPE_NUCLEAR, UPLINK_TYPE_SST)
-
-/datum/uplink_item/ammo/bulldog_ammobag
- name = "Bulldog - 12g Ammo Duffel Bag"
- desc = "A duffel bag filled with nine 8 round drum magazines. (6 Slug, 2 Buckshot, 1 Dragon's Breath)"
- reference = "12ADB"
- item = /obj/item/storage/backpack/duffel/syndie/shotgun
- cost = 60 // normally 90
- uplinktypes = list(UPLINK_TYPE_NUCLEAR, UPLINK_TYPE_SST)
-
-/datum/uplink_item/ammo/bulldog_xl_magsbag
- name = "Bulldog - 12g Extra-Large Magazine Duffel Bag"
- desc = "A duffel bag containing five XL 16 round drum magazines. (3 Slug, 1 Buckshot, 1 Dragon's Breath)."
- reference = "12XLDB"
- item = /obj/item/storage/backpack/duffel/syndie/shotgun_xl_mags
- // same price for more ammo, but you're likely to lose more ammo if you drop your bulldog. High risk, high reward.
- cost = 60
- uplinktypes = list(UPLINK_TYPE_NUCLEAR, UPLINK_TYPE_SST)
-
-/datum/uplink_item/ammo/smg
- name = "C-20r - .45 Magazine"
- desc = "An additional 20-round .45 magazine for use in the C-20r submachine gun. These bullets pack a lot of punch that can knock most targets down, but do limited overall damage."
- reference = "45"
- item = /obj/item/ammo_box/magazine/smgm45
- cost = 15
- uplinktypes = list(UPLINK_TYPE_NUCLEAR, UPLINK_TYPE_SST)
-
-/datum/uplink_item/ammo/smg_ammobag
- name = "C-20r - .45 Ammo Duffel Bag"
- desc = "A duffel bag filled with enough .45 ammo to supply an entire team, at a discounted price."
- reference = "45ADB"
- item = /obj/item/storage/backpack/duffel/syndie/smg
- cost = 105 // Normally 150, so 30% off
- uplinktypes = list(UPLINK_TYPE_NUCLEAR, UPLINK_TYPE_SST)
-
-/datum/uplink_item/ammo/carbine
- name = "Carbine - 5.56 Toploader Magazine"
- desc = "An additional 30-round 5.56 magazine for use in the M-90gl carbine. These bullets don't have the punch to knock most targets down, but dish out higher overall damage."
- reference = "556"
- item = /obj/item/ammo_box/magazine/m556
- cost = 10
- uplinktypes = list(UPLINK_TYPE_NUCLEAR, UPLINK_TYPE_SST)
-
-/datum/uplink_item/ammo/a40mm
- name = "Carbine - 40mm Grenade Ammo Box"
- desc = "A box of 4 additional 40mm HE grenades for use the C-90gl's underbarrel grenade launcher. Your teammates will thank you to not shoot these down small hallways."
- reference = "40MM"
- item = /obj/item/ammo_box/a40mm
- cost = 20
- uplinktypes = list(UPLINK_TYPE_NUCLEAR, UPLINK_TYPE_SST)
-
-/datum/uplink_item/ammo/rocket
- name = "Rocket Launcher Shell"
- desc = "An extra shell for your RPG. Make sure your bestie isn't standing in front of you."
- reference = "HE"
- item = /obj/item/ammo_casing/rocket
- cost = 30
- uplinktypes = list(UPLINK_TYPE_NUCLEAR, UPLINK_TYPE_SST)
-
-/datum/uplink_item/ammo/chemical_canister
- name = "Box of chemical flamethrower canisters"
- desc = "A box filled with 2 canisters of flamethrower fuel, exactly enough to fully refill your flamethrower once!"
- reference = "CHEM_CAN"
- item = /obj/item/storage/box/syndie_kit/chemical_canister
- cost = 30
- uplinktypes = list(UPLINK_TYPE_NUCLEAR, UPLINK_TYPE_SST)
-
-/datum/uplink_item/ammo/machinegun
- name = "L6 SAW - 5.56x45mm Box Magazine"
- desc = "A 50-round magazine of 5.56x45mm ammunition for use in the L6 SAW machine gun. By the time you need to use this, you'll already be on a pile of corpses."
- reference = "762"
- item = /obj/item/ammo_box/magazine/mm556x45
- cost = 60
- uplinktypes = list(UPLINK_TYPE_NUCLEAR, UPLINK_TYPE_SST)
- surplus = 0
-
-/datum/uplink_item/ammo/sniper
- cost = 15
- uplinktypes = list(UPLINK_TYPE_NUCLEAR, UPLINK_TYPE_SST)
-
-/datum/uplink_item/ammo/sniper/basic
- name = "Sniper - .50 Magazine"
- desc = "An additional standard 6-round magazine for use with .50 sniper rifles."
- reference = "50M"
- item = /obj/item/ammo_box/magazine/sniper_rounds
-
-/datum/uplink_item/ammo/sniper/antimatter
- name = "Sniper - .50 Antimatter Magazine"
- desc = "A 6-round magazine of antimatter ammo for use with .50 sniper rifles. \
- Able to heavily damage objects, and delimb people. Requires zooming in for accurate aiming."
- reference = "50A"
- item = /obj/item/ammo_box/magazine/sniper_rounds/antimatter
- cost = 30
-
-/datum/uplink_item/ammo/sniper/soporific
- name = "Sniper - .50 Soporific Magazine"
- desc = "A 3-round magazine of soporific ammo designed for use with .50 sniper rifles. Put your enemies to sleep today!"
- reference = "50S"
- item = /obj/item/ammo_box/magazine/sniper_rounds/soporific
-
-/datum/uplink_item/ammo/sniper/haemorrhage
- name = "Sniper - .50 Haemorrhage Magazine"
- desc = "A 5-round magazine of haemorrhage ammo designed for use with .50 sniper rifles; causes heavy bleeding \
- in the target."
- reference = "50B"
- item = /obj/item/ammo_box/magazine/sniper_rounds/haemorrhage
-
-/datum/uplink_item/ammo/sniper/penetrator
- name = "Sniper - .50 Penetrator Magazine"
- desc = "A 5-round magazine of penetrator ammo designed for use with .50 sniper rifles. \
- Can pierce walls and multiple enemies."
- reference = "50P"
- item = /obj/item/ammo_box/magazine/sniper_rounds/penetrator
- cost = 20
-
-/datum/uplink_item/ammo/bioterror
- name = "Box of Bioterror Syringes"
- desc = "A box full of preloaded syringes, containing various chemicals that seize up the victim's motor and broca system , making it impossible for them to move or speak while in their system."
- reference = "BTS"
- item = /obj/item/storage/box/syndie_kit/bioterror
- cost = 25
- uplinktypes = list(UPLINK_TYPE_NUCLEAR, UPLINK_TYPE_SST)
-
-/datum/uplink_item/ammo/toydarts
- name = "Box of Riot Darts"
- desc = "A box of 40 Donksoft foam riot darts, for reloading any compatible foam dart gun. Don't forget to share!"
- reference = "FOAM"
- item = /obj/item/ammo_box/foambox/riot
- cost = 10
- uplinktypes = list(UPLINK_TYPE_NUCLEAR, UPLINK_TYPE_SST)
- surplus = 0
-
-////////////////////////////////////////
-// MARK: EXPLOSIVES
-////////////////////////////////////////
-
-/datum/uplink_item/explosives/c4bag
- name = "Bag of C-4 explosives"
- desc = "Because sometimes quantity is quality. Contains 10 C-4 plastic explosives."
- reference = "C4B"
- item = /obj/item/storage/backpack/duffel/syndie/c4
- cost = 40 //20% discount!
- can_discount = FALSE
- uplinktypes = list(UPLINK_TYPE_NUCLEAR, UPLINK_TYPE_SST)
-
-/datum/uplink_item/explosives/breaching_charge
- name = "Composition X-4"
- desc = "X-4 is a shaped charge designed to be safe to the user while causing maximum damage to the occupants of the room beach breached. It has a modifiable timer with a minimum setting of 10 seconds."
- reference = "X4"
- item = /obj/item/grenade/plastic/c4/x4
- cost = 10
- uplinktypes = list(UPLINK_TYPE_NUCLEAR, UPLINK_TYPE_SST)
-
-/datum/uplink_item/explosives/x4bag
- name = "Bag of X-4 explosives"
- desc = "Contains 3 X-4 shaped plastic explosives. Similar to C4, but with a stronger blast that is directional instead of circular. \
- X-4 can be placed on a solid surface, such as a wall or window, and it will blast through the wall, injuring anything on the opposite side, while being safer to the user. \
- For when you want a controlled explosion that leaves a wider, deeper, hole."
- reference = "X4B"
- item = /obj/item/storage/backpack/duffel/syndie/x4
- cost = 20
- can_discount = FALSE
- uplinktypes = list(UPLINK_TYPE_NUCLEAR, UPLINK_TYPE_SST)
-
-/datum/uplink_item/explosives/grenadier
- name = "Grenadier's belt"
- desc = "A belt containing 26 lethally dangerous and destructive grenades."
- reference = "GRB"
- item = /obj/item/storage/belt/grenade/full
- cost = 120
- surplus = 0
- uplinktypes = list(UPLINK_TYPE_NUCLEAR, UPLINK_TYPE_SST)
-
-/datum/uplink_item/explosives/manhacks
- name = "Viscerator Delivery Grenade"
- desc = "A unique grenade that deploys a swarm of viscerators upon activation, which will chase down and shred any non-operatives in the area."
- reference = "VDG"
- item = /obj/item/grenade/spawnergrenade/manhacks
- cost = 10
- uplinktypes = list(UPLINK_TYPE_NUCLEAR, UPLINK_TYPE_SST)
- surplus = 35
-
-////////////////////////////////////////
-// MARK: STEALTHY WEAPONS
-////////////////////////////////////////
-
-// There's no nukie only stealthy weapons right now, but if you want to add one, put it here.
-
-////////////////////////////////////////
-// MARK: STEALTHY TOOLS
-////////////////////////////////////////
-
-/datum/uplink_item/stealthy_tools/clownkit
- name = "Honk Brand Infiltration Kit"
- desc = "All the tools you need to play the best prank Nanotrasen has ever seen. Includes a voice changer mask, magnetic clown shoes, and standard clown outfit, tools, and backpack."
- reference = "HBIK"
- item = /obj/item/storage/backpack/clown/syndie
- cost = 30
- uplinktypes = list(UPLINK_TYPE_NUCLEAR, UPLINK_TYPE_SST)
- surplus = 0
-
-////////////////////////////////////////
-// MARK: DEVICES AND TOOLS
-////////////////////////////////////////
-
-/datum/uplink_item/device_tools/diamond_drill
- name = "Amplifying Diamond Tipped Thermal Safe Drill"
- desc = "A diamond tipped thermal drill with magnetic clamps for the purpose of quickly drilling hardened objects. Comes with built in security detection and nanite system, to keep you up if security comes a-knocking."
- reference = "DDRL"
- item = /obj/item/thermal_drill/diamond_drill/syndicate
- cost = 5
- uplinktypes = list(UPLINK_TYPE_NUCLEAR, UPLINK_TYPE_SST)
-
-/datum/uplink_item/device_tools/medkit
- name = "Syndicate Combat Medic Kit"
- desc = "The syndicate medkit is a suspicious black and red. Included is a combat stimulant injector for rapid healing, a medical HUD for quick identification of injured comrades, \
- and other medical supplies helpful for a medical field operative."
- reference = "SCMK"
- item = /obj/item/storage/firstaid/tactical
- cost = 30
- uplinktypes = list(UPLINK_TYPE_NUCLEAR, UPLINK_TYPE_SST)
-
-/datum/uplink_item/device_tools/vtec
- name = "Syndicate Cyborg Upgrade Module (VTEC)"
- desc = "Increases the movement speed of a Cyborg. Install into any Borg, Syndicate or subverted"
- reference = "VTEC"
- item = /obj/item/borg/upgrade/vtec
- cost = 30
- uplinktypes = list(UPLINK_TYPE_NUCLEAR, UPLINK_TYPE_SST)
-
-/datum/uplink_item/device_tools/magboots
- name = "Blood-Red Magboots"
- desc = "A pair of magnetic boots with a Syndicate paintjob that assist with freer movement in space or on-station during gravitational generator failures. \
- These reverse-engineered knockoffs of Nanotrasen's 'Advanced Magboots' slow you down in simulated-gravity environments much like the standard issue variety."
- reference = "BRMB"
- item = /obj/item/clothing/shoes/magboots/syndie
- cost = 10
- uplinktypes = list(UPLINK_TYPE_NUCLEAR, UPLINK_TYPE_SST)
-
-/datum/uplink_item/device_tools/syndicate_detonator
- name = "Syndicate Detonator"
- desc = "The Syndicate Detonator is a companion device to the Syndicate Bomb. Simply press the included button and an encrypted radio frequency will instruct all live syndicate bombs to detonate. \
- Useful for when speed matters or you wish to synchronize multiple bomb blasts. Be sure to stand clear of the blast radius before using the detonator."
- reference = "SD"
- item = /obj/item/syndicatedetonator
- cost = 5
- uplinktypes = list(UPLINK_TYPE_NUCLEAR, UPLINK_TYPE_SST)
-
-/datum/uplink_item/device_tools/teleporter
- name = "Teleporter Circuit Board"
- desc = "A printed circuit board that completes the teleporter onboard the mothership. Advise you test fire the teleporter before entering it, as malfunctions can occur."
- item = /obj/item/circuitboard/teleporter
- reference = "TP"
- cost = 100
- uplinktypes = list(UPLINK_TYPE_NUCLEAR, UPLINK_TYPE_SST)
- surplus = 0
-
-/datum/uplink_item/device_tools/assault_pod
- name = "Assault Pod Targetting Device"
- desc = "Use to select the landing zone of your assault pod."
- item = /obj/item/assault_pod
- reference = "APT"
- cost = 125
- uplinktypes = list(UPLINK_TYPE_NUCLEAR, UPLINK_TYPE_SST)
- surplus = 0
-
-/datum/uplink_item/device_tools/shield
- name = "Energy Shield"
- desc = "An incredibly useful personal shield projector, capable of reflecting energy projectiles, but it cannot block other attacks. Pair with an Energy Sword for a killer combination."
- item = /obj/item/shield/energy
- reference = "ESD"
- cost = 40
- uplinktypes = list(UPLINK_TYPE_NUCLEAR, UPLINK_TYPE_SST)
- surplus = 20
-
-/datum/uplink_item/device_tools/dropwall
- name = "Dropwall generator box"
- desc = "A box of 5 dropwall shield generators, which can be used to make temporary directional shields that block projectiles, thrown objects, and reduce explosions. Configure the direction before throwing."
- item = /obj/item/storage/box/syndie_kit/dropwall
- reference = "DWG"
- cost = 50
- uplinktypes = list(UPLINK_TYPE_NUCLEAR, UPLINK_TYPE_SST)
-
-/datum/uplink_item/device_tools/medgun
- name = "Medbeam Gun"
- desc = "Medical Beam Gun, useful in prolonged firefights. DO NOT CROSS THE BEAMS. Crossing beams with another medbeam or attaching two beams to one target will have explosive consequences."
- item = /obj/item/gun/medbeam
- reference = "MBG"
- cost = 75
- uplinktypes = list(UPLINK_TYPE_NUCLEAR, UPLINK_TYPE_SST)
-
-////////////////////////////////////////
-// MARK: SPACE SUITS
-////////////////////////////////////////
-
-/datum/uplink_item/suits/elite_nukie
- name = "Elite Syndicate MODsuit"
- desc = "An advanced MODsuit with superior armor and mobility to the standard Syndicate MODsuit."
- item = /obj/item/mod/control/pre_equipped/elite
- cost = 40
- reference = "ESHS"
- excludefrom = list()
- uplinktypes = list(UPLINK_TYPE_NUCLEAR, UPLINK_TYPE_SST)
-
-/datum/uplink_item/suits/shielded
- name = "Energy Shield Module"
- desc = "A personal, protective forcefield typically seen in military applications. \
- This advanced deflector shield is essentially a scaled down version of those seen on starships, \
- and the power cost can be an easy indicator of this. However, it is capable of blocking nearly any incoming attack, \
- though with its' low amount of separate charges, the user remains mortal."
- item = /obj/item/mod/module/energy_shield
- cost = 200
- reference = "SHS"
- excludefrom = list()
- uplinktypes = list(UPLINK_TYPE_NUCLEAR, UPLINK_TYPE_SST)
-
-////////////////////////////////////////
-// MARK: IMPLANTS
-////////////////////////////////////////
-
-/datum/uplink_item/bio_chips/krav_implant
- name = "Krav Maga Implant"
- desc = "A biochip that teaches you Krav Maga when implanted, great as a cheap backup weapon. Warning: the biochip will override any other fighting styles such as CQC while active."
- reference = "KMI"
- item = /obj/item/bio_chip_implanter/krav_maga
- cost = 25
- uplinktypes = list(UPLINK_TYPE_NUCLEAR, UPLINK_TYPE_SST)
-
-/datum/uplink_item/bio_chips/uplink/nuclear
- name = "Nuclear Uplink Bio-chip"
- reference = "UIN"
- item = /obj/item/bio_chip_implanter/nuclear
- excludefrom = list()
- uplinktypes = list(UPLINK_TYPE_NUCLEAR, UPLINK_TYPE_SST)
-
-/datum/uplink_item/bio_chips/microbomb
- name = "Microbomb Bio-chip"
- desc = "A bio-chip injected into the body, and later activated either manually or automatically upon death. The more implants inside of you, the higher the explosive power. \
- This will permanently destroy your body, however."
- reference = "MBI"
- item = /obj/item/bio_chip_implanter/explosive
- cost = 10
- uplinktypes = list(UPLINK_TYPE_NUCLEAR, UPLINK_TYPE_SST)
-
-/datum/uplink_item/bio_chips/macrobomb
- name = "Macrobomb Bio-chip"
- desc = "A bio-chip injected into the body, and later activated either manually or automatically upon death. Upon death, releases a massive explosion that will wipe out everything nearby."
- reference = "HAB"
- item = /obj/item/bio_chip_implanter/explosive_macro
- cost = 50
- uplinktypes = list(UPLINK_TYPE_NUCLEAR, UPLINK_TYPE_SST)
-
-
-// CYBERNETICS
-
-/datum/uplink_item/cyber_implants/thermals
- name = "Thermal Vision Implant"
- desc = "These cybernetic eyes will give you thermal vision. Comes with an autosurgeon."
- reference = "CIT"
- item = /obj/item/autosurgeon/organ/syndicate/thermal_eyes
- cost = 40
- surplus = 0
- uplinktypes = list(UPLINK_TYPE_NUCLEAR, UPLINK_TYPE_SST)
-
-/datum/uplink_item/cyber_implants/xray
- name = "X-Ray Vision Implant"
- desc = "These cybernetic eyes will give you X-ray vision. Comes with an autosurgeon."
- reference = "CIX"
- item = /obj/item/autosurgeon/organ/syndicate/xray_eyes
- cost = 50
- surplus = 0
- uplinktypes = list(UPLINK_TYPE_NUCLEAR, UPLINK_TYPE_SST)
-
-/datum/uplink_item/cyber_implants/antistun
- name = "Hardened CNS Rebooter Implant"
- desc = "This implant will help you get back up on your feet faster after being fatigued. It is immune to EMP attacks. Comes with an autosurgeon."
- reference = "CIAS"
- item = /obj/item/autosurgeon/organ/syndicate/anti_stam
- cost = 60
- surplus = 0
- uplinktypes = list(UPLINK_TYPE_NUCLEAR, UPLINK_TYPE_SST)
-
-/datum/uplink_item/cyber_implants/reviver
- name = "Hardened Reviver Implant"
- desc = "This implant will attempt to revive and heal you if you lose consciousness. It is immune to EMP attacks. Comes with an autosurgeon."
- reference = "CIR"
- item = /obj/item/autosurgeon/organ/syndicate/reviver
- cost = 40
- surplus = 0
- uplinktypes = list(UPLINK_TYPE_NUCLEAR, UPLINK_TYPE_SST)
-
-////////////////////////////////////////
-// MARK: BUNDLES
-////////////////////////////////////////
-
-/datum/uplink_item/bundles_tc/c20r
- name = "C-20r Bundle"
- desc = "Old Faithful: The classic C-20r, bundled with three magazines and a (surplus) suppressor at discount price."
- reference = "C20B"
- item = /obj/item/storage/backpack/duffel/syndie/c20rbundle
- cost = 90 // normally 105
- uplinktypes = list(UPLINK_TYPE_NUCLEAR, UPLINK_TYPE_SST)
-
-/datum/uplink_item/bundles_tc/cyber_implants
- name = "Cybernetic Implants Bundle"
- desc = "A random selection of cybernetic implants. Guaranteed 5 high quality implants. Comes with an autosurgeon."
- reference = "CIB"
- item = /obj/item/storage/box/cyber_implants
- cost = 200
- uplinktypes = list(UPLINK_TYPE_NUCLEAR, UPLINK_TYPE_SST)
-
-/datum/uplink_item/bundles_tc/medical
- name = "Medical Bundle"
- desc = "The support specialist: Aid your fellow operatives with this medical bundle. Contains a tactical medkit, \
- a medical beam gun and a pair of syndicate magboots."
- reference = "MEDB"
- item = /obj/item/storage/backpack/duffel/syndie/med/medicalbundle
- cost = 80 // normally 105
- uplinktypes = list(UPLINK_TYPE_NUCLEAR, UPLINK_TYPE_SST)
-
-/datum/uplink_item/bundles_tc/sniper
- name = "Sniper bundle"
- desc = "Elegant and refined: Contains a collapsed sniper rifle in an expensive carrying case, \
- two soporific knockout magazines, a free surplus suppressor, and a sharp-looking tactical turtleneck suit. \
- We'll throw in a free red tie if you order NOW."
- reference = "SNPB"
- item = /obj/item/storage/briefcase/sniperbundle
- cost = 90 // normally 115
- uplinktypes = list(UPLINK_TYPE_NUCLEAR, UPLINK_TYPE_SST)
-
-////////////////////////////////////////
-// MARK: PRICES OVERRIDE FOR TRAITOR ITEMS
-////////////////////////////////////////
-
-/datum/uplink_item/stealthy_weapons/cqc/nuke
- reference = "NCQC"
- cost = 40
- excludefrom = list(UPLINK_TYPE_TRAITOR, UPLINK_TYPE_SIT)
-
-/datum/uplink_item/explosives/syndicate_bomb/nuke
- reference = "NSB"
- cost = 55
- excludefrom = list(UPLINK_TYPE_TRAITOR, UPLINK_TYPE_SIT)
- surplus = 0
- hijack_only = FALSE
-
-/datum/uplink_item/explosives/emp_bomb/nuke
- reference = "NSBEMP"
- cost = 50
- excludefrom = list(UPLINK_TYPE_TRAITOR, UPLINK_TYPE_SIT)
- surplus = 0
- can_discount = FALSE
-
-/datum/uplink_item/explosives/atmosfiregrenades/nuke
- reference = "NAPG"
- cost = 60
- excludefrom = list(UPLINK_TYPE_TRAITOR, UPLINK_TYPE_SIT)
- surplus = 0
- hijack_only = TRUE
-
-/datum/uplink_item/stealthy_tools/chameleon/nuke
- reference = "NCHAM"
- item = /obj/item/storage/box/syndie_kit/chameleon/nuke
- cost = 30
- excludefrom = list(UPLINK_TYPE_TRAITOR, UPLINK_TYPE_SIT)
-
-/datum/uplink_item/stealthy_tools/syndigaloshes/nuke
- reference = "NNSSS"
- cost = 20
- excludefrom = list(UPLINK_TYPE_TRAITOR, UPLINK_TYPE_SIT)
-
-/datum/uplink_item/explosives/detomatix/nuclear
- desc = "When inserted into a personal digital assistant, this cartridge gives you five opportunities to detonate PDAs of crewmembers who have their message feature enabled. The concussive effect from the explosion will knock the recipient out for a short period, and deafen them for longer. It has a chance to detonate your PDA. This version comes with a program to toggle your nuclear shuttle blast doors remotely."
- item = /obj/item/cartridge/syndicate/nuclear
- reference = "DEPCN"
- excludefrom = list()
- uplinktypes = list(UPLINK_TYPE_NUCLEAR, UPLINK_TYPE_SST)
-
-////////////////////////////////////////
-// MARK: NUKIE ONLY POINTLESS BADASSERY
-////////////////////////////////////////
-
-/datum/uplink_item/badass/confettidrum
- name = "Bulldog - 12g party Magazine"
- desc = "An alternative 12-round confetti magazine for use in the Bulldog shotgun. Why? Because we can - Honkco Industries"
- item = /obj/item/ammo_box/magazine/m12g/confetti
- reference = "12CS"
- uplinktypes = list(UPLINK_TYPE_NUCLEAR, UPLINK_TYPE_SST)
- cost = 5
-
-/datum/uplink_item/badass/confetti_party_pack
- name = "Nuclear party pack"
- desc = "A dufflebag filled with hilarious equipment! Comes with free confetti grenades and a cap gun!"
- item = /obj/item/storage/backpack/duffel/syndie/party
- reference = "SPP"
- uplinktypes = list(UPLINK_TYPE_NUCLEAR, UPLINK_TYPE_SST)
- cost = 50
diff --git a/code/datums/wires/alarm.dm b/code/datums/wires/alarm.dm
deleted file mode 100644
index 08b2ed5a17058..0000000000000
--- a/code/datums/wires/alarm.dm
+++ /dev/null
@@ -1,81 +0,0 @@
-
-/datum/wires/alarm
- holder_type = /obj/machinery/alarm
- wire_count = 5
- proper_name = "Air alarm"
-
-/datum/wires/alarm/New(atom/_holder)
- wires = list(
- WIRE_IDSCAN , WIRE_MAIN_POWER1 , WIRE_SIPHON,
- WIRE_AI_CONTROL, WIRE_AALARM
- )
- return ..()
-
-/datum/wires/alarm/interactable(mob/user)
- var/obj/machinery/alarm/A = holder
- if(A.wiresexposed)
- return TRUE
- return FALSE
-
-/datum/wires/alarm/get_status()
- . = ..()
- var/obj/machinery/alarm/A = holder
- . += "The Air Alarm is [A.locked ? "" : "un"]locked."
- . += "The Air Alarm is [(A.shorted || (A.stat & (NOPOWER|BROKEN))) ? "offline." : "working properly!"]"
- . += "The 'AI control allowed' light is [A.aidisabled ? "off" : "on"]."
-
-/datum/wires/alarm/on_cut(wire, mend)
- var/obj/machinery/alarm/A = holder
- switch(wire)
- if(WIRE_IDSCAN)
- if(!mend)
- A.locked = TRUE
-
- if(WIRE_MAIN_POWER1)
- A.shock(usr, 50)
- A.shorted = !mend
- A.update_icon()
-
- if(WIRE_AI_CONTROL)
- A.aidisabled = !mend
-
- if(WIRE_SIPHON)
- if(!mend)
- A.mode = AALARM_MODE_PANIC
- A.apply_mode()
-
- if(WIRE_AALARM)
- A.alarm_area.atmosalert(ATMOS_ALARM_DANGER, A)
- A.update_icon()
- ..()
-
-/datum/wires/alarm/on_pulse(wire)
- var/obj/machinery/alarm/A = holder
- switch(wire)
- if(WIRE_IDSCAN)
- A.locked = !A.locked
-
- if(WIRE_MAIN_POWER1)
- if(!A.shorted)
- A.shorted = TRUE
- A.update_icon()
- addtimer(CALLBACK(A, TYPE_PROC_REF(/obj/machinery/alarm, unshort_callback)), 120 SECONDS)
-
- if(WIRE_AI_CONTROL)
- if(!A.aidisabled)
- A.aidisabled = TRUE
- A.updateDialog()
- addtimer(CALLBACK(A, TYPE_PROC_REF(/obj/machinery/alarm, enable_ai_control_callback)), 10 SECONDS)
-
-
- if(WIRE_SIPHON)
- if(A.mode == AALARM_MODE_FILTERING)
- A.mode = AALARM_MODE_PANIC
- else
- A.mode = AALARM_MODE_FILTERING
- A.apply_mode()
-
- if(WIRE_AALARM)
- A.alarm_area.atmosalert(ATMOS_ALARM_NONE, A)
- A.update_icon()
- ..()
diff --git a/code/datums/wires/particle_accelerator_wires.dm b/code/datums/wires/particle_accelerator_wires.dm
deleted file mode 100644
index 34a7d1313fe7a..0000000000000
--- a/code/datums/wires/particle_accelerator_wires.dm
+++ /dev/null
@@ -1,50 +0,0 @@
-/datum/wires/particle_accelerator
- wire_count = 5
- holder_type = /obj/machinery/particle_accelerator/control_box
- proper_name = "Particle accelerator control"
-
-/datum/wires/particle_accelerator/New(atom/_holder)
- wires = list(WIRE_PARTICLE_POWER, WIRE_PARTICLE_STRENGTH, WIRE_PARTICLE_INTERFACE, WIRE_PARTICLE_POWER_LIMIT)
- return ..()
-
-/datum/wires/particle_accelerator/interactable(mob/user)
- var/obj/machinery/particle_accelerator/control_box/C = holder
- if(C.construction_state == 2)
- return TRUE
- return FALSE
-
-/datum/wires/particle_accelerator/on_pulse(wire)
- var/obj/machinery/particle_accelerator/control_box/C = holder
- switch(wire)
- if(WIRE_PARTICLE_POWER)
- C.toggle_power()
-
- if(WIRE_PARTICLE_STRENGTH)
- C.add_strength()
-
- if(WIRE_PARTICLE_INTERFACE)
- C.interface_control = !C.interface_control
-
- if(WIRE_PARTICLE_POWER_LIMIT)
- C.visible_message("[bicon(C)][C] makes a large whirring noise.")
- ..()
-
-/datum/wires/particle_accelerator/on_cut(wire, mend)
- var/obj/machinery/particle_accelerator/control_box/C = holder
- switch(wire)
- if(WIRE_PARTICLE_POWER)
- if(C.active == !mend)
- C.toggle_power()
-
- if(WIRE_PARTICLE_STRENGTH)
- for(var/i in 1 to 2)
- C.remove_strength()
-
- if(WIRE_PARTICLE_INTERFACE)
- C.interface_control = mend
-
- if(WIRE_PARTICLE_POWER_LIMIT)
- C.strength_upper_limit = (mend ? 2 : 3)
- if(C.strength_upper_limit < C.strength)
- C.remove_strength()
- ..()
diff --git a/code/game/area/ai_monitored.dm b/code/game/area/ai_monitored.dm
deleted file mode 100644
index 61c9cab09b78e..0000000000000
--- a/code/game/area/ai_monitored.dm
+++ /dev/null
@@ -1,35 +0,0 @@
-/area/station/ai_monitored
- name = "AI Monitored Area"
- var/list/motioncameras = list()
- var/list/motionTargets = list()
- sound_environment = SOUND_ENVIRONMENT_ROOM
-
-/area/station/ai_monitored/Initialize(mapload)
- . = ..()
- if(mapload)
- for(var/obj/machinery/camera/M in src)
- if(M.isMotion())
- motioncameras.Add(M)
- M.set_area_motion(src)
-
-/area/station/ai_monitored/Entered(atom/movable/O)
- ..()
- if(ismob(O) && length(motioncameras))
- for(var/X in motioncameras)
- var/obj/machinery/camera/cam = X
- cam.newTarget(O)
- return
-
-/area/station/ai_monitored/Exited(atom/movable/O, direction)
- ..()
- if(ismob(O) && length(motioncameras))
- for(var/X in motioncameras)
- var/obj/machinery/camera/cam = X
- cam.lostTargetRef(O.UID())
- return
-
-/area/station/ai_monitored/storage/eva
- name = "EVA Storage"
- icon_state = "eva"
- ambientsounds = HIGHSEC_SOUNDS
- request_console_name = "EVA"
diff --git a/code/game/area/areas/depot-areas.dm b/code/game/area/areas/depot-areas.dm
deleted file mode 100644
index 52def39ba0d5d..0000000000000
--- a/code/game/area/areas/depot-areas.dm
+++ /dev/null
@@ -1,502 +0,0 @@
-
-/area/syndicate_depot
- name = "Suspicious Supply Depot"
- icon_state = "dark"
- tele_proof = TRUE
-
-/area/syndicate_depot/core
- icon_state = "red"
-
- var/local_alarm = FALSE // Level 1: Local alarm tripped, bot spawned, red fire overlay activated
- var/called_backup = FALSE // Level 2: Remote alarm tripped. Bot may path through depot. Backup spawned.
- var/used_self_destruct = FALSE // Level 3: Self destruct activated. Depot will be destroyed shortly.
-
- var/run_started = FALSE
- var/run_finished = FALSE
-
- // Soft UID-based refs
- var/list/guard_list = list()
- var/list/hostile_list = list()
- var/list/dead_list = list()
- var/list/peaceful_list = list()
- var/list/shield_list = list()
-
- var/list/alert_log = list() // no refs, just a simple list of text strings
-
- var/used_lockdown = FALSE
- var/destroyed = FALSE
- var/something_looted = FALSE
- var/on_peaceful = FALSE
- var/peace_betrayed = FALSE
- var/detected_mech = FALSE
- var/detected_pod = FALSE
- var/detected_double_agent = FALSE
- var/mine_trigger_count = 0
- var/perimeter_shield_status = FALSE
- var/obj/machinery/computer/syndicate_depot/syndiecomms/comms_computer = null
- var/obj/structure/fusionreactor/reactor
-
-/area/syndicate_depot/core/proc/update_state()
- if(destroyed)
- invisibility = INVISIBILITY_MAXIMUM
- else if(on_peaceful)
- invisibility = INVISIBILITY_LIGHTING
- else if(used_self_destruct)
- invisibility = INVISIBILITY_LIGHTING
- else if(called_backup)
- invisibility = INVISIBILITY_LIGHTING
- else if(local_alarm)
- invisibility = INVISIBILITY_LIGHTING
- else
- invisibility = INVISIBILITY_MAXIMUM
- update_icon(UPDATE_ICON_STATE)
-
-/area/syndicate_depot/core/update_icon_state()
- if(invisibility == INVISIBILITY_MAXIMUM)
- icon_state = null
- return
- else if(on_peaceful)
- icon_state = "green"
- else if(used_self_destruct)
- icon_state = "radiation"
- else if(called_backup)
- icon_state = "red"
- else if(local_alarm)
- icon_state = "bluenew"
-
-/area/syndicate_depot/core/proc/reset_alert()
-
- if(used_self_destruct)
- for(var/obj/effect/overload/O in src)
- new /obj/structure/fusionreactor(O.loc)
- qdel(O)
-
- if(on_peaceful)
- peaceful_mode(FALSE, TRUE)
-
- local_alarm = FALSE
- called_backup = FALSE
- unlock_computers()
- used_self_destruct = FALSE
-
- run_started = FALSE
- run_finished = FALSE
-
- despawn_guards()
- hostile_list = list()
- dead_list = list()
- peaceful_list = list()
-
- something_looted = FALSE
- detected_mech = FALSE
- detected_pod = FALSE
- detected_double_agent = FALSE
- mine_trigger_count = 0
- update_icon(UPDATE_ICON_STATE)
-
- if(!istype(reactor))
- for(var/obj/structure/fusionreactor/R in src)
- reactor = R
- R.has_overloaded = FALSE
-
- for(var/obj/machinery/door/airlock/A in src)
- if(A.density && A.locked)
- spawn(0)
- A.unlock()
-
- alert_log += "Alert level reset."
-
-/area/syndicate_depot/core/proc/increase_alert(reason)
- if(on_peaceful)
- peaceful_mode(FALSE, FALSE)
- peace_betrayed = TRUE
- activate_self_destruct("Depot has been infiltrated by double-agents.", TRUE, null)
- return
- if(!local_alarm)
- local_alarm(reason, FALSE)
- return
- if(!called_backup)
- call_backup(reason, FALSE)
- return
- if(!used_self_destruct)
- activate_self_destruct(reason, FALSE, null)
- update_icon(UPDATE_ICON_STATE)
-
-/area/syndicate_depot/core/proc/locker_looted()
- if(!something_looted)
- something_looted = TRUE
- if(on_peaceful)
- increase_alert("Thieves!")
- if(perimeter_shield_status)
- increase_alert("Perimeter shield breach!")
-
-
-/area/syndicate_depot/core/proc/armory_locker_looted()
- if(!run_finished && !used_self_destruct)
- if(length(shield_list))
- activate_self_destruct("Armory compromised despite armory shield being online.", FALSE)
- return
- declare_finished()
-
-/area/syndicate_depot/core/proc/turret_died()
- something_looted = TRUE
- if(on_peaceful)
- increase_alert("Vandals!")
-
-/area/syndicate_depot/core/proc/mine_triggered(mob/living/M)
- if(mine_trigger_count)
- return TRUE
- mine_trigger_count++
- increase_alert("Intruder detected by sentry mine: [M]")
-
-/area/syndicate_depot/core/proc/saw_mech(obj/mecha/E)
- if(detected_mech)
- return
- detected_mech = TRUE
- increase_alert("Hostile mecha detected: [E]")
-
-/area/syndicate_depot/core/proc/saw_double_agent(mob/living/M)
- if(detected_double_agent)
- return
- detected_double_agent = TRUE
- increase_alert("Hostile double-agent detected: [M]")
-
-/area/syndicate_depot/core/proc/peaceful_mode(newvalue, bycomputer)
- if(newvalue)
- log_game("Depot visit: started")
- alert_log += "Code GREEN: visitor mode started."
- ghostlog("The syndicate depot has visitors")
- for(var/mob/living/simple_animal/bot/medbot/syndicate/B in src)
- qdel(B)
- for(var/mob/living/simple_animal/hostile/syndicate/N in src)
- N.a_intent = INTENT_HELP
- for(var/obj/structure/closet/secure_closet/depot/L in src)
- if(L.opened)
- L.close()
- if(!L.locked)
- L.locked = !L.locked
- L.req_access = list(ACCESS_SYNDICATE_LEADER)
- L.update_icon()
- else
- log_game("Depot visit: ended")
- alert_log += "Visitor mode ended."
- for(var/mob/living/simple_animal/hostile/syndicate/N in src)
- N.a_intent = INTENT_HARM
- for(var/obj/machinery/door/airlock/A in src)
- A.req_access = list(ACCESS_SYNDICATE_LEADER)
- for(var/obj/structure/closet/secure_closet/depot/L in src)
- if(L.locked)
- L.locked = !L.locked
- L.req_access = list()
- L.update_icon()
- on_peaceful = newvalue
- if(newvalue)
- announce_here("Depot Visitor","A Syndicate agent is visiting the depot.")
- else
- if(bycomputer)
- message_admins("Syndicate Depot visitor mode deactivated. Visitors:")
- announce_here("Depot Alert","Visit ended. All visting agents signed out.")
- else
- message_admins("Syndicate Depot visitor mode auto-deactivated because visitors robbed depot! Visitors:")
- announce_here("Depot Alert","A visiting agent has betrayed the Syndicate. Shoot all visitors on sight!")
- for(var/mob/M in list_getmobs(peaceful_list))
- if("syndicate" in M.faction)
- M.faction -= "syndicate"
- message_admins("- SYNDI DEPOT VISITOR: [ADMIN_FULLMONTY(M)]")
- list_add(M, hostile_list)
- peaceful_list = list()
- update_icon(UPDATE_ICON_STATE)
-
-/area/syndicate_depot/core/proc/local_alarm(reason, silent)
- if(local_alarm)
- return
- log_game("Depot code: blue: " + list_show(hostile_list, TRUE))
- ghostlog("The syndicate depot has declared code blue.")
- alert_log += "Code BLUE: [reason]"
- local_alarm = TRUE
- if(!silent)
- announce_here("Depot Code BLUE", reason)
- var/list/possible_bot_spawns = list()
- for(var/thing in GLOB.landmarks_list)
- var/obj/effect/landmark/L = thing
- if(L.name == "syndi_depot_bot")
- possible_bot_spawns |= L
- if(length(possible_bot_spawns))
- var/obj/effect/landmark/S = pick(possible_bot_spawns)
- new /obj/effect/portal(get_turf(S))
- var/mob/living/simple_animal/bot/ed209/syndicate/B = new /mob/living/simple_animal/bot/ed209/syndicate(get_turf(S))
- list_add(B, guard_list)
- B.depotarea = src
- update_icon(UPDATE_ICON_STATE)
-
-/area/syndicate_depot/core/proc/call_backup(reason, silent)
- if(called_backup || used_self_destruct)
- return
- log_game("Depot code: red: " + list_show(hostile_list, TRUE))
- ghostlog("The syndicate depot has declared code red.")
- alert_log += "Code RED: [reason]"
- called_backup = TRUE
- lockout_computers()
- for(var/obj/machinery/door/poddoor/P in GLOB.airlocks)
- if(P.density && P.id_tag == "syndi_depot_lvl2" && !P.operating)
- spawn(0)
- P.open()
- if(!silent)
- announce_here("Depot Code RED", reason)
-
- var/comms_online = FALSE
- if(istype(comms_computer))
- if(!(comms_computer.stat & (NOPOWER|BROKEN)))
- comms_online = TRUE
- if(comms_online)
- spawn(0)
- for(var/thing in GLOB.landmarks_list)
- var/obj/effect/landmark/L = thing
- if(prob(50))
- if(L.name == "syndi_depot_backup")
- var/mob/living/simple_animal/hostile/syndicate/melee/autogib/depot/space/S = new /mob/living/simple_animal/hostile/syndicate/melee/autogib/depot/space(get_turf(L))
- S.name = "Syndicate Backup " + "([rand(1, 1000)])"
- S.depotarea = src
- list_add(S, guard_list)
- else if(!silent)
- announce_here("Depot Communications Offline", "Comms computer is damaged, destroyed or depowered. Unable to call in backup from Syndicate HQ.")
- update_icon(UPDATE_ICON_STATE)
-
-/area/syndicate_depot/core/proc/activate_self_destruct(reason, containment_failure, mob/user)
- if(used_self_destruct)
- return
- log_game("Depot code: delta: " + list_show(hostile_list, TRUE))
- ghostlog("The syndicate depot is about to self-destruct.")
- alert_log += "Code DELTA: [reason]"
- used_self_destruct = TRUE
- local_alarm = TRUE
- called_backup = TRUE
- activate_lockdown(TRUE)
- lockout_computers()
- update_icon(UPDATE_ICON_STATE)
- despawn_guards()
- if(containment_failure)
- announce_here("Depot Code DELTA", reason)
- else
- announce_here("Depot Code DELTA","[reason] Depot declared lost to hostile forces. Priming self-destruct!")
-
- if(user)
- var/turf/T = get_turf(user)
- var/area/A = get_area(T)
- var/log_msg = "[key_name(user)] has triggered the depot self destruct at [A.name] ([T.x],[T.y],[T.z])"
- message_admins(log_msg)
- log_game(log_msg)
- playsound(user, 'sound/machines/alarm.ogg', 100, FALSE, 0)
- else
- log_game("Depot self destruct activated.")
- if(reactor)
- if(!reactor.has_overloaded)
- reactor.overload(containment_failure)
- else
- log_debug("Depot: [src] called activate_self_destruct with no reactor.")
- message_admins("Syndicate Depot lacks reactor to initiate self-destruct. Must be destroyed manually.")
- update_icon(UPDATE_ICON_STATE)
-
-/area/syndicate_depot/core/proc/activate_lockdown()
- if(used_lockdown)
- return
- used_lockdown = TRUE
- for(var/obj/machinery/door/airlock/A in src)
- spawn(0)
- A.close()
- if(A.density && !A.locked)
- A.lock()
-
-/area/syndicate_depot/core/proc/lockout_computers()
- for(var/obj/machinery/computer/syndicate_depot/C in src)
- C.activate_security_lockout()
-
-/area/syndicate_depot/core/proc/unlock_computers()
- for(var/obj/machinery/computer/syndicate_depot/C in src)
- C.security_lockout = FALSE
-
-/area/syndicate_depot/core/proc/set_emergency_access(openaccess)
- for(var/obj/machinery/door/airlock/A in src)
- if(istype(A, /obj/machinery/door/airlock/hatch/syndicate/vault))
- continue
- A.emergency = !!openaccess
- A.update_icon()
-
-/area/syndicate_depot/core/proc/toggle_falsewalls()
- for(var/obj/structure/falsewall/plastitanium/F in src)
- spawn(0)
- F.toggle()
-
-/area/syndicate_depot/core/proc/toggle_teleport_beacon()
- for(var/obj/machinery/bluespace_beacon/syndicate/B in src)
- return B.toggle()
-
-/area/syndicate_depot/core/proc/announce_here(a_header = "Depot Defense Alert", a_text = "")
- var/msg_text = "[a_header] [a_text]"
- var/list/receivers = list()
- for(var/mob/M in GLOB.mob_list)
- if(!M.ckey)
- continue
- var/turf/T = get_turf(M)
- if(T && T.loc && T.loc == src)
- receivers |= M
- for(var/mob/R in receivers)
- to_chat(R, msg_text)
- SEND_SOUND(R, sound('sound/misc/notice1.ogg'))
-
-/area/syndicate_depot/core/proc/shields_up()
- if(length(shield_list))
- return
- for(var/thing in GLOB.landmarks_list)
- var/obj/effect/landmark/L = thing
- if(L.name == "syndi_depot_shield")
- var/obj/machinery/shieldwall/syndicate/S = new /obj/machinery/shieldwall/syndicate(L.loc)
- shield_list += S.UID()
- for(var/obj/structure/closet/secure_closet/depot/armory/L in src)
- if(L.opened)
- L.close()
- if(!L.locked)
- L.locked = !L.locked
- L.update_icon()
- for(var/obj/machinery/door/airlock/hatch/syndicate/vault/A in src)
- A.lock()
-
-/area/syndicate_depot/core/proc/shields_key_check()
- if(!length(shield_list))
- return
- if(detected_mech || detected_pod || detected_double_agent)
- return
- shields_down()
-
-/area/syndicate_depot/core/proc/shields_down()
- for(var/shuid in shield_list)
- var/obj/machinery/shieldwall/syndicate/S = locateUID(shuid)
- if(S)
- qdel(S)
- shield_list = list()
- for(var/obj/machinery/door/airlock/hatch/syndicate/vault/A in src)
- A.unlock()
-
-/area/syndicate_depot/core/proc/unlock_lockers() // used on QM's death
- for(var/obj/structure/closet/secure_closet/depot/armory/L in src)
- if(L.locked)
- L.locked = !L.locked
- L.update_icon()
-
-/area/syndicate_depot/core/proc/despawn_guards()
- for(var/mob/thismob in list_getmobs(guard_list))
- new /obj/effect/portal(get_turf(thismob))
- qdel(thismob)
- guard_list = list()
-
-/area/syndicate_depot/core/proc/ghostlog(gmsg)
- if(istype(reactor))
- var/image/alert_overlay = image('icons/obj/flag.dmi', "syndiflag")
- notify_ghosts(gmsg, title = "Depot News", source = reactor.loc, alert_overlay = alert_overlay, flashwindow = FALSE, action = NOTIFY_JUMP)
-
-/area/syndicate_depot/core/proc/declare_started()
- if(!run_started)
- run_started = TRUE
- log_game("Depot run: started: " + list_show(hostile_list, TRUE))
-
-/area/syndicate_depot/core/proc/declare_finished()
- if(!run_finished && !used_self_destruct)
- run_finished = TRUE
- log_game("Depot run: finished successfully: " + list_show(hostile_list, TRUE))
-
-/area/syndicate_depot/core/proc/list_add(mob/M, list/L)
- if(!istype(M))
- return
- var/mob_uid = M.UID()
- if(mob_uid in L)
- return
- L += mob_uid
-
-/area/syndicate_depot/core/proc/list_remove(mob/M, list/L)
- if(!istype(M))
- return
- var/mob_uid = M.UID()
- if(mob_uid in L)
- L -= mob_uid
-
-/area/syndicate_depot/core/proc/list_includes(mob/M, list/L)
- if(!istype(M))
- return FALSE
- var/mob_uid = M.UID()
- if(mob_uid in L)
- return TRUE
- return FALSE
-
-/**
- * Returns a STRING, containing the NAMES of the mobs in the provided list, JOINED together with ", "
- *
- * E.g. list_show(depotarea.guard_list) returns a string like:
- * "Syndicate Backup (123), Syndicate Backup(456), Syndicate Backup(789)", etc.
- * Arguments:
- * * list/L, the list of UIDs from which to draw members
- * * show_ckeys, bool, if true will display ckeys in addition to names
- */
-/area/syndicate_depot/core/proc/list_show(list/L, show_ckeys = FALSE)
- var/list/formatted = list_shownames(L, show_ckeys)
- return formatted.Join(", ")
-
-/**
- * Returns a LIST of the NAMES of the mobs in the provided list.
- *
- * E.g. list_shownames(depotarea.guard_list) returns a list of the names of extra guard mobs in depot.
- * Arguments:
- * * list/L, the list of UIDs from which to draw members
- * * show_ckeys, bool, if true will display ckeys in addition to names
- */
-/area/syndicate_depot/core/proc/list_shownames(list/L, show_ckeys = FALSE)
- var/list/names = list()
- for(var/uid in L)
- var/mob/M = locateUID(uid)
- if(!istype(M))
- continue
- if(show_ckeys)
- names += "[M.ckey]([M])"
- else
- names += "[M]"
- return names
-
-/**
- * Returns a LIST of the MOBS in one of the depot area's lists.
- *
- * E.g. list_getmobs(depotarea.guard_list) returns a list of the extra guard mobs in the depot.
- * Arguments:
- * * list/L, the list of UIDs from which to draw members
- * * show_ckeys, bool, if true will display ckeys in addition to names
- */
-/area/syndicate_depot/core/proc/list_getmobs(list/L, show_ckeys = FALSE)
- var/list/moblist = list()
- for(var/uid in L)
- var/mob/M = locateUID(uid)
- if(!istype(M))
- continue
- moblist += M
- return moblist
-
-/area/syndicate_depot/outer
- name = "Suspicious Asteroid"
- icon_state = "green"
-
-/area/syndicate_depot/perimeter
- name = "Suspicious Asteroid Perimeter"
- icon_state = "yellow"
- var/list/shield_list = list()
-
-/area/syndicate_depot/perimeter/proc/perimeter_shields_up()
- if(length(shield_list))
- return
- for(var/turf/T in src)
- var/obj/machinery/shieldwall/syndicate/S = new /obj/machinery/shieldwall/syndicate(T)
- S.alpha = 0
- shield_list += S.UID()
-
-/area/syndicate_depot/perimeter/proc/perimeter_shields_down()
- for(var/shuid in shield_list)
- var/obj/machinery/shieldwall/syndicate/S = locateUID(shuid)
- if(S)
- qdel(S)
- shield_list = list()
diff --git a/code/game/area/areas/ruins/blackmarketpackers.dm b/code/game/area/areas/ruins/blackmarketpackers.dm
deleted file mode 100644
index ab3330a2c00b0..0000000000000
--- a/code/game/area/areas/ruins/blackmarketpackers.dm
+++ /dev/null
@@ -1,23 +0,0 @@
-//Packer Ship Areas
-
-/area/ruin/unpowered/bmp_ship
- name = "\improper BMP Asteroids"
- icon_state = "away"
- report_alerts = FALSE
- requires_power = TRUE
-
-/area/ruin/unpowered/bmp_ship/aft
- name = "\improper BMP Aft Block"
- icon_state = "away1"
-
-/area/ruin/unpowered/bmp_ship/midship
- name = "\improper BMP Midship Block"
- icon_state = "away2"
-
-/area/ruin/unpowered/bmp_ship/fore
- name = "\improper BMP Fore Block"
- icon_state = "away3"
-
-/area/ruin/unpowered/bmp_ship/delta
- name = "\improper BMP Delta Block"
- icon_state = "away4"
diff --git a/code/game/area/areas/ruins/space_areas.dm b/code/game/area/areas/ruins/space_areas.dm
deleted file mode 100644
index 13727e83da1f3..0000000000000
--- a/code/game/area/areas/ruins/space_areas.dm
+++ /dev/null
@@ -1,279 +0,0 @@
-//Space Ruins
-
-/area/ruin/space
- var/baseturf = /turf/space
-
-/area/ruin/space/powered
- requires_power = FALSE
-
-/area/ruin/space/unpowered
- always_unpowered = FALSE
-
-/area/ruin/space/unpowered/no_grav
- has_gravity = FALSE
-
-/area/ruin/space/abandtele
- name = "\improper Abandoned Teleporter"
- icon_state = "teleporter"
- ambientsounds = list('sound/ambience/ambimalf.ogg', 'sound/ambience/signal.ogg')
- there_can_be_many = TRUE
-
-/area/ruin/space/unpowered/no_grav/way_home
- name = "\improper Salvation"
-
-// Old tcommsat
-/area/ruin/space/tcommsat
- name = "Telecommunications Satellite"
- icon_state = "tcomms"
-
-// Ruins of "onehalf" ship
-/area/ruin/space/onehalf/hallway
- name = "DK Excavator 453 Hallway"
- icon_state = "hallC"
-
-/area/ruin/space/onehalf/drone_bay
- name = "DK Excavator 453 Mining Drone Bay"
- icon_state = "engine"
-
-/area/ruin/space/onehalf/dorms_med
- name = "DK Excavator 453 Crew Quarters"
- icon_state = "Sleep"
-
-/area/ruin/space/onehalf/abandonedbridge
- name = "DK Excavator 453 Abandoned Bridge"
- icon_state = "bridge"
-
-// Ruins of the Unathi Breacher ship
-/area/ruin/space/unathi_breacher/engineering
- name = "Breacher Engine Bay"
-
-/area/ruin/space/unathi_breacher/dorms
- name = "Breacher Crew Quarters"
-
-/area/ruin/space/unathi_breacher/bar
- name = "Breacher Bar"
-
-/area/ruin/space/unathi_breacher/bridge
- name = "Breacher Bridge"
-
-/area/ruin/space/unathi_breacher/hold
- name = "Breacher Hold"
-
-//DJSTATION
-/area/ruin/space/djstation
- name = "\improper Soviet DJ Station"
- icon_state = "DJ"
-
-/area/ruin/space/djstation/solars
- name = "\improper Soviet DJ Station Solars"
- icon_state = "DJ"
-
-//Methlab
-/area/ruin/space/methlab
- name = "\improper Abandoned Drug Lab"
- icon_state = "green"
- there_can_be_many = TRUE
-
-// Space Bar
-/area/ruin/space/powered/bar
- name = "\improper Space Bar"
-
-//DERELICT (USSP station and USSP Teleporter)
-/area/ruin/space/derelict
- name = "\improper Derelict Station"
- icon_state = "storage"
-
-/area/ruin/space/derelict/hallway/primary
- name = "\improper Derelict Primary Hallway"
- icon_state = "hallP"
-
-/area/ruin/space/derelict/hallway/secondary
- name = "\improper Derelict Secondary Hallway"
- icon_state = "hallS"
-
-/area/ruin/space/derelict/arrival
- name = "\improper Derelict Arrival Centre"
- icon_state = "yellow"
-
-/area/ruin/space/derelict/storage/equipment
- name = "Derelict Equipment Storage"
-
-/area/ruin/space/derelict/storage/storage_access
- name = "Derelict Storage Access"
-
-/area/ruin/space/derelict/storage/engine_storage
- name = "Derelict Engine Storage"
- icon_state = "green"
-
-/area/ruin/space/derelict/bridge
- name = "\improper Derelict Control Room"
- icon_state = "bridge"
-
-/area/ruin/space/derelict/secret
- name = "\improper Derelict Secret Room"
- icon_state = "library"
-
-/area/ruin/space/derelict/bridge/access
- name = "Derelict Control Room Access"
- icon_state = "auxstorage"
-
-/area/ruin/space/derelict/bridge/ai_upload
- name = "\improper Derelict Computer Core"
- icon_state = "ai"
-
-/area/ruin/space/derelict/solar_control
- name = "\improper Derelict Solar Control"
- icon_state = "general_solar_control"
-
-/area/ruin/space/derelict/se_solar
- name = "\improper Derelict South East Solars"
- icon_state = "general_solars"
-
-/area/ruin/space/derelict/crew_quarters
- name = "\improper Derelict Crew Quarters"
- icon_state = "fitness"
-
-/area/ruin/space/derelict/medical
- name = "Derelict Medbay"
- icon_state = "medbay"
-
-/area/ruin/space/derelict/medical/morgue
- name = "\improper Derelict Morgue"
- icon_state = "morgue"
- is_haunted = TRUE
-
-/area/ruin/space/derelict/medical/chapel
- name = "\improper Derelict Chapel"
- icon_state = "chapel"
- is_haunted = TRUE
-
-/area/ruin/space/derelict/teleporter
- name = "\improper Derelict Teleporter"
- icon_state = "teleporter"
-
-/area/ruin/space/derelict/eva
- name = "Derelict EVA Storage"
- icon_state = "eva"
-
-/area/ruin/space/syndicate_druglab
- name = "Suspicious Station"
- icon_state = "red"
-
-/area/ruin/space/syndicate_druglab/asteroid
- name = "Suspicious Asteroid"
- icon_state = "dark"
- requires_power = FALSE
-
-/area/ruin/space/bubblegum_arena
- name = "Bubblegum Arena"
-
-/area/ruin/space/wreck_cargoship
- name = "Faint Signal"
- icon_state = "yellow"
-
-// Syndicate Listening Station
-
-/area/ruin/space/syndicate_listening_station
- name = "Listening Post"
- icon_state = "red"
-
-/area/ruin/space/syndicate_listening_station/asteroid
- name = "Listening Post Asteroid"
- icon_state = "dark"
- requires_power = FALSE
-
-/area/ruin/space/abandoned_engi_sat
- name = "Abandoned NT Engineering Satellite"
- apc_starts_off = TRUE
-
-/area/ruin/space/moonbase19
- name = "Moon Base 19"
- apc_starts_off = TRUE
-
-/area/ruin/space/mech_transport
- name = "Cybersun Mobile Exosuit Factory"
- apc_starts_off = TRUE
- there_can_be_many = FALSE
-
-/area/ruin/space/powered/casino
- name = "Dorian Casino"
- there_can_be_many = FALSE
- requires_power = TRUE
-
-/area/ruin/space/powered/casino/docked_ships
- name = "Dorian Casino Shuttle"
- requires_power = FALSE
-
-/area/ruin/space/powered/casino/arrivals
- name = "Dorian Casino Arrivals"
-
-/area/ruin/space/powered/casino/kitchen
- name = "Dorian Casino Dining and Kitchen"
-
-/area/ruin/space/powered/casino/floor
- name = "Dorian Casino Casino Floor"
-
-/area/ruin/space/powered/casino/hall
- name = "Dorian Casino Main Hall"
-
-/area/ruin/space/powered/casino/engine
- name = "Dorian Casino Engine Room"
-
-/area/ruin/space/powered/casino/security
- name = "Dorian Casino Security"
-
-/area/ruin/space/powered/casino/teleporter
- name = "Dorian Casino Teleporter"
-
-/area/ruin/space/powered/casino/maints
- name = "Dorian Casino Service Tunnels"
-
-/// telecomms: Alternative telecomms sat
-/area/ruin/space/telecomms
- name = "\improper Telecommunications Sat"
- icon_state = "tcomms"
- tele_proof = TRUE // No patrick, you can not syndicate teleport or hand teleport instantly into or out of this ruin
- ambientsounds = list('sound/ambience/dvorak_ambience_final.ogg')
- min_ambience_cooldown = 110 SECONDS // 3 seconds longer than the length of the song
- max_ambience_cooldown = 170 SECONDS // A minute break at most
-
-/area/ruin/space/telecomms/powercontrol
- name = "\improper Telecommunications Power Control"
- icon_state = "engine_smes"
-
-/area/ruin/space/telecomms/tele
- name = "\improper Tel3coMMunic@tions-SS-S KILL_Welcoming Room" // If you teleport to it. With a name like that. Thats on you.
- icon_state = "teleporter"
- tele_proof = FALSE // Oh, right. The teleporter room. The teleporter room for Kuzco, the poison chosen especially to teleport Kuzco, Kuzco's teleporter room. That teleporter room?
-
-/area/ruin/space/telecomms/foyer
- name = "\improper Telecommunications Foyer"
- icon_state = "entry"
-
-/area/ruin/space/telecomms/computer
- name = "\improper Telecommunications Control Room"
- icon_state = "bridge"
-
-/area/ruin/space/telecomms/chamber
- name = "\improper Telecommunications Central Compartment"
- icon_state = "ai_chamber"
-
-/area/ruin/space/deepstorage
- name = "Derelict Facility"
- apc_starts_off = TRUE
-
-/area/ruin/space/sec_shuttle
- name = "Abandoned Security Shuttle"
- apc_starts_off = TRUE
-
-/area/ruin/space/syndicakefactory
- name = "Syndicake factory"
-
-/area/ruin/space/clown_mime_ruin
- name = "\improper Derelict Transport Vessel"
-
-/area/ruin/space/clockwork_monastery
- name = "\improper Abandoned Clockwork Monastery"
- there_can_be_many = FALSE
- requires_power = FALSE
- ambientsounds = list("sound/ambience/reebe_ambience_1.ogg", "sound/ambience/reebe_ambience_2.ogg", "sound/ambience/reebe_ambience_3.ogg")
diff --git a/code/game/area/ss13_areas/procedure_areas.dm b/code/game/area/ss13_areas/procedure_areas.dm
deleted file mode 100644
index 12d2e177b5b29..0000000000000
--- a/code/game/area/ss13_areas/procedure_areas.dm
+++ /dev/null
@@ -1,4 +0,0 @@
-
-/area/station/procedure/trainer_office
- name = "\improper Trainer's Office"
- icon_state = "procedure_nct"
diff --git a/code/game/area/ss13_areas/science_areas.dm b/code/game/area/ss13_areas/science_areas.dm
deleted file mode 100644
index 2c2734fcbb6f9..0000000000000
--- a/code/game/area/ss13_areas/science_areas.dm
+++ /dev/null
@@ -1,96 +0,0 @@
-// Robotics areas
-
-/area/station/science/robotics
- name = "\improper Robotics Lab"
- icon_state = "robo"
- request_console_flags = RC_SUPPLY
- request_console_name = "Robotics"
-
-/area/station/science/robotics/chargebay
- name = "\improper Mech Bay"
- icon_state = "mechbay"
-
-/area/station/science/robotics/showroom
- name = "\improper Robotics Showroom"
- icon_state = "showroom"
-
-/area/station/science/research
- name = "Research Division"
- icon_state = "sci"
-
-/area/station/science/lobby
- name = "Research Division Lobby"
- icon_state = "sci"
-
-/area/station/science/testrange
- name = "Research Test Range"
- icon_state = "sci"
-
-/area/station/science/break_room
- name = "\improper Science Break Room"
- icon_state = "scibreak"
-
-/area/station/science/genetics
- name = "\improper Genetics Lab"
- icon_state = "genetics"
- request_console_flags = RC_ASSIST
- request_console_name = "Genetics"
-
-/area/station/science
- sound_environment = SOUND_AREA_STANDARD_STATION
-
-/area/station/science/rnd
- name = "Research and Development"
- icon_state = "rnd"
- request_console_flags = RC_SUPPLY
- request_console_name = "Science"
-
-/area/station/science/hallway
- name = "\improper Research Lab"
- icon_state = "sci"
-
-/area/station/science/xenobiology
- name = "\improper Xenobiology Lab"
- icon_state = "xenobio"
- xenobiology_compatible = TRUE
- request_console_flags = RC_ASSIST | RC_INFO
- request_console_name = "Xenobiology"
-
-/area/station/science/storage
- name = "\improper Science Toxins Storage"
- icon_state = "toxstorage"
-
-/area/station/science/toxins/test
- name = "\improper Toxins Test Area"
- icon_state = "toxtest"
- valid_territory = FALSE
-
-/area/station/science/toxins/mixing
- name = "\improper Toxins Mixing Room"
- icon_state = "toxmix"
- request_console_flags = RC_SUPPLY
- request_console_name = "Science"
-
-/area/station/science/toxins/launch
- name = "\improper Toxins Launch Room"
- icon_state = "toxlaunch"
- request_console_flags = RC_SUPPLY
- request_console_name = "Science"
-
-/area/station/science/misc_lab
- name = "\improper Research Testing Lab"
- icon_state = "scichem"
- request_console_flags = RC_SUPPLY
- request_console_name = "Science"
-
-/area/station/science/test_chamber
- name = "\improper Research Testing Chamber"
- icon_state = "scitest"
-
-/area/station/science/server
- name = "\improper Server Room"
- icon_state = "server"
-
-/area/station/science/server/coldroom
- name = "\improper Server Coldroom"
- icon_state = "servercold"
diff --git a/code/game/area/ss13_areas/security_areas.dm b/code/game/area/ss13_areas/security_areas.dm
deleted file mode 100644
index 7e6db172b93ad..0000000000000
--- a/code/game/area/ss13_areas/security_areas.dm
+++ /dev/null
@@ -1,140 +0,0 @@
-
-/area/station/security
- ambientsounds = HIGHSEC_SOUNDS
- sound_environment = SOUND_AREA_STANDARD_STATION
-
-/area/station/security/main
- name = "\improper Security Office"
- icon_state = "securityoffice"
- request_console_flags = RC_ASSIST | RC_INFO
- request_console_name = "Security"
-
-/area/station/security/lobby
- name = "\improper Security Lobby"
- icon_state = "securitylobby"
-
-/area/station/security/brig
- name = "\improper Brig"
- icon_state = "brig"
- request_console_flags = RC_ASSIST | RC_INFO
- request_console_name = "Security"
-
-/area/station/security/brig/prison_break()
- for(var/obj/structure/closet/secure_closet/brig/temp_closet in src)
- temp_closet.locked = FALSE
- temp_closet.close()
- for(var/obj/machinery/door_timer/temp_timer in src)
- temp_timer.releasetime = 1
- ..()
-
-/area/station/security/permabrig
- name = "\improper Prison Wing"
- icon_state = "sec_prison_perma"
- fast_despawn = TRUE
- can_get_auto_cryod = FALSE
-
-/area/station/security/prison
- name = "\improper Prison Wing"
- icon_state = "sec_prison"
- can_get_auto_cryod = FALSE
-
-/area/station/security/prison/prison_break()
- for(var/obj/structure/closet/secure_closet/brig/temp_closet in src)
- temp_closet.locked = FALSE
- temp_closet.close()
- temp_closet.update_icon()
- for(var/obj/machinery/door_timer/temp_timer in src)
- temp_timer.releasetime = 1
- ..()
-
-/area/station/security/prison/cell_block
- name = "\improper Prison Cell Block"
- icon_state = "brig"
-
-/area/station/security/prison/cell_block/a
- name = "\improper Prison Cell Block A"
- icon_state = "brigcella"
-
-/area/station/security/execution
- name = "Execution"
- icon_state = "execution"
- can_get_auto_cryod = FALSE
-
-/area/station/security/processing
- name = "Prisoner Processing"
- icon_state = "prisonerprocessing"
- can_get_auto_cryod = FALSE
-
-/area/station/security/interrogation
- name = "Interrogation"
- icon_state = "interrogation"
- can_get_auto_cryod = FALSE
-
-/area/station/security/storage
- name = "Security Equipment Storage"
- icon_state = "securityequipmentstorage"
- request_console_flags = RC_ASSIST | RC_INFO
- request_console_name = "Security"
-
-/area/station/security/evidence
- name = "\improper Evidence Room"
- icon_state = "evidence"
-
-/area/station/security/prisonlockers
- name = "\improper Prisoner Lockers"
- icon_state = "sec_prison_lockers"
- can_get_auto_cryod = FALSE
-
-/area/station/security/prisonershuttle
- name = "\improper Security Prisoner Shuttle"
- icon_state = "security"
- can_get_auto_cryod = FALSE
-
-/area/station/security/warden
- name = "\improper Warden's Office"
- icon_state = "Warden"
- sound_environment = SOUND_AREA_SMALL_SOFTFLOOR
- request_console_flags = RC_ASSIST | RC_SUPPLY | RC_INFO
- request_console_name = "Warden"
-
-/area/station/security/armory
- name = "\improper Armory"
- icon_state = "armory"
-
-/area/station/security/armory/secure
- name = "\improper Secure Armory"
- icon_state = "secarmory"
- request_console_flags = RC_ASSIST | RC_SUPPLY | RC_INFO
- request_console_name = "Warden"
-
-/area/station/security/detective
- name = "\improper Detective's Office"
- icon_state = "detective"
- ambientsounds = list('sound/ambience/ambidet1.ogg', 'sound/ambience/ambidet2.ogg')
- request_console_flags = RC_ASSIST | RC_INFO
- request_console_name = "Detective"
-
-/area/station/security/range
- name = "\improper Firing Range"
- icon_state = "firingrange"
-
-/area/station/security/defusal
- name = "\improper Defusal Workshop"
- icon_state = "defusal"
-
-// Checkpoints
-
-/area/station/security/checkpoint
- name = "\improper Security Checkpoint"
- icon_state = "checkpoint1"
-
-/area/station/security/checkpoint/secondary
- name = "\improper Security Checkpoint"
- icon_state = "checkpoint1"
- request_console_flags = RC_ASSIST | RC_INFO
- request_console_name = "Security"
-
-// Solitary
-/area/station/security/permasolitary
- name = "Solitary Confinement"
- icon_state = "solitary"
diff --git a/code/game/atom/atom_tool_acts.dm b/code/game/atom/atom_tool_acts.dm
deleted file mode 100644
index 6c56e5972da84..0000000000000
--- a/code/game/atom/atom_tool_acts.dm
+++ /dev/null
@@ -1,166 +0,0 @@
-/**
- * ## Item interaction
- *
- * Handles non-combat iteractions of a tool on this atom,
- * such as using a tool on a wall to deconstruct it,
- * or scanning someone with a health analyzer
- */
-/atom/proc/base_item_interaction(mob/living/user, obj/item/tool, list/modifiers)
- SHOULD_CALL_PARENT(TRUE)
- PROTECTED_PROC(TRUE)
-
- // We do not have a combat mode or secondary actions like /tg/, so instead
- // I'm unilaterally deciding it here: If you are not on harm intent, tool
- // interactions are not attacks. Shit like the autolathe accepting
- // screwdrivers on harm intent is unintuitive and needs to go away, and there
- // are dozens of ${TOOL}_act procs that do constant harm intent checks.
- var/tool_return = tool_act(user, tool, modifiers)
- if(tool_return)
- return tool_return
-
- var/early_sig_return = NONE
- /*
- * This is intentionally using `||` instead of `|` to short-circuit the signal calls
- * This is because we want to return early if ANY of these signals return a value
- *
- * This puts priority on the atom's signals, then the tool's signals, then the user's signals
- */
- early_sig_return = SEND_SIGNAL(src, COMSIG_INTERACT_TARGET, user, tool, modifiers) \
- || SEND_SIGNAL(tool, COMSIG_INTERACTING, user, src, modifiers) \
- || SEND_SIGNAL(user, COMSIG_INTERACT_USER, src, tool, modifiers)
-
- if(early_sig_return)
- return early_sig_return
-
- if(new_attack_chain)
- var/self_interaction = item_interaction(user, tool, modifiers)
- if(self_interaction)
- return self_interaction
-
- if(tool.new_attack_chain)
- var/interact_return = tool.interact_with_atom(src, user, modifiers)
- if(interact_return)
- return interact_return
-
- return NONE
-
-/**
- *
- * ## Tool Act
- *
- * Handles using specific tools on this atom directly.
- *
- * Handles the tool_acts in particular, such as wrenches and screwdrivers.
- *
- * This can be overriden to handle unique "tool interactions"
- * IE using an item like a tool (when it's not actually one)
- * but otherwise does nothing that [item_interaction] doesn't already do.
- *
- * In other words, use sparingly. It's harder to use (correctly) than [item_interaction].
- */
-/atom/proc/tool_act(mob/living/user, obj/item/tool, list/modifiers)
- SHOULD_CALL_PARENT(TRUE)
- PROTECTED_PROC(TRUE)
-
- if(SEND_SIGNAL(src, COMSIG_TOOL_ATTACK, tool, user) & COMPONENT_CANCEL_TOOLACT)
- return FALSE
-
- var/tool_type = tool.tool_behaviour
- if(!tool_type)
- return NONE
-
- var/act_result = NONE // or FALSE, or null, as some things may return
-
- switch(tool_type)
- if(TOOL_CROWBAR)
- act_result = crowbar_act(user, tool)
- if(TOOL_MULTITOOL)
- act_result = multitool_act(user, tool)
- if(TOOL_SCREWDRIVER)
- act_result = screwdriver_act(user, tool)
- if(TOOL_WRENCH)
- act_result = wrench_act(user, tool)
- if(TOOL_WIRECUTTER)
- act_result = wirecutter_act(user, tool)
- if(TOOL_WELDER)
- act_result = welder_act(user, tool)
-
- if(!act_result)
- return NONE
-
- return act_result
-
-/**
- * Called when this atom has an item used on it.
- * IE, a mob is clicking on this atom with an item.
- *
- * Return an ITEM_INTERACT_ flag in the event the interaction was handled, to cancel further interaction code.
- * Return NONE to allow default interaction / tool handling.
- */
-/atom/proc/item_interaction(mob/living/user, obj/item/used, list/modifiers)
- return NONE
-
-/**
- * Called when this item is being used to interact with an atom,
- * IE, a mob is clicking on an atom with this item.
- *
- * Return an ITEM_INTERACT_ flag in the event the interaction was handled, to cancel further interaction code.
- * Return NONE to allow default interaction / tool handling.
- */
-/obj/item/proc/interact_with_atom(atom/target, mob/living/user, list/modifiers)
- return NONE
-
-/**
- * ## Ranged item interaction
- *
- * Handles non-combat ranged interactions of a tool on this atom,
- * such as shooting a gun in the direction of someone*,
- * having a scanner you can point at someone to scan them at any distance,
- * or pointing a laser pointer at something.
- *
- * *While this intuitively sounds combat related, it is not,
- * because a "combat use" of a gun is gun-butting.
- */
-/atom/proc/base_ranged_item_interaction(mob/living/user, obj/item/tool, list/modifiers)
- SHOULD_CALL_PARENT(TRUE)
- PROTECTED_PROC(TRUE)
-
- // See [base_item_interaction] for defails on why this is using `||` (TL;DR it's short circuiting)
- var/early_sig_return = SEND_SIGNAL(src, COMSIG_INTERACT_RANGED, user, tool, modifiers) \
- || SEND_SIGNAL(tool, COMSIG_INTERACTING_RANGED, user, src, modifiers)
-
- if(early_sig_return)
- return early_sig_return
-
- var/self_interaction = ranged_item_interaction(user, tool, modifiers)
- if(self_interaction)
- return self_interaction
-
- var/interact_return = tool.ranged_interact_with_atom(src, user, modifiers)
- if(interact_return)
- return interact_return
-
- return NONE
-
-/**
- * Called when this atom has an item used on it from a distance.
- * IE, a mob is clicking on this atom with an item and is not adjacent.
- *
- * Does NOT include Telekinesis users, they are considered adjacent generally.
- *
- * Return an ITEM_INTERACT_ flag in the event the interaction was handled, to cancel further interaction code.
- */
-/atom/proc/ranged_item_interaction(mob/living/user, obj/item/tool, list/modifiers)
- return NONE
-
-/**
- * Called when this item is being used to interact with an atom from a distance,
- * IE, a mob is clicking on an atom with this item and is not adjacent.
- *
- * Does NOT include Telekinesis users, they are considered adjacent generally
- * (so long as this item is adjacent to the atom).
- *
- * Return an ITEM_INTERACT_ flag in the event the interaction was handled, to cancel further interaction code.
- */
-/obj/item/proc/ranged_interact_with_atom(atom/target, mob/living/user, list/modifiers)
- return NONE
diff --git a/code/game/dna/mutations/monkey_mutation.dm b/code/game/dna/mutations/monkey_mutation.dm
deleted file mode 100644
index e731abe19fe5d..0000000000000
--- a/code/game/dna/mutations/monkey_mutation.dm
+++ /dev/null
@@ -1,79 +0,0 @@
-/datum/mutation/monkey
- name = "Monkey"
-
-/datum/mutation/monkey/New()
- ..()
- block = GLOB.monkeyblock
-
-/datum/mutation/monkey/can_activate(mob/M, flags)
- return ishuman(M)
-
-/datum/mutation/monkey/activate(mob/living/carbon/human/H)
- ..()
- if(!istype(H))
- return
- if(issmall(H))
- return
- for(var/obj/item/W in H)
- if(istype(W, /obj/item/bio_chip))
- continue
- H.drop_item_to_ground(W)
-
- H.regenerate_icons()
- ADD_TRAIT(H, TRAIT_IMMOBILIZED, TRANSFORMING_TRAIT)
- ADD_TRAIT(H, TRAIT_HANDS_BLOCKED, TRANSFORMING_TRAIT)
- H.icon = null
- H.invisibility = 101
- var/has_primitive_form = H.dna.species.primitive_form // cache this
- if(has_primitive_form)
- H.set_species(has_primitive_form, keep_missing_bodyparts = TRUE)
-
- new /obj/effect/temp_visual/monkeyify(H.loc)
- addtimer(CALLBACK(src, PROC_REF(finish_monkeyize), H, !has_primitive_form), 2.2 SECONDS)
-
-/datum/mutation/monkey/proc/finish_monkeyize(mob/living/carbon/human/H, should_gib)
- H.invisibility = initial(H.invisibility)
-
- if(should_gib) //If the pre-change mob in question has no primitive set, this is going to be messy.
- H.gib()
- return
- REMOVE_TRAITS_IN(H, TRANSFORMING_TRAIT)
- to_chat(H, "You are now a [H.dna.species.name].")
-
-/datum/mutation/monkey/deactivate(mob/living/carbon/human/H)
- ..()
- if(!istype(H))
- return
- if(!issmall(H))
- return
- for(var/obj/item/W in H)
- if(W == H.w_uniform) // will be torn
- continue
- if(istype(W, /obj/item/bio_chip))
- continue
- H.drop_item_to_ground(W)
- H.regenerate_icons()
- ADD_TRAIT(H, TRAIT_IMMOBILIZED, TRANSFORMING_TRAIT)
- ADD_TRAIT(H, TRAIT_HANDS_BLOCKED, TRANSFORMING_TRAIT)
- H.icon = null
- H.invisibility = 101
- var/has_greater_form = H.dna.species.greater_form //cache this
- if(has_greater_form)
- H.set_species(has_greater_form, keep_missing_bodyparts = TRUE)
-
- new /obj/effect/temp_visual/monkeyify/humanify(H.loc)
- addtimer(CALLBACK(src, PROC_REF(finish_unmonkeyize), H, !has_greater_form), 2.2 SECONDS)
-
-/datum/mutation/monkey/proc/finish_unmonkeyize(mob/living/carbon/human/H, should_gib)
- REMOVE_TRAITS_IN(H, TRANSFORMING_TRAIT)
- H.invisibility = initial(H.invisibility)
-
- if(should_gib) //If the pre-change mob in question has no primitive set, this is going to be messy.
- H.gib()
- return
-
- H.real_name = H.dna.real_name
- H.name = H.real_name
-
- to_chat(H, "You are now a [H.dna.species.name].")
-
diff --git a/code/game/dna/mutations/mutation_powers.dm b/code/game/dna/mutations/mutation_powers.dm
deleted file mode 100644
index e13572621a01c..0000000000000
--- a/code/game/dna/mutations/mutation_powers.dm
+++ /dev/null
@@ -1,1208 +0,0 @@
-///////////////////////////////////
-// POWERS
-///////////////////////////////////
-
-/datum/mutation/nobreath
- name = "No Breathing"
- activation_messages = list("You feel no need to breathe.")
- deactivation_messages = list("You feel the need to breathe, once more.")
- instability = GENE_INSTABILITY_MAJOR
- traits_to_add = list(TRAIT_NOBREATH)
-
-/datum/mutation/nobreath/New()
- ..()
- block = GLOB.breathlessblock
-
-
-/datum/mutation/regenerate
- name = "Regenerate"
- activation_messages = list("Your wounds start healing.")
- deactivation_messages = list("Your regenerative powers feel like they've vanished.")
- instability = GENE_INSTABILITY_MINOR
-
-/datum/mutation/regenerate/New()
- ..()
- block = GLOB.regenerateblock
-
-/datum/mutation/regenerate/on_life(mob/living/carbon/human/H)
- if(!H.ignore_gene_stability && H.gene_stability < GENETIC_DAMAGE_STAGE_1)
- H.adjustBruteLoss(-0.25, FALSE)
- H.adjustFireLoss(-0.25)
- return
- H.adjustBruteLoss(-1, FALSE)
- H.adjustFireLoss(-1)
-
-/datum/mutation/heat_resist
- name = "Heat Resistance"
- activation_messages = list("Your skin is icy to the touch.")
- deactivation_messages = list("Your skin no longer feels icy to the touch.")
- instability = GENE_INSTABILITY_MODERATE
- traits_to_add = list(TRAIT_RESISTHEAT, TRAIT_RESISTHIGHPRESSURE)
-
-/datum/mutation/heat_resist/New()
- ..()
- block = GLOB.coldblock
-
-/datum/mutation/heat_resist/on_draw_underlays(mob/M, g)
- return "cold_s"
-
-/datum/mutation/cold_resist
- name = "Cold Resistance"
- activation_messages = list("Your body is filled with warmth.")
- deactivation_messages = list("Your body is no longer filled with warmth.")
- instability = GENE_INSTABILITY_MODERATE
- traits_to_add = list(TRAIT_RESISTCOLD, TRAIT_RESISTLOWPRESSURE)
-
-/datum/mutation/cold_resist/New()
- ..()
- block = GLOB.fireblock
-
-/datum/mutation/cold_resist/on_draw_underlays(mob/M, g)
- return "fire_s"
-
-/datum/mutation/noprints
- name = "No Prints"
- activation_messages = list("Your fingers feel numb.")
- deactivation_messages = list("Your fingers no longer feel numb.")
- instability = GENE_INSTABILITY_MODERATE
- traits_to_add = list(TRAIT_NOFINGERPRINTS)
-
-/datum/mutation/noprints/New()
- ..()
- block = GLOB.noprintsblock
-
-/datum/mutation/noshock
- name = "Shock Immunity"
- activation_messages = list("Your skin feels dry and unreactive.")
- deactivation_messages = list("Your skin no longer feels dry and unreactive.")
- instability = GENE_INSTABILITY_MODERATE
- traits_to_add = list(TRAIT_SHOCKIMMUNE)
-
-/datum/mutation/noshock/New()
- ..()
- block = GLOB.shockimmunityblock
-
-/datum/mutation/dwarf
- name = "Dwarf"
- activation_messages = list("Everything around you seems bigger now...")
- deactivation_messages = list("Everything around you seems to shrink...")
- instability = GENE_INSTABILITY_MODERATE
- traits_to_add = list(TRAIT_DWARF)
-
-/datum/mutation/dwarf/New()
- ..()
- block = GLOB.smallsizeblock
-
-/datum/mutation/dwarf/activate(mob/M)
- ..()
- M.pass_flags |= PASSTABLE
- M.resize = 0.8
- M.update_transform()
-
-/datum/mutation/dwarf/deactivate(mob/M)
- ..()
- M.pass_flags &= ~PASSTABLE
- M.resize = 1.25
- M.update_transform()
-
-// OLD HULK BEHAVIOR
-/datum/mutation/hulk
- name = "Hulk"
- activation_messages = list("Your muscles hurt.")
- deactivation_messages = list("Your muscles shrink.")
- instability = GENE_INSTABILITY_MAJOR
- traits_to_add = list(TRAIT_HULK, TRAIT_CHUNKYFINGERS)
-
-/datum/mutation/hulk/New()
- ..()
- block = GLOB.hulkblock
-
-/datum/mutation/hulk/activate(mob/living/carbon/human/M)
- ..()
- var/status = CANSTUN | CANWEAKEN | CANPARALYSE | CANPUSH
- M.status_flags &= ~status
- M.update_body()
-
-/datum/mutation/hulk/deactivate(mob/living/carbon/human/M)
- ..()
- M.status_flags |= CANSTUN | CANWEAKEN | CANPARALYSE | CANPUSH
- M.update_body()
-
-/datum/mutation/hulk/on_life(mob/living/carbon/human/M)
- if(!istype(M))
- return
- if(M.health <= 0)
- M.dna.SetSEState(GLOB.hulkblock, 0)
- singlemutcheck(M, GLOB.hulkblock, MUTCHK_FORCED)
- M.update_mutations() //update our mutation overlays
- M.update_body()
- M.status_flags |= CANSTUN | CANWEAKEN | CANPARALYSE | CANPUSH //temporary fix until the problem can be solved.
- to_chat(M, "You suddenly feel very weak.")
-
-/datum/mutation/tk
- name = "Telekenesis"
- activation_messages = list("You feel smarter.")
- deactivation_messages = list("You feel dumber.")
- instability = GENE_INSTABILITY_MAJOR
- traits_to_add = list(TRAIT_TELEKINESIS)
-
-/datum/mutation/tk/New()
- ..()
- block = GLOB.teleblock
-
-/datum/mutation/tk/on_draw_underlays(mob/M, g)
- return "telekinesishead_s"
-
-#define EAT_MOB_DELAY 300 // 30s
-
-// WAS: /datum/bioEffect/alcres
-/datum/mutation/sober
- name = "Sober"
- activation_messages = list("You feel unusually sober.")
- deactivation_messages = list("You feel like you could use a stiff drink.")
- instability = GENE_INSTABILITY_MINOR
-
- traits_to_add = list(TRAIT_ALCOHOL_TOLERANCE)
-
-/datum/mutation/sober/New()
- ..()
- block = GLOB.soberblock
-
-//WAS: /datum/bioEffect/psychic_resist
-/datum/mutation/psychic_resist
- name = "Psy-Resist"
- desc = "Boosts efficiency in sectors of the brain commonly associated with meta-mental energies."
- activation_messages = list("Your mind feels closed.")
- deactivation_messages = list("You feel oddly exposed.")
- instability = GENE_INSTABILITY_MINOR
-
-/datum/mutation/psychic_resist/New()
- ..()
- block = GLOB.psyresistblock
-
-/////////////////////////
-// Stealth Enhancers
-/////////////////////////
-
-/datum/mutation/stealth
- instability = GENE_INSTABILITY_MAJOR
-
-/datum/mutation/stealth/can_activate(mob/M, flags)
- // Can only activate one of these at a time.
- if(is_type_in_list(/datum/mutation/stealth, M.active_mutations))
- testing("Cannot activate [type]: /datum/mutation/stealth in M.active_mutations.")
- return FALSE
- return ..()
-
-/datum/mutation/stealth/deactivate(mob/living/M)
- ..()
- M.reset_visibility()
-
-// WAS: /datum/bioEffect/darkcloak
-/datum/mutation/stealth/darkcloak
- name = "Cloak of Darkness"
- desc = "Enables the subject to bend low levels of light around themselves, creating a cloaking effect."
- activation_messages = list("You begin to fade into the shadows.")
- deactivation_messages = list("You become fully visible.")
-
-/datum/mutation/stealth/darkcloak/New()
- ..()
- block = GLOB.shadowblock
-
-/datum/mutation/stealth/darkcloak/deactivate(mob/living/M)
- ..()
- if(ishuman(M))
- var/mob/living/carbon/human/H = M
- H.set_alpha_tracking(ALPHA_VISIBLE, src)
- if(!ishuman(M))
- return
- var/mob/living/carbon/human/H = M
- H.set_alpha_tracking(ALPHA_VISIBLE, src)
-/datum/mutation/stealth/darkcloak/on_life(mob/M)
- var/turf/simulated/T = get_turf(M)
- if(!istype(T) || !ishuman(M))
- return
- var/mob/living/carbon/human/H = M
- var/light_available = T.get_lumcount() * 10
- if(light_available <= 2)
- if(H.invisibility != INVISIBILITY_LEVEL_TWO)
- H.set_alpha_tracking(H.get_alpha() * 0.8, src)
- else
- H.reset_visibility()
- H.set_alpha_tracking(ALPHA_VISIBLE * 0.8, src)
- if(H.get_alpha(src) == 0)
- H.make_invisible()
-
-//WAS: /datum/bioEffect/chameleon
-/datum/mutation/stealth/chameleon
- name = "Chameleon"
- desc = "The subject becomes able to subtly alter light patterns to become invisible, as long as they remain still."
- activation_messages = list("You feel one with your surroundings.")
- deactivation_messages = list("You feel oddly visible.")
-
-/datum/mutation/stealth/chameleon/New()
- ..()
- block = GLOB.chameleonblock
-
-/datum/mutation/stealth/chameleon/deactivate(mob/living/M)
- ..()
- if(ishuman(M))
- var/mob/living/carbon/human/H = M
- H.set_alpha_tracking(ALPHA_VISIBLE, src)
-
-/datum/mutation/stealth/chameleon/on_life(mob/living/M)
- if(!ishuman(M))
- return
- var/mob/living/carbon/human/H = M
- if((world.time - H.last_movement) >= 30 && !H.stat && (H.mobility_flags & MOBILITY_STAND) && !H.restrained())
- if(H.invisibility != INVISIBILITY_LEVEL_TWO)
- H.set_alpha_tracking(H.get_alpha() - 25, src)
- else
- H.reset_visibility()
- H.set_alpha_tracking(ALPHA_VISIBLE * 0.8, src)
- if(H.get_alpha(src) == 0)
- H.make_invisible()
-
-/////////////////////////////////////////////////////////////////////////////////////////
-
-/datum/mutation/grant_spell
- var/datum/spell/spelltype
-
-/datum/mutation/grant_spell/activate(mob/M)
- M.AddSpell(new spelltype(null))
- ..()
- return TRUE
-
-/datum/mutation/grant_spell/deactivate(mob/M)
- for(var/datum/spell/S in M.mob_spell_list)
- if(istype(S, spelltype))
- M.RemoveSpell(S)
- ..()
- return TRUE
-
-// WAS: /datum/bioEffect/cryokinesis
-/datum/mutation/grant_spell/cryo
- name = "Cryokinesis"
- desc = "Allows the subject to lower the body temperature of others."
- activation_messages = list("You notice a strange cold tingle in your fingertips.")
- deactivation_messages = list("Your fingers feel warmer.")
- instability = GENE_INSTABILITY_MODERATE
- spelltype = /datum/spell/cryokinesis
-
-/datum/mutation/grant_spell/cryo/New()
- ..()
- block = GLOB.cryoblock
-
-/datum/spell/cryokinesis
- name = "Cryokinesis"
- desc = "Drops the bodytemperature of another person."
-
- base_cooldown = 1200
-
- clothes_req = FALSE
- stat_allowed = CONSCIOUS
- antimagic_flags = NONE
-
- selection_activated_message = "Your mind grow cold. Click on a target to cast the spell."
- selection_deactivated_message = "Your mind returns to normal."
- invocation_type = "none"
- var/list/compatible_mobs = list(/mob/living/carbon/human)
-
- action_icon_state = "genetic_cryo"
-
-/datum/spell/cryokinesis/create_new_targeting()
- var/datum/spell_targeting/click/T = new()
- T.allowed_type = /mob/living/carbon
- T.click_radius = 0
- T.try_auto_target = FALSE // Give the clueless geneticists a way out and to have them not target themselves
- T.selection_type = SPELL_SELECTION_RANGE
- T.include_user = TRUE
- return T
-
-/datum/spell/cryokinesis/cast(list/targets, mob/user = usr)
-
- var/mob/living/carbon/C = targets[1]
-
- if(HAS_TRAIT(C, TRAIT_RESISTCOLD))
- C.visible_message("A cloud of fine ice crystals engulfs [C.name], but disappears almost instantly!")
- return
- var/handle_suit = FALSE
- if(ishuman(C))
- var/mob/living/carbon/human/H = C
- if(istype(H.head, /obj/item/clothing/head/helmet/space))
- if(istype(H.wear_suit, /obj/item/clothing/suit/space))
- handle_suit = TRUE
- if(H.internal)
- H.visible_message("[user] sprays a cloud of fine ice crystals, engulfing [H]!",
- "[user] sprays a cloud of fine ice crystals over your [H.head]'s visor.")
- else
- H.visible_message("[user] sprays a cloud of fine ice crystals engulfing, [H]!",
- "[user] sprays a cloud of fine ice crystals cover your [H.head]'s visor and make it into your air vents!.")
-
- H.bodytemperature = max(0, H.bodytemperature - 100)
- add_attack_logs(user, C, "Cryokinesis")
- if(!handle_suit)
- C.bodytemperature = max(0, C.bodytemperature - 200)
- C.ExtinguishMob()
-
- C.visible_message("[user] sprays a cloud of fine ice crystals, engulfing [C]!")
- add_attack_logs(user, C, "Cryokinesis- NO SUIT/INTERNALS")
-
-///////////////////////////////////////////////////////////////////////////////////////////
-
-// WAS: /datum/bioEffect/mattereater
-/datum/mutation/grant_spell/mattereater
- name = "Matter Eater"
- desc = "Allows the subject to eat just about anything without harm."
- activation_messages = list("You feel hungry.")
- deactivation_messages = list("You don't feel quite so hungry anymore.")
- instability = GENE_INSTABILITY_MODERATE
-
- spelltype=/datum/spell/eat
-
-/datum/mutation/grant_spell/mattereater/New()
- ..()
- block = GLOB.eatblock
-
-/datum/spell/eat
- name = "Eat"
- desc = "Eat just about anything!"
-
- base_cooldown = 300
-
- clothes_req = FALSE
- stat_allowed = CONSCIOUS
- invocation_type = "none"
- antimagic_flags = NONE
-
- action_icon_state = "genetic_eat"
-
-/datum/spell/eat/create_new_targeting()
- return new /datum/spell_targeting/matter_eater
-
-/datum/spell/eat/can_cast(mob/user = usr, charge_check = TRUE, show_message = FALSE)
- . = ..()
- if(!.)
- return
- var/can_eat = TRUE
- if(iscarbon(user))
- var/mob/living/carbon/C = user
- if((C.head && (C.head.flags_cover & HEADCOVERSMOUTH)) || (C.wear_mask && (C.wear_mask.flags_cover & MASKCOVERSMOUTH) && !C.wear_mask.up))
- if(show_message)
- to_chat(C, "Your mouth is covered, preventing you from eating!")
- can_eat = FALSE
- return can_eat
-
-/datum/spell/eat/proc/doHeal(mob/user)
- if(ishuman(user))
- var/mob/living/carbon/human/H = user
- for(var/name in H.bodyparts_by_name)
- var/obj/item/organ/external/affecting = null
- if(!H.bodyparts_by_name[name])
- continue
- affecting = H.bodyparts_by_name[name]
- if(!is_external_organ(affecting))
- continue
- affecting.heal_damage(4, 0, updating_health = FALSE)
- H.UpdateDamageIcon()
- H.updatehealth()
-
-
-
-/datum/spell/eat/cast(list/targets, mob/user = usr)
- if(!length(targets))
- to_chat(user, "No target found in range.")
- return
-
- var/atom/movable/the_item = targets[1]
- if(!user.Adjacent(the_item))
- to_chat(user, "You need to be next to [the_item] for this!")
- return FALSE
- if(ishuman(the_item))
- var/mob/living/carbon/human/H = the_item
- var/obj/item/organ/external/limb = H.get_organ(user.zone_selected)
- if(!istype(limb))
- to_chat(user, "You can't eat this part of them!")
- revert_cast()
- return FALSE
- if(istype(limb,/obj/item/organ/external/head))
- // Bullshit, but prevents being unable to clone someone.
- to_chat(user, "You try to put \the [limb] in your mouth, but [the_item.p_their()] ears tickle your throat!")
- revert_cast()
- return FALSE
- if(istype(limb,/obj/item/organ/external/chest))
- // Bullshit, but prevents being able to instagib someone.
- to_chat(user, "You try to put [the_item.p_their()] [limb] in your mouth, but it's too big to fit!")
- revert_cast()
- return FALSE
- user.visible_message("[user] begins stuffing [the_item]'s [limb.name] into [user.p_their()] gaping maw!")
- if(!do_mob(user, H, EAT_MOB_DELAY))
- to_chat(user, "You were interrupted before you could eat [the_item]!")
- else
- if(!limb || !H)
- return
- if(!user.Adjacent(the_item))
- to_chat(user, "You need to be next to [the_item] for this!")
- return FALSE
- user.visible_message("[user] [pick("chomps","bites")] off [the_item]'s [limb]!")
- playsound(user.loc, 'sound/items/eatfood.ogg', 50, 0)
-
- // Most limbs will drop here. Groin won't, but this
- // still spills out the organs that were in it.
- limb.droplimb(FALSE, DROPLIMB_SHARP)
- if(istype(limb, /obj/item/organ/external/groin))
- limb.receive_damage(100, sharp = TRUE)
-
- var/obj/item/organ/external/left_leg = H.get_organ(BODY_ZONE_L_LEG)
- if(istype(left_leg))
- left_leg.droplimb(FALSE, DROPLIMB_SHARP)
-
- var/obj/item/organ/external/right_leg = H.get_organ(BODY_ZONE_R_LEG)
- if(istype(right_leg))
- right_leg.droplimb(FALSE, DROPLIMB_SHARP)
-
- var/obj/item/organ/external/chest = H.get_organ(BODY_ZONE_CHEST)
- if(istype(chest))
- chest.receive_damage(50, sharp = TRUE)
-
- doHeal(user)
-
- return
-
- if(ismob(the_item.loc) && isitem(the_item))
- var/obj/item/eaten = the_item
- var/mob/the_owner = the_item.loc
- if(!the_owner.drop_item_to_ground(eaten, silent = TRUE))
- to_chat(user, "You can't eat [the_item], it won't go down your throat!")
- return
- user.visible_message("[user] eats [the_item].")
- playsound(user.loc, 'sound/items/eatfood.ogg', 50, FALSE)
- if(ishuman(user))
- var/mob/living/carbon/human/H = user
- var/obj/item/organ/external/chest/target_place = H.get_organ(BODY_ZONE_CHEST)
- if(istype(target_place))
- the_item.forceMove(target_place)
- doHeal(user)
- return
-
- qdel(the_item)
- doHeal(user)
-
-////////////////////////////////////////////////////////////////////////
-
-//WAS: /datum/bioEffect/jumpy
-/datum/mutation/grant_spell/jumpy
- name = "Jumpy"
- desc = "Allows the subject to leap great distances."
- //cooldown = 30
- activation_messages = list("Your leg muscles feel taut and strong.")
- deactivation_messages = list("Your leg muscles shrink back to normal.")
- instability = GENE_INSTABILITY_MODERATE
-
- spelltype =/datum/spell/leap
-
-/datum/mutation/grant_spell/jumpy/New()
- ..()
- block = GLOB.jumpblock
-
-/datum/spell/leap
- name = "Jump"
- desc = "Leap great distances!"
- base_cooldown = 60
-
- clothes_req = FALSE
- stat_allowed = CONSCIOUS
- invocation_type = "none"
- antimagic_flags = NONE
-
- action_icon_state = "genetic_jump"
- var/leap_distance = 10
-
-/datum/spell/leap/create_new_targeting()
- return new /datum/spell_targeting/self
-
-/datum/spell/leap/cast(list/targets, mob/living/user = usr)
- var/failure = FALSE
- if(ismob(user.loc) || IS_HORIZONTAL(user) || user.IsStunned() || user.buckled || user.stat)
- to_chat(user, "You can't jump right now!")
- return
-
- if(isturf(user.loc))
- if(user.restrained())//Why being pulled while cuffed prevents you from moving
- for(var/mob/living/M in range(user, 1))
- if(M.pulling == user)
- if(!M.restrained() && M.stat == CONSCIOUS && !(M.mobility_flags & MOBILITY_STAND) && user.Adjacent(M))
- failure = TRUE
- else
- M.stop_pulling()
-
- user.visible_message("[user.name] takes a huge leap!")
- playsound(user.loc, 'sound/weapons/thudswoosh.ogg', 50, 1)
- if(failure)
- user.Weaken(10 SECONDS)
- user.visible_message("[user] attempts to leap away but is slammed back down to the ground!",
- "You attempt to leap away but are suddenly slammed back down to the ground!",
- "You hear the flexing of powerful muscles and suddenly a crash as a body hits the floor.")
- return FALSE
- var/prevLayer = user.layer
- user.layer = 9
-
- ADD_TRAIT(user, TRAIT_FLYING, "leap")
- for(var/i in 1 to leap_distance)
- var/turf/hit_turf = get_step(user, user.dir)
- var/atom/hit_atom = get_blocking_atom(hit_turf)
- if(hit_atom)
- hit_atom.hit_by_thrown_mob(user, damage = 10)
- break
-
- step(user, user.dir)
- if(i < 6)
- user.pixel_y += 8
- else
- user.pixel_y -= 8
- sleep(1)
-
- REMOVE_TRAIT(user, TRAIT_FLYING, "leap")
- user.pixel_y = 0 // In case leap was varedited to be longer or shorter
-
- if(HAS_TRAIT(user, TRAIT_FAT) && prob(66))
- user.visible_message("[user.name] crashes due to [user.p_their()] heavy weight!")
- //playsound(user.loc, 'zhit.wav', 50, 1)
- user.AdjustWeakened(20 SECONDS)
- user.AdjustStunned(10 SECONDS)
-
- user.layer = prevLayer
-
- if(isobj(user.loc))
- var/obj/container = user.loc
- to_chat(user, "You leap and slam your head against the inside of [container]! Ouch!")
- user.AdjustParalysis(6 SECONDS)
- user.AdjustWeakened(10 SECONDS)
- container.visible_message("[user.loc] emits a loud thump and rattles a bit.")
- playsound(user.loc, 'sound/effects/bang.ogg', 50, 1)
- var/wiggle = 6
- while(wiggle > 0)
- wiggle--
- container.pixel_x = rand(-3,3)
- container.pixel_y = rand(-3,3)
- sleep(1)
- container.pixel_x = 0
- container.pixel_y = 0
-
-/datum/spell/leap/proc/get_blocking_atom(turf/turf_to_check)
- if(!turf_to_check)
- return FALSE
-
- if(turf_to_check.density)
- return turf_to_check
-
- for(var/atom/movable/hit_thing in turf_to_check)
- if(isliving(hit_thing))
- var/mob/living/hit_mob = hit_thing
- if(hit_mob.density)
- return hit_mob
-
- if(isobj(hit_thing))
- var/obj/hit_obj = hit_thing
- if(hit_obj.density)
- return hit_obj
-
- return FALSE
-
-////////////////////////////////////////////////////////////////////////
-
-// WAS: /datum/bioEffect/polymorphism
-
-/datum/mutation/grant_spell/polymorph
- name = "Polymorphism"
- desc = "Enables the subject to reconfigure their appearance to mimic that of others."
-
- spelltype =/datum/spell/polymorph
- //cooldown = 1800
- activation_messages = list("You don't feel entirely like yourself somehow.")
- deactivation_messages = list("You feel secure in your identity.")
- instability = GENE_INSTABILITY_MODERATE
-
-/datum/mutation/grant_spell/polymorph/New()
- ..()
- block = GLOB.polymorphblock
-
-/datum/spell/polymorph
- name = "Polymorph"
- desc = "Mimic the appearance of others!"
- base_cooldown = 1800
-
- clothes_req = FALSE
- stat_allowed = CONSCIOUS
-
- selection_activated_message = "You body becomes unstable. Click on a target to cast transform into them."
- selection_deactivated_message = "Your body calms down again."
-
- invocation_type = "none"
- antimagic_flags = NONE
-
- action_icon_state = "genetic_poly"
-
-/datum/spell/polymorph/create_new_targeting()
- var/datum/spell_targeting/click/T = new()
- T.try_auto_target = FALSE
- T.click_radius = -1
- T.range = 1
- T.selection_type = SPELL_SELECTION_RANGE
- return T
-
-/datum/spell/polymorph/cast(list/targets, mob/user = usr)
- var/mob/living/carbon/human/target = targets[1]
-
- user.visible_message("[user]'s body shifts and contorts.")
-
- spawn(10)
- if(target && user)
- playsound(user.loc, 'sound/goonstation/effects/gib.ogg', 50, 1)
- var/mob/living/carbon/human/H = user
- H.UpdateAppearance(target.dna.UI)
- H.real_name = target.real_name
- H.name = target.name
-
-////////////////////////////////////////////////////////////////////////
-
-// WAS: /datum/bioEffect/empath
-/datum/mutation/grant_spell/empath
- name = "Empathic Thought"
- desc = "The subject becomes able to read the minds of others for certain information."
-
- spelltype = /datum/spell/empath
- activation_messages = list("You suddenly notice more about others than you did before.")
- deactivation_messages = list("You no longer feel able to sense intentions.")
- instability = GENE_INSTABILITY_MINOR
-
-/datum/mutation/grant_spell/empath/New()
- ..()
- block = GLOB.empathblock
-
-/datum/spell/empath
- name = "Read Mind"
- desc = "Read the minds of others for information."
- base_cooldown = 18 SECONDS
- clothes_req = FALSE
- human_req = TRUE
- stat_allowed = CONSCIOUS
- invocation_type = "none"
- antimagic_flags = MAGIC_RESISTANCE_MIND
-
- action_icon_state = "genetic_empath"
-
-/datum/spell/empath/create_new_targeting()
- var/datum/spell_targeting/targeted/T = new()
- T.allowed_type = /mob/living/carbon
- T.selection_type = SPELL_SELECTION_RANGE
- return T
-
-/datum/spell/empath/cast(list/targets, mob/user = usr)
- for(var/mob/living/carbon/M in targets)
- if(!iscarbon(M))
- to_chat(user, "You may only use this on other organic beings.")
- return
-
- if(M.dna?.GetSEState(GLOB.psyresistblock))
- to_chat(user, "You can't see into [M.name]'s mind at all!")
- return
-
- if(M.stat == DEAD)
- to_chat(user, "[M.name] is dead and cannot have [M.p_their()] mind read.")
- return
- if(M.health < 0)
- to_chat(user, "[M.name] is dying, and [M.p_their()] thoughts are too scrambled to read.")
- return
-
- to_chat(user, "Mind Reading of [M.name]:")
-
- var/pain_condition = M.health / M.maxHealth
- // lower health means more pain
- var/list/randomthoughts = list("what to have for lunch","the future","the past","money",
- "[M.p_their()] hair","what to do next","[M.p_their()] job","space","amusing things","sad things",
- "annoying things","happy things","something incoherent","something [M.p_they()] did wrong")
- var/thoughts = "thinking about [pick(randomthoughts)]"
-
- if(M.fire_stacks)
- pain_condition -= 0.5
- thoughts = "preoccupied with the fire"
-
- if(M.radiation)
- pain_condition -= 0.25
-
- switch(pain_condition)
- if(0.81 to INFINITY)
- to_chat(user, "Condition: [M.name] feels good.")
- if(0.61 to 0.8)
- to_chat(user, "Condition: [M.name] is suffering mild pain.")
- if(0.41 to 0.6)
- to_chat(user, "Condition: [M.name] is suffering significant pain.")
- if(0.21 to 0.4)
- to_chat(user, "Condition: [M.name] is suffering severe pain.")
- else
- to_chat(user, "Condition: [M.name] is suffering excruciating pain.")
- thoughts = "haunted by [M.p_their()] own mortality"
-
- switch(M.a_intent)
- if(INTENT_HELP)
- to_chat(user, "Mood: You sense benevolent thoughts from [M.name].")
- if(INTENT_DISARM)
- to_chat(user, "Mood: You sense cautious thoughts from [M.name].")
- if(INTENT_GRAB)
- to_chat(user, "Mood: You sense hostile thoughts from [M.name].")
- if(INTENT_HARM)
- to_chat(user, "Mood: You sense cruel thoughts from [M.name].")
- for(var/mob/living/L in view(7,M))
- if(L == M)
- continue
- thoughts = "thinking about punching [L.name]"
- break
- else
- to_chat(user, "Mood: You sense strange thoughts from [M.name].")
-
- if(ishuman(M))
- var/numbers[0]
- var/mob/living/carbon/human/H = M
- if(H.mind && H.mind.initial_account)
- numbers += H.mind.initial_account.account_number
- numbers += H.mind.initial_account.account_pin
- if(length(numbers)>0)
- to_chat(user, "Numbers: You sense the number[length(numbers)>1?"s":""] [english_list(numbers)] [length(numbers)>1?"are":"is"] important to [M.name].")
- to_chat(user, "Thoughts: [M.name] is currently [thoughts].")
-
- if(M.dna?.GetSEState(GLOB.empathblock))
- to_chat(M, "You sense [user.name] reading your mind.")
- else if(prob(5) || M.mind?.assigned_role=="Chaplain")
- to_chat(M, "You sense someone intruding upon your thoughts...")
-
-///////////////////Vanilla Morph////////////////////////////////////
-
-/datum/mutation/grant_spell/morph
- name = "Morphism"
- desc = "Enables the subject to reconfigure their appearance to that of any human."
- spelltype =/datum/spell/morph
- activation_messages = list("Your body feels like it can alter its appearance.")
- deactivation_messages = list("Your body doesn't feel capable of altering its appearance.")
- instability = GENE_INSTABILITY_MODERATE
-
-/datum/mutation/grant_spell/morph/New()
- ..()
- block = GLOB.morphblock
-
-/datum/spell/morph
- name = "Morph"
- desc = "Mimic the appearance of your choice!"
- base_cooldown = 1800
-
- clothes_req = FALSE
- stat_allowed = CONSCIOUS
- invocation_type = "none"
- antimagic_flags = NONE
-
- action_icon_state = "genetic_morph"
-
-/datum/spell/morph/create_new_targeting()
- return new /datum/spell_targeting/self
-
-/datum/spell/morph/cast(list/targets, mob/user = usr)
- if(!ishuman(user))
- return
-
- if(ismob(user.loc))
- to_chat(user, "You can't change your appearance right now!")
- return
- var/mob/living/carbon/human/M = user
- var/obj/item/organ/external/head/head_organ = M.get_organ("head")
- var/obj/item/organ/internal/eyes/eyes_organ = M.get_int_organ(/obj/item/organ/internal/eyes)
-
- var/new_gender = tgui_alert(user, "Please select gender.", "Character Generation", list("Male", "Female"))
- if(new_gender)
- if(new_gender == "Male")
- M.change_gender(MALE)
- else
- M.change_gender(FEMALE)
-
- if(eyes_organ)
- var/new_eyes = tgui_input_color(user, "Please select eye color.", "Character Generation", eyes_organ.eye_color)
- if(isnull(new_eyes))
- return
- M.change_eye_color(new_eyes)
-
- if(istype(head_organ))
- //Alt heads.
- if(head_organ.dna.species.bodyflags & HAS_ALT_HEADS)
- var/list/valid_alt_heads = M.generate_valid_alt_heads()
- var/new_alt_head = tgui_input_list(user, "Please select alternate head", "Character Generation", valid_alt_heads)
- if(new_alt_head)
- M.change_alt_head(new_alt_head)
-
- // hair
- var/list/valid_hairstyles = M.generate_valid_hairstyles()
- var/new_style = tgui_input_list(user, "Please select hair style", "Character Generation", valid_hairstyles)
-
- // if new style selected (not cancel)
- if(new_style)
- M.change_hair(new_style)
-
- var/new_hair = tgui_input_color(user, "Please select hair color.", "Character Generation", head_organ.hair_colour)
- if(!isnull(new_hair))
- M.change_hair_color(new_hair)
-
- var/datum/sprite_accessory/hair_style = GLOB.hair_styles_public_list[head_organ.h_style]
- if(hair_style.secondary_theme && !hair_style.no_sec_colour)
- new_hair = tgui_input_color(user, "Please select secondary hair color.", "Character Generation", head_organ.sec_hair_colour)
- if(!isnull(new_hair))
- M.change_hair_color(new_hair, TRUE)
-
- // facial hair
- var/list/valid_facial_hairstyles = M.generate_valid_facial_hairstyles()
- new_style = tgui_input_list(user, "Please select facial style", "Character Generation", valid_facial_hairstyles)
-
- if(new_style)
- M.change_facial_hair(new_style)
-
- var/new_facial = tgui_input_color(user, "Please select facial hair color.", "Character Generation", head_organ.facial_colour)
- if(!isnull(new_facial))
- M.change_facial_hair_color(new_facial)
-
- var/datum/sprite_accessory/facial_hair_style = GLOB.facial_hair_styles_list[head_organ.f_style]
- if(facial_hair_style.secondary_theme && !facial_hair_style.no_sec_colour)
- new_facial = tgui_input_color(user, "Please select secondary facial hair color.", "Character Generation", head_organ.sec_facial_colour)
- if(!isnull(new_facial))
- M.change_facial_hair_color(new_facial, TRUE)
-
- //Head accessory.
- if(head_organ.dna.species.bodyflags & HAS_HEAD_ACCESSORY)
- var/list/valid_head_accessories = M.generate_valid_head_accessories()
- var/new_head_accessory = tgui_input_list(user, "Please select head accessory style", "Character Generation", valid_head_accessories)
- if(!isnull(new_head_accessory))
- M.change_head_accessory(new_head_accessory)
-
- var/new_head_accessory_colour = tgui_input_color(user, "Please select head accessory color.", "Character Generation", head_organ.headacc_colour)
- if(!isnull(new_head_accessory_colour))
- M.change_head_accessory_color(new_head_accessory_colour)
-
-
- //Body accessory.
- if((M.dna.species.tail && M.dna.species.bodyflags & (HAS_TAIL)) || (M.dna.species.wing && M.dna.species.bodyflags & (HAS_WING)))
- var/list/valid_body_accessories = M.generate_valid_body_accessories()
- if(length(valid_body_accessories) > 1) //By default valid_body_accessories will always have at the very least a 'none' entry populating the list, even if the user's species is not present in any of the list items.
- var/new_body_accessory = tgui_input_list(user, "Please select body accessory style", "Character Generation", valid_body_accessories)
- if(!isnull(new_body_accessory))
- M.change_body_accessory(new_body_accessory)
-
- if(istype(head_organ))
- //Head markings.
- if(M.dna.species.bodyflags & HAS_HEAD_MARKINGS)
- var/list/valid_head_markings = M.generate_valid_markings("head")
- var/new_marking = tgui_input_list(user, "Please select head marking style", "Character Generation", valid_head_markings)
- if(!isnull(new_marking))
- M.change_markings(new_marking, "head")
-
- var/new_marking_colour = tgui_input_color(user, "Please select head marking color.", "Character Generation", M.m_colours["head"])
- if(!isnull(new_marking_colour))
- M.change_marking_color(new_marking_colour, "head")
-
- //Body markings.
- if(M.dna.species.bodyflags & HAS_BODY_MARKINGS)
- var/list/valid_body_markings = M.generate_valid_markings("body")
- var/new_marking = tgui_input_list(user, "Please select body marking style", "Character Generation", valid_body_markings)
- if(!isnull(new_marking))
- M.change_markings(new_marking, "body")
-
- var/new_marking_colour = tgui_input_color(user, "Please select body marking color.", "Character Generation", M.m_colours["body"])
- if(!isnull(new_marking_colour))
- M.change_marking_color(new_marking_colour, "body")
- //Tail markings.
- if(M.dna.species.bodyflags & HAS_TAIL_MARKINGS)
- var/list/valid_tail_markings = M.generate_valid_markings("tail")
- var/new_marking = tgui_input_list("Please select tail marking style", "Character Generation", valid_tail_markings)
- if(!isnull(new_marking))
- M.change_markings(new_marking, "tail")
-
- var/new_marking_colour = tgui_input_color(user, "Please select tail marking color.", "Character Generation", M.m_colours["tail"])
- if(!isnull(new_marking_colour))
- M.change_marking_color(new_marking_colour, "tail")
-
- //Skin tone.
- if(M.dna.species.bodyflags & HAS_SKIN_TONE)
- var/new_tone = input("Please select skin tone level: 1-220 (1=albino, 35=caucasian, 150=black, 220='very' black)", "Character Generation", M.s_tone) as null|text
- if(!new_tone)
- new_tone = 35
- else
- new_tone = 35 - max(min(round(text2num(new_tone)), 220), 1)
- M.change_skin_tone(new_tone)
-
- if(M.dna.species.bodyflags & HAS_ICON_SKIN_TONE)
- var/prompt = "Please select skin tone: 1-[length(M.dna.species.icon_skin_tones)] ("
- for(var/i = 1 to length(M.dna.species.icon_skin_tones))
- prompt += "[i] = [M.dna.species.icon_skin_tones[i]]"
- if(i != length(M.dna.species.icon_skin_tones))
- prompt += ", "
- prompt += ")"
-
- var/new_tone = input(prompt, "Character Generation", M.s_tone) as null|text
- if(!new_tone)
- new_tone = 0
- else
- new_tone = max(min(round(text2num(new_tone)), length(M.dna.species.icon_skin_tones)), 1)
- M.change_skin_tone(new_tone)
-
- //Skin colour.
- if(M.dna.species.bodyflags & HAS_SKIN_COLOR)
- var/new_body_colour = tgui_input_color(user, "Please select body color.", "Character Generation", M.skin_colour)
- if(!isnull(new_body_colour))
- M.change_skin_color(new_body_colour)
-
- M.update_dna()
-
- M.visible_message("[M] morphs and changes [M.p_their()] appearance!", "You change your appearance!", "Oh, god! What the hell was that? It sounded like flesh getting squished and bone ground into a different shape!")
-
-/datum/mutation/grant_spell/remotetalk
- name = "Telepathy"
- activation_messages = list("You feel you can project your thoughts.")
- deactivation_messages = list("You no longer feel you can project your thoughts.")
- instability = GENE_INSTABILITY_MINOR
-
- spelltype =/datum/spell/remotetalk
-
-/datum/mutation/grant_spell/remotetalk/New()
- ..()
- block = GLOB.remotetalkblock
-
-/datum/mutation/grant_spell/remotetalk/activate(mob/living/M)
- ..()
- M.AddSpell(new /datum/spell/mindscan(null))
-
-/datum/mutation/grant_spell/remotetalk/deactivate(mob/user)
- ..()
- for(var/datum/spell/S in user.mob_spell_list)
- if(istype(S, /datum/spell/mindscan))
- user.RemoveSpell(S)
-
-/datum/spell/remotetalk
- name = "Project Mind"
- desc = "Make people understand your thoughts!"
- base_cooldown = 0
-
- clothes_req = FALSE
- stat_allowed = CONSCIOUS
- invocation_type = "none"
- antimagic_flags = MAGIC_RESISTANCE_MIND
-
- action_icon_state = "genetic_project"
-
-/datum/spell/remotetalk/create_new_targeting()
- return new /datum/spell_targeting/telepathic
-
-/datum/spell/remotetalk/cast(list/targets, mob/user = usr)
- if(!ishuman(user))
- return
- if(user.mind?.miming) // Dont let mimes telepathically talk
- to_chat(user,"You can't communicate without breaking your vow of silence.")
- return
- var/say = tgui_input_text(user, "What do you wish to say?", "Project Mind")
- if(!say || usr.stat)
- return
- say = pencode_to_html(say, usr, format = FALSE, fields = FALSE)
-
- for(var/mob/living/target in targets)
- log_say("(TPATH to [key_name(target)]) [say]", user)
- user.create_log(SAY_LOG, "Telepathically said '[say]' using [src]", target)
- if(target.dna?.GetSEState(GLOB.remotetalkblock))
- target.show_message("You hear [user.real_name]'s voice: [say]")
- else
- target.show_message("You hear a voice that seems to echo around the room: [say]")
- user.show_message("You project your mind into [(target in user.get_visible_mobs()) ? target.name : "the unknown entity"]: [say]")
- for(var/mob/dead/observer/G in GLOB.player_list)
- G.show_message("Telepathic message from [user] ([ghost_follow_link(user, ghost=G)]) to [target] ([ghost_follow_link(target, ghost=G)]): [say]")
-
-/datum/spell/mindscan
- name = "Scan Mind"
- desc = "Offer people a chance to share their thoughts!"
- base_cooldown = 0
- clothes_req = FALSE
- stat_allowed = CONSCIOUS
- antimagic_flags = MAGIC_RESISTANCE_MIND
- invocation_type = "none"
- action_icon_state = "genetic_mindscan"
- var/list/expanded_minds = list()
-
-/datum/spell/mindscan/create_new_targeting()
- return new /datum/spell_targeting/telepathic
-
-/datum/spell/mindscan/cast(list/targets, mob/user = usr)
- if(!ishuman(user))
- return
- for(var/mob/living/target in targets)
- var/message = "You feel your mind expand briefly... (Click to send a message.)"
- if(target.dna?.GetSEState(GLOB.remotetalkblock))
- message = "You feel [user.real_name] request a response from you... (Click here to project mind.)"
- user.show_message("You offer your mind to [(target in user.get_visible_mobs()) ? target.name : "the unknown entity"].")
- target.show_message("[message]")
- expanded_minds += target
- addtimer(CALLBACK(src, PROC_REF(removeAvailability), target), 10 SECONDS)
-
-/datum/spell/mindscan/proc/removeAvailability(mob/living/target)
- if(target in expanded_minds)
- expanded_minds -= target
- if(!(target in expanded_minds))
- target.show_message("You feel the sensation fade...")
-
-/datum/spell/mindscan/Topic(href, href_list)
- var/mob/living/message_source
- message_source = locateUID(href_list["from"])
- if(!message_source)
- return
- if(!message_source || !(message_source in expanded_minds))
- return
-
- expanded_minds -= message_source
-
- var/mob/living/message_target = locateUID(href_list["to"])
- if(!message_target)
- return
-
- var/say = tgui_input_text(message_source, "What do you wish to say?", "Expanded Mind")
- if(!say)
- return
- say = pencode_to_html(say, message_source, format = FALSE, fields = FALSE)
-
- message_source.create_log(SAY_LOG, "Telepathically responded '[say]' using [src]", message_target)
- log_say("(TPATH to [key_name(message_target)]) [say]", message_source)
-
- if(message_source.dna?.GetSEState(GLOB.remotetalkblock))
- message_source.show_message("You project your mind into [message_target]: [say]")
- else
- message_source.show_message("You fill the space in your thoughts: [say]")
-
- message_target.show_message("You hear [message_source]'s voice: [say]")
-
- for(var/mob/dead/observer/G in GLOB.player_list)
- G.show_message("Telepathic response from [message_source] ([ghost_follow_link(message_source, ghost=G)]) to [message_target] ([ghost_follow_link(message_target, ghost=G)]): [say]")
-
-/datum/spell/mindscan/Destroy()
- expanded_minds.Cut()
- return ..()
-
-/datum/mutation/grant_spell/remoteview
- name = "Remote Viewing"
- activation_messages = list("Your mind can see things from afar.")
- deactivation_messages = list("Your mind can no longer can see things from afar.")
- instability = GENE_INSTABILITY_MINOR
-
- spelltype =/datum/spell/remoteview
-
-/datum/mutation/grant_spell/remoteview/New()
- ..()
- block = GLOB.remoteviewblock
-
-/datum/mutation/grant_spell/remoteview/deactivate(mob/user)
- . = ..()
- if(ishuman(user))
- var/mob/living/carbon/human/H = user
- H.remoteview_target = null
- H.reset_perspective()
-
-/datum/spell/remoteview
- name = "Remote View"
- desc = "Spy on people from any range!"
- base_cooldown = 10 SECONDS
-
- clothes_req = FALSE
- stat_allowed = CONSCIOUS
- invocation_type = "none"
- antimagic_flags = MAGIC_RESISTANCE_MIND
-
- action_icon_state = "genetic_view"
-
-/datum/spell/remoteview/create_new_targeting()
- return new /datum/spell_targeting/remoteview
-
-/datum/spell/remoteview/cast(list/targets, mob/user = usr)
- var/mob/living/carbon/human/H
- if(ishuman(user))
- H = user
- else
- return
-
- var/mob/target
-
- if(istype(H.l_hand, /obj/item/tk_grab) || istype(H.r_hand, /obj/item/tk_grab))
- to_chat(H, "Your mind is too busy with that telekinetic grab.")
- H.remoteview_target = null
- H.reset_perspective()
- return
-
- if(H.client.eye != user.client.mob)
- H.remoteview_target = null
- H.reset_perspective()
- return
-
- for(var/mob/living/L in targets)
- target = L
-
- if(target)
- H.remoteview_target = target
- H.reset_perspective(target)
- else
- H.remoteview_target = null
- H.reset_perspective()
-
-/datum/mutation/meson_vision
- name = "Meson Vision"
- activation_messages = list("More information seems to reach your eyes...")
- deactivation_messages = list("The amount of information reaching your eyes fades...")
- instability = GENE_INSTABILITY_MINOR
- traits_to_add = list(TRAIT_MESON_VISION)
-
-/datum/mutation/meson_vision/New()
- ..()
- block = GLOB.mesonblock
-
-/datum/mutation/meson_vision/activate(mob/living/M)
- ..()
- M.update_sight()
-
-/datum/mutation/meson_vision/deactivate(mob/living/M)
- ..()
- M.update_sight()
-
-/datum/mutation/night_vision
- name = "Night Vision"
- activation_messages = list("Were the lights always that bright?")
- deactivation_messages = list("The ambient light level returns to normal...")
- instability = GENE_INSTABILITY_MODERATE
- traits_to_add = list(TRAIT_NIGHT_VISION)
-
-/datum/mutation/night_vision/New()
- ..()
- block = GLOB.nightvisionblock
-
-/datum/mutation/night_vision/activate(mob/living/M)
- ..()
- M.update_sight()
- if(ishuman(M))
- var/mob/living/carbon/human/H = M
- H.update_misc_effects()
-
-/datum/mutation/night_vision/deactivate(mob/living/M)
- ..()
- M.update_sight()
- if(ishuman(M))
- var/mob/living/carbon/human/H = M
- H.update_misc_effects()
-
-/datum/mutation/flash_protection
- name = "Flash Protection"
- activation_messages = list("You stop noticing the glare from lights...")
- deactivation_messages = list("Lights begin glaring again...")
- instability = GENE_INSTABILITY_MODERATE
- traits_to_add = list(TRAIT_FLASH_PROTECTION)
-
-/datum/mutation/flash_protection/New()
- ..()
- block = GLOB.noflashblock
-
-#undef EAT_MOB_DELAY
diff --git a/code/game/gamemodes/changeling/changeling.dm b/code/game/gamemodes/changeling/changeling.dm
deleted file mode 100644
index ee868afd1be82..0000000000000
--- a/code/game/gamemodes/changeling/changeling.dm
+++ /dev/null
@@ -1,109 +0,0 @@
-
-// This list is basically a copy of GLOB.greek_letters, but it also removes letters when a changeling spawns in with that ID
-GLOBAL_LIST_INIT(possible_changeling_IDs, list("Alpha","Beta","Gamma","Delta","Epsilon","Zeta","Eta","Theta","Iota","Kappa","Lambda","Mu","Nu","Xi","Omicron","Pi","Rho","Sigma","Tau","Upsilon","Phi","Chi","Psi","Omega"))
-
-/datum/game_mode/changeling
- name = "changeling"
- config_tag = "changeling"
- restricted_jobs = list("AI", "Cyborg")
- protected_jobs = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Blueshield", "Nanotrasen Representative", "Magistrate", "Internal Affairs Agent", "Nanotrasen Career Trainer", "Nanotrasen Navy Officer", "Special Operations Officer", "Syndicate Officer", "Trans-Solar Federation General", "Nanotrasen Career Trainer")
- species_to_mindflayer = list("Machine")
- required_players = 15
- required_enemies = 1
- recommended_enemies = 4
- /// The total number of changelings allowed to be picked.
- var/changeling_amount = 4
-
-/datum/game_mode/changeling/Destroy(force, ...)
- pre_changelings.Cut()
- pre_mindflayers.Cut()
- return ..()
-
-/datum/game_mode/changeling/announce()
- to_chat(world, "The current game mode is - Changeling!")
- to_chat(world, "There are alien changelings on the station. Do not let the changelings succeed!")
-
-/datum/game_mode/changeling/pre_setup()
- if(GLOB.configuration.gamemode.prevent_mindshield_antags)
- restricted_jobs += protected_jobs
-
- var/list/datum/mind/possible_changelings = get_players_for_role(ROLE_CHANGELING)
-
- changeling_amount = 1 + round(num_players() / 10)
-
- for(var/i in 1 to changeling_amount)
- if(!length(possible_changelings))
- break
- var/datum/mind/changeling = pick_n_take(possible_changelings)
- changeling.restricted_roles = restricted_jobs
- if(changeling.current?.client?.prefs.active_character.species in species_to_mindflayer)
- pre_mindflayers += changeling
- changeling.special_role = SPECIAL_ROLE_MIND_FLAYER
- continue
- pre_changelings += changeling
- changeling.special_role = SPECIAL_ROLE_CHANGELING
-
- if(!(length(pre_changelings) + length(pre_mindflayers)))
- return FALSE
-
- return TRUE
-
-/datum/game_mode/changeling/post_setup()
- for(var/datum/mind/changeling as anything in pre_changelings)
- changeling.add_antag_datum(/datum/antagonist/changeling)
- pre_changelings -= changeling
- ..()
-
-/datum/game_mode/proc/auto_declare_completion_changeling()
- if(length(changelings))
- var/list/text = list("The changelings were:")
- for(var/datum/mind/changeling in changelings)
- var/changelingwin = TRUE
-
- text += " [changeling.get_display_key()] was [changeling.name] ("
- if(changeling.current)
- if(changeling.current.stat == DEAD)
- text += "died"
- else
- text += "survived"
- if(changeling.current.real_name != changeling.name)
- text += " as [changeling.current.real_name]"
- else
- text += "body destroyed"
- changelingwin = FALSE
- text += ")"
-
- //Removed sanity if(changeling) because we -want- a runtime to inform us that the changelings list is incorrect and needs to be fixed.
- var/datum/antagonist/changeling/cling = changeling.has_antag_datum(/datum/antagonist/changeling)
- text += " Changeling ID: [cling.changelingID]."
- text += " Genomes Extracted: [cling.absorbed_count]"
-
- var/list/all_objectives = changeling.get_all_objectives(include_team = FALSE)
-
- if(length(all_objectives))
- var/count = 1
- for(var/datum/objective/objective in all_objectives)
- if(objective.check_completion())
- text += " Objective #[count]: [objective.explanation_text] Success!"
- if(istype(objective, /datum/objective/steal))
- var/datum/objective/steal/S = objective
- SSblackbox.record_feedback("nested tally", "changeling_steal_objective", 1, list("Steal [S.steal_target]", "SUCCESS"))
- else
- SSblackbox.record_feedback("nested tally", "changeling_objective", 1, list("[objective.type]", "SUCCESS"))
- else
- text += " Objective #[count]: [objective.explanation_text] Fail."
- if(istype(objective, /datum/objective/steal))
- var/datum/objective/steal/S = objective
- SSblackbox.record_feedback("nested tally", "changeling_steal_objective", 1, list("Steal [S.steal_target]", "FAIL"))
- else
- SSblackbox.record_feedback("nested tally", "changeling_objective", 1, list("[objective.type]", "FAIL"))
- changelingwin = 0
- count++
-
- if(changelingwin)
- text += " The changeling was successful!"
- SSblackbox.record_feedback("tally", "changeling_success", 1, "SUCCESS")
- else
- text += " The changeling has failed."
- SSblackbox.record_feedback("tally", "changeling_success", 1, "FAIL")
- return text.Join("")
diff --git a/code/game/gamemodes/cult/blood_magic.dm b/code/game/gamemodes/cult/blood_magic.dm
deleted file mode 100644
index 12b80aa8da5d5..0000000000000
--- a/code/game/gamemodes/cult/blood_magic.dm
+++ /dev/null
@@ -1,999 +0,0 @@
-/// Blood magic handles the creation of blood spells (formerly talismans)
-/datum/action/innate/cult/blood_magic
- name = "Prepare Blood Magic"
- button_overlay_icon_state = "carve"
- desc = "Prepare blood magic by carving runes into your flesh. This is easier with an empowering rune."
- default_button_position = DEFAULT_BLOODSPELLS
- var/list/spells = list()
- var/channeling = FALSE
-
-/datum/action/innate/cult/blood_magic/Remove()
- for(var/X in spells)
- qdel(X)
- ..()
-
-/datum/action/innate/cult/blood_magic/Activate()
- var/rune = FALSE
- var/limit = RUNELESS_MAX_BLOODCHARGE
- for(var/obj/effect/rune/empower/R in range(1, owner))
- rune = TRUE
- limit = MAX_BLOODCHARGE
- break
- if(length(spells) >= limit)
- if(rune)
- to_chat(owner, "You cannot store more than [MAX_BLOODCHARGE] spell\s. Pick a spell to remove.")
- remove_spell("You cannot store more than [MAX_BLOODCHARGE] spell\s, pick a spell to remove.")
- else
- to_chat(owner, "You cannot store more than [RUNELESS_MAX_BLOODCHARGE] spell\s without an empowering rune! Pick a spell to remove.")
- remove_spell("You cannot store more than [RUNELESS_MAX_BLOODCHARGE] spell\s without an empowering rune, pick a spell to remove.")
- return
- var/entered_spell_name
- var/datum/action/innate/cult/blood_spell/BS
- var/list/possible_spells = list()
-
- for(var/I in subtypesof(/datum/action/innate/cult/blood_spell))
- var/datum/action/innate/cult/blood_spell/J = I
- var/cult_name = initial(J.name)
- possible_spells[cult_name] = J
- if(length(spells))
- possible_spells += "(REMOVE SPELL)"
- entered_spell_name = tgui_input_list(owner, "Pick a blood spell to prepare...", "Spell Choices", possible_spells)
- if(entered_spell_name == "(REMOVE SPELL)")
- remove_spell()
- return
- BS = possible_spells[entered_spell_name]
- if(QDELETED(src) || owner.incapacitated() || !BS || (rune && !(locate(/obj/effect/rune/empower) in range(1, owner))) || (length(spells) >= limit))
- return
-
- if(!channeling)
- channeling = TRUE
- to_chat(owner, "You begin to carve unnatural symbols into your flesh!")
- else
- to_chat(owner, "You are already invoking blood magic!")
- return
-
- if(do_after(owner, 100 - rune * 60, target = owner))
- if(ishuman(owner))
- var/mob/living/carbon/human/H = owner
- if(H.dna && (NO_BLOOD in H.dna.species.species_traits))
- H.cult_self_harm(3 - rune * 2)
- else
- H.bleed(20 - rune * 12)
- var/datum/action/innate/cult/blood_spell/new_spell = new BS(owner)
- spells += new_spell
- new_spell.Grant(owner, src)
- to_chat(owner, "Your wounds glow with power, you have prepared a [new_spell.name] invocation!")
- SSblackbox.record_feedback("tally", "cult_spells_prepared", 1, "[new_spell.name]")
- channeling = FALSE
-
-/datum/action/innate/cult/blood_magic/proc/remove_spell()
- var/nullify_spell = tgui_input_list(owner, "Pick a spell to remove", "Current Spells", spells)
- if(nullify_spell)
- qdel(nullify_spell)
-
-/// The next generation of talismans, handles storage/creation of blood magic
-/datum/action/innate/cult/blood_spell
- name = "Blood Magic"
- button_overlay_icon_state = "telerune"
- desc = "Fear the Old Blood."
- default_button_position = SCRN_OBJ_CULT_LIST
- var/charges = 1
- var/magic_path = null
- var/obj/item/melee/blood_magic/hand_magic
- var/datum/action/innate/cult/blood_magic/all_magic
- var/base_desc //To allow for updating tooltips
- var/invocation = "Hoi there something's wrong!"
- var/health_cost = 0
- /// Have we already been positioned into our starting location?
- var/positioned = FALSE
-
-
-/datum/action/innate/cult/blood_spell/proc/get_panel_text()
- if(initial(charges) == 1)
- return
- var/available_charges = hand_magic ? "[hand_magic.uses]" : "[charges]"
- return "[available_charges]/[initial(charges)]"
-
-/datum/action/innate/cult/blood_spell/UpdateButton(atom/movable/screen/movable/action_button/button, status_only, force)
- . = ..()
- var/text = get_panel_text()
- if(!text || !button)
- return
- var/image/count_down_holder = image('icons/effects/effects.dmi', icon_state = "nothing")
- count_down_holder.maptext = "[text] "
- button.add_overlay(count_down_holder)
-
-/datum/action/innate/cult/blood_spell/Grant(mob/living/owner, datum/action/innate/cult/blood_magic/BM)
- if(health_cost)
- desc += " Deals [health_cost] damage to your arm per use."
- base_desc = desc
- desc += " Has [charges] use\s remaining."
- all_magic = BM
- // todo blood magic guh
- // button.ordered = FALSE
- ..()
-
-/datum/action/innate/cult/blood_spell/Remove()
- if(all_magic)
- all_magic.spells -= src
- if(hand_magic)
- qdel(hand_magic)
- hand_magic = null
- ..()
-
-/datum/action/innate/cult/blood_spell/IsAvailable()
- if(!IS_CULTIST(owner) || owner.incapacitated() || !charges)
- return FALSE
- return ..()
-
-/datum/action/innate/cult/blood_spell/Activate()
- if(owner.holy_check())
- return
- if(magic_path) // If this spell flows from the hand
- if(!hand_magic) // If you don't already have the spell active
- hand_magic = new magic_path(owner, src)
- if(!owner.put_in_hands(hand_magic))
- qdel(hand_magic)
- hand_magic = null
- to_chat(owner, "You have no empty hand for invoking blood magic!")
- return
- to_chat(owner, "Your wounds glow as you invoke the [name].")
-
- else // If the spell is active, and you clicked on the button for it
- qdel(hand_magic)
- hand_magic = null
-
-//the spell list
-
-/datum/action/innate/cult/blood_spell/stun
- name = "Stun"
- desc = "Will knock down and mute a victim on contact. Strike them with a cult blade to complete the invocation, stunning them and extending the mute."
- button_overlay_icon_state = "stun"
- magic_path = /obj/item/melee/blood_magic/stun
- health_cost = 10
-
-/datum/action/innate/cult/blood_spell/teleport
- name = "Teleport"
- desc = "Empowers your hand to teleport yourself or another cultist to a teleport rune on contact."
- button_overlay_icon_state = "teleport"
- magic_path = /obj/item/melee/blood_magic/teleport
- health_cost = 7
-
-/datum/action/innate/cult/blood_spell/emp
- name = "Electromagnetic Pulse"
- desc = "Releases an Electromagnetic Pulse, affecting nearby non-cultists. The pulse will still affect you."
- button_overlay_icon_state = "emp"
- health_cost = 10
- invocation = "Ta'gh fara'qha fel d'amar det!"
-
-/datum/action/innate/cult/blood_spell/emp/Grant(mob/living/owner)
- if(ishuman(owner))
- var/mob/living/carbon/human/H = owner
- var/oof = FALSE
- for(var/obj/item/organ/external/E in H.bodyparts)
- if(E.is_robotic())
- oof = TRUE
- break
- if(!oof)
- for(var/obj/item/organ/internal/I in H.internal_organs)
- if(I.is_robotic())
- oof = TRUE
- break
- if(oof)
- to_chat(owner, "You get the feeling this is a bad idea.")
- ..()
-
-/datum/action/innate/cult/blood_spell/emp/Activate()
- if(owner.holy_check())
- return
- owner.visible_message("[owner]'s body flashes a bright blue!", \
- "You speak the cursed words, channeling an electromagnetic pulse from your body.")
- owner.emp_act(EMP_LIGHT)
- INVOKE_ASYNC(GLOBAL_PROC, GLOBAL_PROC_REF(empulse), owner, 2, 5, TRUE, "cult")
- owner.whisper(invocation)
- charges--
- if(charges <= 0)
- qdel(src)
-
-/datum/action/innate/cult/blood_spell/shackles
- name = "Shadow Shackles"
- desc = "Empowers your hand to start handcuffing victim on contact, and mute them if successful."
- button_overlay_icon_state = "shackles"
- charges = 4
- magic_path = /obj/item/melee/blood_magic/shackles
-
-/datum/action/innate/cult/blood_spell/construction
- name = "Twisted Construction"
- desc = "Empowers your hand to corrupt certain metalic objects. Converts: Plasteel into runed metal 50 metal into a construct shell Cyborg shells into construct shells Airlocks into brittle runed airlocks after a delay (harm intent)"
- button_overlay_icon_state = "transmute"
- magic_path = "/obj/item/melee/blood_magic/construction"
- health_cost = 12
-
-/datum/action/innate/cult/blood_spell/dagger
- name = "Summon Dagger"
- desc = "Summon a ritual dagger, necessary to scribe runes."
- button_overlay_icon_state = "cult_dagger"
-
-/datum/action/innate/cult/blood_spell/dagger/New()
- button_overlay_icon_state = GET_CULT_DATA(dagger_icon, "cult_dagger")
- ..()
-
-/datum/action/innate/cult/blood_spell/dagger/Activate()
- var/turf/T = get_turf(owner)
- owner.visible_message("[owner]'s hand glows red for a moment.", \
- "Red light begins to shimmer and take form within your hand!")
- var/obj/item/melee/cultblade/dagger/O = new(T)
- if(owner.put_in_hands(O))
- to_chat(owner, "A [O.name] appears in your hand!")
- else
- owner.visible_message("A [O.name] appears at [owner]'s feet!", \
- "A [O.name] materializes at your feet.")
- playsound(owner, 'sound/magic/cult_spell.ogg', 25, TRUE, SOUND_RANGE_SET(4))
- charges--
- desc = base_desc
- desc += " Has [charges] use\s remaining."
- if(charges <= 0)
- qdel(src)
-
-/datum/action/innate/cult/blood_spell/equipment
- name = "Summon Equipment"
- desc = "Empowers your hand to summon combat gear onto a cultist you touch, including cult armor into open slots, a cult bola, and a cult sword."
- button_overlay_icon_state = "equip"
- magic_path = /obj/item/melee/blood_magic/armor
-
-/datum/action/innate/cult/blood_spell/horror
- name = "Hallucinations"
- desc = "Gives hallucinations to a target at range. A silent and invisible spell."
- button_overlay_icon_state = "horror"
- var/datum/spell/horror/PH
- charges = 4
-
-/datum/action/innate/cult/blood_spell/horror/New()
- PH = new()
- PH.attached_action = src
- ..()
-
-/datum/action/innate/cult/blood_spell/horror/Destroy()
- var/datum/spell/horror/destroy = PH
- . = ..()
- if(!QDELETED(destroy))
- QDEL_NULL(destroy)
-
-/datum/action/innate/cult/blood_spell/horror/Activate()
- PH.toggle(owner) //the important bit
- return TRUE
-
-/datum/spell/horror
- active = FALSE
- ranged_mousepointer = 'icons/effects/cult_target.dmi'
- var/datum/action/innate/cult/blood_spell/attached_action
-
-/datum/spell/horror/Destroy()
- var/datum/action/innate/cult/blood_spell/AA = attached_action
- . = ..()
- if(!QDELETED(AA))
- QDEL_NULL(AA)
-
-/datum/spell/horror/proc/toggle(mob/user)
- if(active)
- remove_ranged_ability(user, "You dispel the magic...")
- else
- add_ranged_ability(user, "You prepare to horrify a target...")
-
-/datum/spell/horror/InterceptClickOn(mob/living/user, params, atom/target)
- if(..())
- return
- if(ranged_ability_user.incapacitated() || !IS_CULTIST(user))
- user.ranged_ability.remove_ranged_ability(user)
- return
- if(user.holy_check())
- return
- var/turf/T = get_turf(ranged_ability_user)
- if(!isturf(T))
- return FALSE
- if(target in view(7, ranged_ability_user))
- if(!ishuman(target) || IS_CULTIST(target))
- return
- var/mob/living/carbon/human/H = target
- H.Hallucinate(120 SECONDS)
- attached_action.charges--
- attached_action.UpdateButtons()
- attached_action.desc = attached_action.base_desc
- attached_action.desc += " Has [attached_action.charges] use\s remaining."
- attached_action.UpdateButtons()
- user.ranged_ability.remove_ranged_ability(user, "[H] has been cursed with living nightmares!")
- if(attached_action.charges <= 0)
- to_chat(ranged_ability_user, "You have exhausted the spell's power!")
- qdel(src)
-
-/datum/action/innate/cult/blood_spell/veiling
- name = "Conceal Presence"
- desc = "Alternates between hiding and revealing nearby cult structures, cult airlocks and runes."
- invocation = "Kla'atu barada nikt'o!"
- button_overlay_icon_state = "veiling"
- charges = 10
- var/revealing = FALSE //if it reveals or not
-
-/datum/action/innate/cult/blood_spell/veiling/Activate()
- if(owner.holy_check())
- return
- if(!revealing) // Hiding stuff
- owner.visible_message("Thin grey dust falls from [owner]'s hand!", \
- "You invoke the veiling spell, hiding nearby runes and cult structures.")
- charges--
- 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.
- owner.whisper(invocation)
- for(var/obj/O in range(4, owner))
- O.cult_conceal()
- revealing = TRUE // Switch on use
- name = "Reveal Runes"
- button_overlay_icon_state = "revealing"
-
- else // Unhiding stuff
- owner.visible_message("A flash of light shines from [owner]'s hand!", \
- "You invoke the counterspell, revealing nearby runes and cult structures.")
- charges--
- owner.whisper(invocation)
- 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.
- for(var/obj/O in range(5, owner)) // Slightly higher in case we arent in the exact same spot
- O.cult_reveal()
- revealing = FALSE // Switch on use
- name = "Conceal Runes"
- button_overlay_icon_state = "veiling"
- if(charges <= 0)
- qdel(src)
- desc = "[revealing ? "Reveals" : "Conceals"] nearby cult structures, airlocks, and runes."
- desc += " Has [charges] use\s remaining."
- UpdateButtons()
-
-/datum/action/innate/cult/blood_spell/manipulation
- name = "Blood Rites"
- desc = "Empowers your hand to manipulate blood. Use on blood or a noncultist to absorb blood to be used later, use on yourself or another cultist to heal them using absorbed blood. \
- \nUse the spell in-hand to cast advanced rites, such as summoning a magical blood spear, firing blood projectiles out of your hands, and more!"
- invocation = "Fel'th Dol Ab'orod!"
- button_overlay_icon_state = "manip"
- charges = 5
- magic_path = /obj/item/melee/blood_magic/manipulator
-
-/datum/action/innate/cult/blood_spell/manipulation/get_panel_text()
- return hand_magic ? "[hand_magic.uses]" : "[charges]"
-
-// The "magic hand" items
-/obj/item/melee/blood_magic
- name = "magical aura"
- desc = "A sinister looking aura that distorts the flow of reality around it."
- icon = 'icons/obj/weapons/magical_weapons.dmi'
- lefthand_file = 'icons/mob/inhands/items_lefthand.dmi'
- righthand_file = 'icons/mob/inhands/items_righthand.dmi'
- icon_state = "disintegrate"
- item_state = "disintegrate"
- flags = ABSTRACT | DROPDEL
-
- w_class = WEIGHT_CLASS_HUGE
- throwforce = 0
- throw_range = 0
- throw_speed = 0
- /// Does it have a source, AKA bloody empowerment.
- var/has_source = TRUE
- var/invocation
- var/uses = 1
- var/health_cost = 0 //The amount of health taken from the user when invoking the spell
- var/datum/action/innate/cult/blood_spell/source
- var/antimagic_flags = MAGIC_RESISTANCE_HOLY
-
-/obj/item/melee/blood_magic/Initialize(mapload, spell)
- . = ..()
- if(spell && has_source)
- source = spell
- uses = source.charges
- health_cost = source.health_cost
-
-/obj/item/melee/blood_magic/Destroy()
- if(has_source && !QDELETED(source))
- if(uses <= 0)
- source.hand_magic = null
- qdel(source)
- source = null
- else
- source.hand_magic = null
- source.charges = uses
- source.desc = source.base_desc
- source.desc += " Has [uses] use\s remaining."
- source.UpdateButtons()
- return ..()
-
-/obj/item/melee/blood_magic/customised_abstract_text(mob/living/carbon/owner)
- return "[owner.p_their(TRUE)] [owner.l_hand == src ? "left hand" : "right hand"] is burning in blood-red fire."
-
-/obj/item/melee/blood_magic/attack_self__legacy__attackchain(mob/living/user)
- attackby__legacy__attackchain(user, user, TRUE)
-
-/obj/item/melee/blood_magic/attack__legacy__attackchain(mob/living/M, mob/living/carbon/user)
- if(!iscarbon(user) || !IS_CULTIST(user))
- uses = 0
- qdel(src)
- return
- if(M.can_block_magic(MAGIC_RESISTANCE_HOLY))
- to_chat(user, "[M] absorbs your spell!")
- uses = 0
- qdel(src)
- return
- add_attack_logs(user, M, "used a cult spell ([src]) on")
- M.lastattacker = user.real_name
-
-/obj/item/melee/blood_magic/afterattack__legacy__attackchain(atom/target, mob/living/carbon/user, proximity)
- . = ..()
- if(invocation)
- user.whisper(invocation)
- if(health_cost && ishuman(user))
- user.cult_self_harm(health_cost)
- if(uses <= 0)
- qdel(src)
- else if(source)
- source.desc = source.base_desc
- source.desc += " Has [uses] use\s remaining."
- source.UpdateButtons()
-
-//The spell effects
-
-//stun
-/obj/item/melee/blood_magic/stun
- name = "Stunning Aura"
- desc = "Will knock down and mute a victim on contact. Strike them with a cult blade to complete the invocation, stunning them and extending the mute."
- color = RUNE_COLOR_RED
- invocation = "Fuu ma'jin!"
-
-/obj/item/melee/blood_magic/stun/afterattack__legacy__attackchain(atom/target, mob/living/carbon/user, proximity)
- if(!isliving(target) || !proximity)
- return
- var/mob/living/L = target
- if(IS_CULTIST(target))
- return
- if(user.holy_check())
- return
- user.visible_message("[user] holds up [user.p_their()] hand, which explodes in a flash of red light!", \
- "You attempt to stun [L] with the spell!")
-
- user.mob_light(LIGHT_COLOR_BLOOD_MAGIC, 3, _duration = 2)
-
- var/obj/item/nullrod/N = locate() in target
- if(N)
- target.visible_message("[target]'s holy weapon absorbs the red light!", \
- "Your holy weapon absorbs the blinding light!")
- else
- to_chat(user, "In a brilliant flash of red, [L] falls to the ground!")
-
- L.apply_status_effect(STATUS_EFFECT_CULT_STUN)
- L.Silence(6 SECONDS)
- if(issilicon(target))
- var/mob/living/silicon/S = L
- S.emp_act(EMP_HEAVY)
- else if(iscarbon(target))
- var/mob/living/carbon/C = L
- C.KnockDown(10 SECONDS)
- C.apply_damage(60, STAMINA)
- C.flash_eyes(1, TRUE)
- C.Stuttering(16 SECONDS)
- C.CultSlur(20 SECONDS)
- C.Jitter(16 SECONDS)
- to_chat(user, "Stun mark applied! Stab them with a dagger, sword or blood spear to stun them fully!")
- user.do_attack_animation(target)
- uses--
- ..()
-
-
-//Teleportation
-/obj/item/melee/blood_magic/teleport
- name = "Teleporting Aura"
- color = RUNE_COLOR_TELEPORT
- desc = "Will teleport a cultist to a teleport rune on contact."
- invocation = "Sas'so c'arta forbici!"
-
-/obj/item/melee/blood_magic/teleport/afterattack__legacy__attackchain(atom/target, mob/living/carbon/user, proximity)
- if(user.holy_check())
- return
- var/list/potential_runes = list()
- var/list/teleportnames = list()
- var/list/duplicaterunecount = list()
- var/atom/movable/teleportee
- if(!IS_CULTIST(target) || !proximity)
- to_chat(user, "You can only teleport adjacent cultists with this spell!")
- return
- if(user != target) // So that the teleport effect shows on the correct mob
- teleportee = target
- else
- teleportee = user
-
- for(var/R in GLOB.teleport_runes)
- var/obj/effect/rune/teleport/T = R
- var/resultkey = T.listkey
- if(resultkey in teleportnames)
- duplicaterunecount[resultkey]++
- resultkey = "[resultkey] ([duplicaterunecount[resultkey]])"
- else
- teleportnames.Add(resultkey)
- duplicaterunecount[resultkey] = 1
- potential_runes[resultkey] = T
-
- if(!length(potential_runes))
- to_chat(user, "There are no valid runes to teleport to!")
- log_game("Teleport spell failed - no other teleport runes")
- return
- if(!is_level_reachable(user.z))
- to_chat(user, "You are too far away from the station to teleport!")
- log_game("Teleport spell failed - user in away mission")
- return
-
- var/input_rune_key = tgui_input_list(user, "Choose a rune to teleport to", "Rune to Teleport to", potential_runes) //we know what key they picked
- var/obj/effect/rune/teleport/actual_selected_rune = potential_runes[input_rune_key] //what rune does that key correspond to?
- if(QDELETED(src) || !user || !user.is_holding(src) || user.incapacitated() || !actual_selected_rune)
- return
-
- if(HAS_TRAIT(user, TRAIT_FLOORED))
- to_chat(user, "You cannot cast this spell while knocked down!")
- return
-
- uses--
-
- var/turf/origin = get_turf(teleportee)
- var/turf/destination = get_turf(actual_selected_rune)
- if(SEND_SIGNAL(target, COMSIG_MOVABLE_TELEPORTING, destination) & COMPONENT_BLOCK_TELEPORT)
- return
- INVOKE_ASYNC(actual_selected_rune, TYPE_PROC_REF(/obj/effect/rune, teleport_effect), teleportee, origin, destination)
-
- if(is_mining_level(user.z) && !is_mining_level(destination.z)) //No effect if you stay on lavaland
- actual_selected_rune.handle_portal("lava")
- else if(!is_station_level(user.z) || isspacearea(get_area(user)))
- actual_selected_rune.handle_portal("space", origin)
-
- if(user == target)
- target.visible_message("Dust flows from [user]'s hand, and [user.p_they()] disappear[user.p_s()] in a flash of red light!", \
- "You speak the words and find yourself somewhere else!")
- else
- target.visible_message("Dust flows from [user]'s hand, and [target] disappears in a flash of red light!", \
- "You suddenly find yourself somewhere else!")
- destination.visible_message("There is a boom of outrushing air as something appears above the rune!", null, "You hear a boom.")
- teleportee.forceMove(destination)
- return ..()
-
-//Shackles
-/obj/item/melee/blood_magic/shackles
- name = "Shackling Aura"
- desc = "Will start handcuffing a victim on contact, and mute them for a short duration if successful."
- invocation = "In'totum Lig'abis!"
- color = "#000000" // black
-
-/obj/item/melee/blood_magic/shackles/afterattack__legacy__attackchain(atom/target, mob/living/carbon/user, proximity)
- if(user.holy_check())
- return
- if(iscarbon(target) && proximity)
- var/mob/living/carbon/C = target
- if(!(C.has_left_hand() || C.has_right_hand()))
- user.visible_message("This victim doesn't have enough arms to complete the restraint!")
- return
- CuffAttack(C, user)
- source.UpdateButtons()
- ..()
-
-/obj/item/melee/blood_magic/shackles/proc/CuffAttack(mob/living/carbon/C, mob/living/user)
- if(!C.handcuffed)
- playsound(loc, 'sound/weapons/cablecuff.ogg', 30, TRUE, SOUND_RANGE_SET(7))
- C.visible_message("[user] begins restraining [C] with dark magic!", \
- "[user] begins shaping dark magic shackles around your wrists!")
- if(do_mob(user, C, 30))
- if(!C.handcuffed)
- C.handcuffed = new /obj/item/restraints/handcuffs/cult(C)
- C.update_handcuffed()
- C.Silence(12 SECONDS)
- to_chat(user, "You shackle [C].")
- add_attack_logs(user, C, "shackled")
- uses--
- else
- to_chat(user, "[C] is already bound.")
- else
- to_chat(user, "You fail to shackle [C].")
- else
- to_chat(user, "[C] is already bound.")
-
-
-/// For the shackling spell
-/obj/item/restraints/handcuffs/cult
- name = "shadow shackles"
- desc = "Shackles that bind the wrists with sinister magic."
- icon_state = "cablecuff"
- breakouttime = 45 SECONDS
- origin_tech = "materials=4;magnets=5;abductor=2"
- flags = DROPDEL
-
-/obj/item/restraints/handcuffs/cult/finish_resist_restraints(mob/living/carbon/user, break_cuffs, silent)
- user.visible_message("[user]'s shackles shatter in a discharge of dark magic!", "Your [name] shatter in a discharge of dark magic!")
- break_cuffs = TRUE
- silent = TRUE
- . = ..()
-
-//Construction: Converts 50 metal to a construct shell, plasteel to runed metal, or an airlock to brittle runed airlock
-/obj/item/melee/blood_magic/construction
- name = "Twisting Aura"
- desc = "Corrupts certain metalic objects on contact."
- invocation = "Ethra p'ni dedol!"
- color = "#000000" // black
- var/channeling = FALSE
-
-/obj/item/melee/blood_magic/construction/examine(mob/user)
- . = ..()
- . += {"A sinister spell used to convert:\n
- Plasteel into runed metal\n
- [METAL_TO_CONSTRUCT_SHELL_CONVERSION] metal into a construct shell\n
- Airlocks into brittle runed airlocks after a delay (harm intent)"}
-
-/obj/item/melee/blood_magic/construction/afterattack__legacy__attackchain(atom/target, mob/user, proximity_flag, click_parameters)
- if(user.holy_check())
- return
- if(proximity_flag)
- if(channeling)
- to_chat(user, "You are already invoking twisted construction!")
- return
- var/turf/T = get_turf(target)
-
- //Metal to construct shell
- if(istype(target, /obj/item/stack/sheet/metal))
- var/obj/item/stack/sheet/candidate = target
- if(candidate.use(METAL_TO_CONSTRUCT_SHELL_CONVERSION))
- uses--
- to_chat(user, "A dark cloud emanates from your hand and swirls around the metal, twisting it into a construct shell!")
- new /obj/structure/constructshell(T)
- playsound(user, 'sound/magic/cult_spell.ogg', 25, TRUE, SOUND_RANGE_SET(4))
- else
- to_chat(user, "You need [METAL_TO_CONSTRUCT_SHELL_CONVERSION] metal to produce a construct shell!")
- return
-
- //Plasteel to runed metal
- else if(istype(target, /obj/item/stack/sheet/plasteel))
- var/obj/item/stack/sheet/plasteel/candidate = target
- var/quantity = candidate.amount
- if(candidate.use(quantity))
- uses--
- new /obj/item/stack/sheet/runed_metal(T, quantity)
- to_chat(user, "A dark cloud emanates from you hand and swirls around the plasteel, transforming it into runed metal!")
- playsound(user, 'sound/magic/cult_spell.ogg', 25, TRUE, SOUND_RANGE_SET(4))
-
- //Airlock to cult airlock
- else if(istype(target, /obj/machinery/door/airlock) && !istype(target, /obj/machinery/door/airlock/cult))
- channeling = TRUE
- playsound(T, 'sound/machines/airlockforced.ogg', 50, TRUE, SOUND_RANGE_SET(7))
- do_sparks(5, TRUE, target)
- if(do_after(user, 50, target = target))
- target.narsie_act(TRUE)
- uses--
- user.visible_message("Black ribbons suddenly emanate from [user]'s hand and cling to the airlock - twisting and corrupting it!")
- playsound(user, 'sound/magic/cult_spell.ogg', 25, TRUE, SOUND_RANGE_SET(7))
- channeling = FALSE
- else
- channeling = FALSE
- return
- else
- to_chat(user, "The spell will not work on [target]!")
- return
- ..()
-
-//Armor: Gives the target a basic cultist combat loadout
-/obj/item/melee/blood_magic/armor
- name = "Arming Aura"
- desc = "Will equipt cult combat gear onto a cultist on contact."
- color = "#33cc33" // green
-
-/obj/item/melee/blood_magic/armor/afterattack__legacy__attackchain(atom/target, mob/living/carbon/user, proximity)
- if(user.holy_check())
- return
- if(iscarbon(target) && proximity)
- uses--
- var/mob/living/carbon/C = target
- var/armour = C.equip_to_slot_or_del(new /obj/item/clothing/suit/hooded/cultrobes/alt(user), ITEM_SLOT_OUTER_SUIT)
- C.equip_to_slot_or_del(new /obj/item/clothing/under/color/black(user), ITEM_SLOT_JUMPSUIT)
- C.equip_to_slot_or_del(new /obj/item/storage/backpack/cultpack(user), ITEM_SLOT_BACK)
- C.equip_to_slot_or_del(new /obj/item/clothing/shoes/cult(user), ITEM_SLOT_SHOES)
-
- if(C == user)
- qdel(src) //Clears the hands
- C.put_in_hands(new /obj/item/melee/cultblade(user))
- C.put_in_hands(new /obj/item/restraints/legcuffs/bola/cult(user))
- C.visible_message("Otherworldly [armour ? "armour" : "equipment"] suddenly appears on [C]!")
- ..()
-//Used by blood rite, to recharge things like viel shifter or the cultest shielded robes
-/obj/item/melee/blood_magic/empower
- name = "Blood Recharge"
- desc = "Can be used on some cult items, to restore them to their previous state."
- invocation = "Ditans Gut'ura Inpulsa!"
- color = "#9c0651"
- has_source = FALSE //special, only availible for a blood cost.
-
-/obj/item/melee/blood_magic/empower/afterattack__legacy__attackchain(atom/target, mob/user, proximity_flag, click_parameters)
- if(user.holy_check())
- return
- if(proximity_flag)
- // Shielded suit
- if(istype(target, /obj/item/clothing/suit/hooded/cultrobes/cult_shield))
- var/datum/component/shielded/shield = target.GetComponent(/datum/component/shielded)
- if(shield.current_charges >= 3)
- to_chat(user, "[target] is already at full charge!")
- return
- uses--
- to_chat(user, "You empower [target] with blood, recharging its shields!")
- playsound(user, 'sound/magic/cult_spell.ogg', 25, TRUE, SOUND_RANGE_SET(7))
- shield.current_charges = 3
- user.update_appearance(UPDATE_ICON)
- return ..()
-
- // Veil Shifter
- if(istype(target, /obj/item/cult_shift))
- var/obj/item/cult_shift/S = target
- if(S.uses >= 4)
- to_chat(user, "[target] is already at full charge!")
- return
- uses--
- to_chat(user, "You empower [target] with blood, recharging its ability to shift!")
- playsound(user, 'sound/magic/cult_spell.ogg', 25, TRUE, SOUND_RANGE_SET(7))
- S.uses = 4
- S.icon_state = "shifter"
- return ..()
-
- to_chat(user, "The spell will not work on [target]!")
- return ..()
-
-//Blood Rite: Absorb blood to heal cult members or summon weapons
-/obj/item/melee/blood_magic/manipulator
- name = "Blood Rite Aura"
- desc = "Absorbs blood from anything you touch. Touching cultists and constructs can heal them. Use in-hand to cast an advanced rite."
- color = "#7D1717"
-
-/obj/item/melee/blood_magic/manipulator/examine(mob/user)
- . = ..()
- . += "Blood spear and blood barrage cost [BLOOD_SPEAR_COST] and [BLOOD_BARRAGE_COST] charges respectively."
- . += "Blood orb and blood empower cost [BLOOD_ORB_COST] and [BLOOD_RECHARGE_COST] charges respectively."
- . += "You have collected [uses] charge\s of blood."
-
-/obj/item/melee/blood_magic/manipulator/proc/restore_blood(mob/living/carbon/human/user, mob/living/carbon/human/H)
- if(uses == 0)
- return
- if(!H.dna || (NO_BLOOD in H.dna.species.species_traits) || !isnull(H.dna.species.exotic_blood))
- return
- if(H.blood_volume >= BLOOD_VOLUME_SAFE)
- return
- var/restore_blood = BLOOD_VOLUME_SAFE - H.blood_volume
- if(uses * 2 < restore_blood)
- H.blood_volume += uses * 2
- to_chat(user, "You use the last of your charges to restore what blood you could, and the spell dissipates!")
- uses = 0
- else
- H.blood_volume = BLOOD_VOLUME_SAFE
- uses -= round(restore_blood / 2)
- to_chat(user, "Your blood rites have restored [H == user ? "your" : "[H.p_their()]"] blood to safe levels!")
-
-/obj/item/melee/blood_magic/manipulator/proc/heal_human_damage(mob/living/carbon/human/user, mob/living/carbon/human/H)
- if(uses == 0)
- return
- var/overall_damage = H.getBruteLoss() + H.getFireLoss() + H.getToxLoss() + H.getOxyLoss()
- if(overall_damage == 0)
- to_chat(user, "[H] doesn't require healing!")
- return
-
- var/ratio = uses / overall_damage
- if(H == user)
- to_chat(user, "Your blood healing is far less efficient when used on yourself!")
- ratio *= 0.35 // Healing is half as effective if you can't perform a full heal
- uses -= round(overall_damage) // Healing is 65% more "expensive" even if you can still perform the full heal
- if(ratio > 1)
- ratio = 1
- uses -= round(overall_damage)
- H.visible_message("[H] is fully healed by [H == user ? "[H.p_their()]" : "[H]'s"] blood magic!",
- "You are fully healed by [H == user ? "your" : "[user]'s"] blood magic!")
- else
- H.visible_message("[H] is partially healed by [H == user ? "[H.p_their()]" : "[H]'s"] blood magic.",
- "You are partially healed by [H == user ? "your" : "[user]'s"] blood magic.")
- uses = 0
- ratio *= -1
- H.adjustOxyLoss((overall_damage * ratio) * (H.getOxyLoss() / overall_damage), FALSE, null, TRUE)
- H.adjustToxLoss((overall_damage * ratio) * (H.getToxLoss() / overall_damage), FALSE, null, TRUE)
- H.adjustFireLoss((overall_damage * ratio) * (H.getFireLoss() / overall_damage), FALSE, null, TRUE)
- H.adjustBruteLoss((overall_damage * ratio) * (H.getBruteLoss() / overall_damage), FALSE, null, TRUE)
- H.updatehealth()
- playsound(get_turf(H), 'sound/magic/staff_healing.ogg', 25, extrarange = SOUND_RANGE_SET(7))
- new /obj/effect/temp_visual/cult/sparks(get_turf(H))
- user.Beam(H, icon_state="sendbeam", time = 15)
-
-/obj/item/melee/blood_magic/manipulator/proc/heal_cultist(mob/living/carbon/human/user, mob/living/carbon/human/H)
- if(H.stat == DEAD)
- to_chat(user, "Only a revive rune can bring back the dead!")
- return
- var/charge_loss = uses
- restore_blood(user, H)
- heal_human_damage(user, H)
- charge_loss = charge_loss - uses
- if(!uses)
- to_chat(user, "You use the last of your charges to heal [H == user ? "yourself" : "[H]"], and the spell dissipates!")
- else
- to_chat(user, "You use [charge_loss] charge\s, and have [uses] remaining.")
-
-/obj/item/melee/blood_magic/manipulator/proc/heal_construct(mob/living/carbon/human/user, mob/living/simple_animal/M)
- if(uses == 0)
- return
- var/missing = M.maxHealth - M.health
- if(!missing)
- to_chat(user, "[M] doesn't require healing!")
- return
- if(uses > missing)
- M.adjustHealth(-missing)
- M.visible_message("[M] is fully healed by [user]'s blood magic!",
- "You are fully healed by [user]'s blood magic!")
- uses -= missing
- else
- M.adjustHealth(-uses)
- M.visible_message("[M] is partially healed by [user]'s blood magic!",
- "You are partially healed by [user]'s blood magic.")
- uses = 0
- playsound(get_turf(M), 'sound/magic/staff_healing.ogg', 25, extrarange = SOUND_RANGE_SET(7))
- user.Beam(M, icon_state = "sendbeam", time = 10)
-
-/obj/item/melee/blood_magic/manipulator/proc/steal_blood(mob/living/carbon/human/user, mob/living/carbon/human/H)
- if(H.stat == DEAD)
- to_chat(user, "[H.p_their(TRUE)] blood has stopped flowing, you'll have to find another way to extract it.")
- return
- if(H.AmountCultSlurring())
- to_chat(user, "[H.p_their(TRUE)] blood has been tainted by an even stronger form of blood magic, it's no use to us like this!")
- return
- if(!H.dna || (NO_BLOOD in H.dna.species.species_traits) || H.dna.species.exotic_blood != null)
- to_chat(user, "[H] does not have any usable blood!")
- return
- if(H.blood_volume <= BLOOD_VOLUME_SAFE)
- to_chat(user, "[H] is missing too much blood - you cannot drain [H.p_them()] further!")
- return
- H.blood_volume -= 100
- uses += 50
- user.Beam(H, icon_state = "drainbeam", time = 10)
- playsound(get_turf(H), 'sound/misc/enter_blood.ogg', 50, extrarange = SOUND_RANGE_SET(7))
- H.visible_message("[user] has drained some of [H]'s blood!",
- "[user] has drained some of your blood!")
- to_chat(user, "Your blood rite gains 50 charges from draining [H]'s blood.")
- new /obj/effect/temp_visual/cult/sparks(get_turf(H))
-
-// This should really be split into multiple procs
-/obj/item/melee/blood_magic/manipulator/afterattack__legacy__attackchain(atom/target, mob/living/carbon/human/user, proximity)
- if(user.holy_check())
- return
- if(!proximity)
- return ..()
- if(ishuman(target))
- if(IS_CULTIST(target))
- heal_cultist(user, target)
- target.clean_blood()
- else
- steal_blood(user, target)
- source.UpdateButtons()
- return
-
- if(isconstruct(target))
- heal_construct(user, target)
- source.UpdateButtons()
- return
-
- if(istype(target, /obj/item/blood_orb))
- var/obj/item/blood_orb/candidate = target
- if(candidate.blood)
- uses += candidate.blood
- to_chat(user, "You obtain [candidate.blood] blood from the orb of blood!")
- playsound(user, 'sound/misc/enter_blood.ogg', 50, extrarange = SOUND_RANGE_SET(7))
- qdel(candidate)
- source.UpdateButtons()
- return
- blood_draw(target, user)
- source.UpdateButtons()
-
-/obj/item/melee/blood_magic/manipulator/proc/blood_draw(atom/target, mob/living/carbon/human/user)
- var/temp = 0
- var/turf/T = get_turf(target)
- if(!T)
- return
- for(var/obj/effect/decal/cleanable/blood/B in range(T, 2))
- if(B.blood_state == BLOOD_STATE_HUMAN && (B.can_bloodcrawl_in()))
- if(B.bloodiness == 100) //Bonus for "pristine" bloodpools, also to prevent cheese with footprint spam
- temp += 30
- else
- temp += max((B.bloodiness ** 2) / 800, 1)
- new /obj/effect/temp_visual/cult/turf/open/floor(get_turf(B))
- qdel(B)
- for(var/obj/effect/decal/cleanable/trail_holder/TH in range(T, 2))
- new /obj/effect/temp_visual/cult/turf/open/floor(get_turf(TH))
- qdel(TH)
- if(temp)
- user.Beam(T, icon_state = "drainbeam", time = 15)
- new /obj/effect/temp_visual/cult/sparks(get_turf(user))
- playsound(T, 'sound/misc/enter_blood.ogg', 50, extrarange = SOUND_RANGE_SET(7))
- temp = round(temp)
- to_chat(user, "Your blood rite has gained [temp] charge\s from blood sources around you!")
- uses += max(1, temp)
-
-/obj/item/melee/blood_magic/manipulator/attack_self__legacy__attackchain(mob/living/user)
- if(user.holy_check())
- return
- var/list/options = list("Blood Orb (50)" = image(icon = 'icons/obj/cult.dmi', icon_state = "summoning_orb"),
- "Blood Recharge (75)" = image(icon = 'icons/mob/actions/actions_cult.dmi', icon_state = "blood_charge"),
- "Blood Spear (150)" = image(icon = 'icons/mob/actions/actions_cult.dmi', icon_state = "bloodspear"),
- "Blood Bolt Barrage (300)" = image(icon = 'icons/mob/actions/actions_cult.dmi', icon_state = "blood_barrage"))
- var/choice = show_radial_menu(user, src, options)
-
- switch(choice)
- if("Blood Orb (50)")
- if(uses < BLOOD_ORB_COST)
- to_chat(user, "You need [BLOOD_ORB_COST] charges to perform this rite.")
- else
- var/ammount = input("How much blood would you like to transfer? You have [uses] blood.", "How much blood?", 50) as null|num
- if(ammount < 50) // No 1 blood orbs, 50 or more.
- to_chat(user, "You need to give up at least 50 blood.")
- return
- if(ammount > uses) // No free blood either
- to_chat(user, "You do not have that much blood to give!")
- return
- uses -= ammount
- var/turf/T = get_turf(user)
- qdel(src)
- var/obj/item/blood_orb/rite = new(T)
- rite.blood = ammount
- if(user.put_in_hands(rite))
- to_chat(user, "A [rite.name] appears in your hand!")
- else
- user.visible_message("A [rite.name] appears at [user]'s feet!",
- "A [rite.name] materializes at your feet.")
-
- if("Blood Recharge (75)")
- if(uses < BLOOD_RECHARGE_COST)
- to_chat(user, "You need [BLOOD_RECHARGE_COST] charges to perform this rite.")
- else
- var/obj/rite = new /obj/item/melee/blood_magic/empower()
- uses -= BLOOD_RECHARGE_COST
- qdel(src)
- if(user.put_in_hands(rite))
- to_chat(user, "Your hand glows with power!")
- else
- to_chat(user, "You need a free hand for this rite!")
- uses += BLOOD_RECHARGE_COST // Refund the charges
- qdel(rite)
-
- if("Blood Spear (150)")
- if(uses < BLOOD_SPEAR_COST)
- to_chat(user, "You need [BLOOD_SPEAR_COST] charges to perform this rite.")
- else
- uses -= BLOOD_SPEAR_COST
- var/turf/T = get_turf(user)
- qdel(src)
- var/datum/action/innate/cult/spear/S = new(user)
- var/obj/item/cult_spear/rite = new(T)
- S.Grant(user, rite)
- rite.spear_act = S
- if(user.put_in_hands(rite))
- to_chat(user, "A [rite.name] appears in your hand!")
- else
- user.visible_message("A [rite.name] appears at [user]'s feet!",
- "A [rite.name] materializes at your feet.")
-
- if("Blood Bolt Barrage (300)")
- if(uses < BLOOD_BARRAGE_COST)
- to_chat(user, "You need [BLOOD_BARRAGE_COST] charges to perform this rite.")
- else
- var/obj/rite = new /obj/item/gun/projectile/shotgun/boltaction/enchanted/arcane_barrage/blood()
- uses -= BLOOD_BARRAGE_COST
- qdel(src)
- user.swap_hand()
- user.drop_item()
- if(user.put_in_hands(rite))
- to_chat(user, "Both of your hands glow with power!")
- else
- to_chat(user, "You need a free hand for this rite!")
- uses += BLOOD_BARRAGE_COST // Refund the charges
- qdel(rite)
- source.UpdateButtons()
diff --git a/code/game/gamemodes/cult/cult_actions.dm b/code/game/gamemodes/cult/cult_actions.dm
deleted file mode 100644
index 0fc42ddf9feb5..0000000000000
--- a/code/game/gamemodes/cult/cult_actions.dm
+++ /dev/null
@@ -1,128 +0,0 @@
-/datum/action/innate/cult
- button_overlay_icon = 'icons/mob/actions/actions_cult.dmi'
- button_background_icon_state = "bg_cult"
- check_flags = AB_CHECK_RESTRAINED|AB_CHECK_STUNNED|AB_CHECK_CONSCIOUS
- buttontooltipstyle = "cult"
-
-/datum/action/innate/cult/IsAvailable()
- if(!IS_CULTIST(owner))
- return FALSE
- return ..()
-
-
-//Comms
-/datum/action/innate/cult/comm
- name = "Communion"
- desc = "Whispered words that all cultists can hear. Warning:Nearby non-cultists can still hear you."
- button_overlay_icon_state = "cult_comms"
- check_flags = AB_CHECK_CONSCIOUS
-
-/datum/action/innate/cult/comm/Activate()
- var/input = tgui_input_text(usr, "Please choose a message to tell to the other acolytes.", "Voice of Blood", encode = FALSE)
- if(!input || !IsAvailable())
- return
- cultist_commune(usr, input)
- return
-
-/datum/action/innate/cult/comm/proc/cultist_commune(mob/living/user, message)
- if(!user || !message)
- return
-
- if(user.holy_check())
- return
-
- if(!user.can_speak())
- to_chat(user, "You can't speak!")
- return
-
- if(HAS_TRAIT(user, TRAIT_MUTE) || user.mind.miming) //Under vow of silence/mute?
- user.visible_message("[user] appears to whisper to themselves.",
- "You begin to whisper to yourself.") //Make them do *something* abnormal.
- sleep(10)
- else
- user.whisper("O bidai nabora se[pick("'","`")]sma!") // Otherwise book club sayings.
- sleep(10)
- user.whisper(message) // And whisper the actual message
-
- var/title
- var/large = FALSE
- var/living_message
- if(istype(user, /mob/living/simple_animal/demon/slaughter/cult)) //Harbringers of the Slaughter
- title = "Harbringer of the Slaughter"
- large = TRUE
- else
- title = "[(isconstruct(user) ? "Construct" : isshade(user) ? "" : "Acolyte")] [user.real_name]"
-
- living_message = "[title]: [message]"
- for(var/mob/M in GLOB.player_list)
- if(IS_CULTIST(M))
- to_chat(M, living_message)
- else if((M in GLOB.dead_mob_list) && !isnewplayer(M))
- to_chat(M, "[title] ([ghost_follow_link(user, ghost=M)]): [message]")
-
- log_say("(CULT) [message]", user)
-
-/datum/action/innate/cult/comm/spirit
- name = "Spiritual Communion"
- desc = "Conveys a message from the spirit realm that all cultists can hear."
-
-/datum/action/innate/cult/comm/spirit/IsAvailable()
- return TRUE
-
-/datum/action/innate/cult/comm/spirit/cultist_commune(mob/living/user, message)
-
- var/living_message
- if(!message)
- return
- var/title = "The [user.name]"
- living_message = "[title]: [message]"
-
- for(var/mob/M in GLOB.player_list)
- if(IS_CULTIST(M))
- to_chat(M, living_message)
- else if((M in GLOB.dead_mob_list) && !isnewplayer(M))
- to_chat(M, "[title] ([ghost_follow_link(user, ghost=M)]): [message]")
-
-
-//Objectives
-/datum/action/innate/cult/check_progress
- name = "Study the Veil"
- button_overlay_icon_state = "tome"
- desc = "Check your cult's current progress and objective."
- check_flags = AB_CHECK_CONSCIOUS
-
-/datum/action/innate/cult/check_progress/New()
- button_overlay_icon_state = GET_CULT_DATA(tome_icon, "tome")
- ..()
-
-/datum/action/innate/cult/check_progress/IsAvailable()
- return IS_CULTIST(owner) || isobserver(owner)
-
-/datum/action/innate/cult/check_progress/Activate()
- if(!IsAvailable())
- return
- if(SSticker?.mode?.cult_team)
- SSticker.mode.cult_team.study_objectives(usr, TRUE)
- else
- to_chat(usr, "You fail to study the Veil. (This should never happen, adminhelp and/or yell at a coder)")
-
-
-//Draw rune
-/datum/action/innate/cult/use_dagger
- name = "Draw Blood Rune"
- desc = "Use the ritual dagger to create a powerful blood rune."
- button_overlay_icon_state = "blood_dagger"
- default_button_position = "10:29,4:-2"
-
-/datum/action/innate/cult/use_dagger/Grant()
- button_overlay_icon_state = GET_CULT_DATA(dagger_icon, "blood_dagger")
- ..()
-
-/datum/action/innate/cult/use_dagger/Activate()
- var/obj/item/melee/cultblade/dagger/D = owner.find_item(/obj/item/melee/cultblade/dagger)
- if(D)
- owner.unequip(D)
- owner.put_in_hands(D)
- D.activate_self(owner)
- else
- to_chat(usr, "You do not seem to carry a ritual dagger to draw a rune with. If you need a new one, prepare and use the Summon Dagger spell.")
diff --git a/code/game/gamemodes/cult/cult_datums.dm b/code/game/gamemodes/cult/cult_datums.dm
deleted file mode 100644
index 827c962e14744..0000000000000
--- a/code/game/gamemodes/cult/cult_datums.dm
+++ /dev/null
@@ -1,258 +0,0 @@
-/datum/cult_info
- var/name = "Cult of Nar'Sie"
- var/theme = "blood"
- var/tome_icon = "tome"
- var/dagger_icon = "blood_dagger"
- var/sword_icon = "blood_blade"
- var/construct_glow = LIGHT_COLOR_BLOOD_MAGIC
-
- //God Entity
- var/entity_name = "Nar'Sie"
- var/entity_title1 = "The Dark One"
- var/entity_title2 = "The One Who Sees"
- var/entity_title3 = "The Geometer of Blood"
- var/entity_icon_state = "narsie"
- var/entity_spawn_animation = "narsie_spawn_anim"
-
-
- //Builder Construct
- var/artificer_name = "Artificer"
- var/artificer_icon_state = "artificer"
- var/artificer_dead_state = "shade_dead"
-
- //Behemoth Construct
- var/behemoth_name = "Behemoth"
- var/behemoth_icon_state = "behemoth"
- var/behemoth_dead_state = "shade_dead"
-
- //Wraith Construct
- var/wraith_name = "Wraith"
- var/wraith_icon_state = "floating"
- var/wraith_dead_state = "shade_dead"
- var/wraith_jaunt_out_animation = "phase_shift"
- var/wraith_jaunt_in_animation = "phase_shift2"
-
- //Armored Construct
- var/juggernaut_name = "Juggernaut"
- var/juggernaut_icon_state = "behemoth"
- var/juggernaut_dead_state = "shade_dead"
-
- //Harvester Construct
- var/harvester_name = "Harvester"
- var/harvester_icon_state = "harvester"
- var/harvester_dead_state = "shade_dead"
-
- //Proteon Construct
- var/proteon_name = "Proteon"
- var/proteon_icon_state = "proteon"
- var/proteon_dead_state = "shade_dead"
- //Shade Spirit
- var/shade_name = "Shade"
- var/shade_icon_state = "shade2"
- var/shade_dead_state = "shade_dead"
-
- //Turfs
- var/cult_floor_icon_state = "cult"
- var/cult_wall_icon_state = "cult"
- var/cult_girder_icon_state = "cultgirder"
-
- //Structures
- var/pylon_icon_state = "pylon"
- var/pylon_icon_state_off = "pylon_off"
-
- var/forge_icon_state = "forge"
- var/forge_icon_state_off = "forge_off"
-
- var/altar_icon_state = "altar"
- var/altar_icon_state_off = "altar_off"
-
- var/archives_icon_state = "archives"
- var/archives_icon_state_off = "archives_off"
-
- var/runed_metal_icon_state = "sheet-runed"
- var/runed_metal_item_state = "sheet-runed"
-
- var/airlock_runed_icon_file = 'icons/obj/doors/airlocks/cult/runed/cult.dmi'
- var/airlock_runed_overlays_file = 'icons/obj/doors/airlocks/cult/runed/cult-overlays.dmi'
-
- 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'
-
-
-/datum/cult_info/fire
- name = "Cult of Kha'Rin"
- theme = "fire"
- tome_icon = "helltome"
- dagger_icon = "hell_dagger"
- sword_icon = "hell_blade"
- construct_glow = LIGHT_COLOR_FIRE
-
- entity_name = "Kha'Rin"
- entity_title1 = "The Burning One"
- entity_title2 = "The One Who Consumes"
- entity_title3 = "The Harbinger of Fire"
- entity_icon_state = "kha'rin"
- entity_spawn_animation = "kha'rin_spawn_anim"
-
- cult_wall_icon_state = "hellcult"
- cult_floor_icon_state = "culthell"
- cult_girder_icon_state = "hell_girder"
-
- //artificer_name = "Summoner"
- artificer_icon_state = "summoner"
-
- //behemoth_name = "Incarnation of Pain"
- behemoth_icon_state = "incarnation_of_pain"
-
- //wraith_name = "Hell Knight"
- wraith_icon_state = "hell_knight"
- wraith_jaunt_out_animation = "infernal_rift_out"
- wraith_jaunt_in_animation = "infernal_rift_in"
-
- //juggernaut_name = "Incarnation of Pain"
- juggernaut_icon_state = "incarnation_of_pain"
-
- //harvester_name = "Lost Soul"
- harvester_icon_state = "lost_soul"
-
- //shade_name = "Ifrit"
- shade_icon_state = "ifrit"
-
- pylon_icon_state = "hell_pylon"
- pylon_icon_state_off = "hell_pylon_off"
-
- forge_icon_state = "hell_forge"
- forge_icon_state_off = "hell_forge_off"
-
- altar_icon_state = "hell_altar"
- altar_icon_state_off = "hell_altar_off"
-
- archives_icon_state = "hell_archives"
- archives_icon_state_off = "hell_archives_off"
-
- runed_metal_icon_state = "sheet_runed_hell"
- runed_metal_item_state = "sheet_runed_hell"
-
- airlock_runed_icon_file = 'icons/obj/doors/airlocks/cult/runed/hell.dmi'
- airlock_runed_overlays_file = 'icons/obj/doors/airlocks/cult/runed/hell-overlays.dmi'
-
- airlock_unruned_icon_file = 'icons/obj/doors/airlocks/cult/unruned/hell.dmi'
- airlock_unruned_overlays_file = 'icons/obj/doors/airlocks/cult/unruned/hell-overlays.dmi'
-
-/datum/cult_info/death
- name = "Cult of Mortality"
- theme = "death"
- tome_icon = "deathtome"
- dagger_icon = "death_dagger"
- sword_icon = "death_blade"
- construct_glow = LIGHT_COLOR_DARKRED
-
- entity_name = "The Reaper"
- entity_title1 = "The Silent One"
- entity_title2 = "The One Who Beckons"
- entity_title3 = "The Ferryman of Oblivion"
- entity_icon_state = "reaper"
- entity_spawn_animation = "reaper_spawn_anim"
-
- cult_wall_icon_state = "deathcult"
- cult_floor_icon_state = "cultdeath"
- cult_girder_icon_state = "reaper_cultgirder"
-
- //artificer_name = "Boneshaper"
- artificer_icon_state = "boneshaper"
-
- //behemoth_name = "Draugr"
- behemoth_icon_state = "golem"
-
- //wraith_name = "Envoy of Death"
- wraith_icon_state = "envoy_of_death"
- wraith_jaunt_out_animation = "shadowstep_out"
- wraith_jaunt_in_animation = "shadowstep_in"
-
- //juggernaut_name = "Golem"
- juggernaut_icon_state = "golem"
-
- //harvester_name = "Necrophage"
- harvester_icon_state = "necrophage"
-
- //shade_name = "Banshee"
- shade_icon_state = "banshee"
-
- pylon_icon_state = "reaper_pylon"
- pylon_icon_state_off = "reaper_pylon_off"
-
- forge_icon_state = "reaper_forge"
- forge_icon_state_off = "reaper_forge_off"
-
- altar_icon_state = "reaper_altar"
- altar_icon_state_off = "reaper_altar_off"
-
- archives_icon_state = "reaper_archives"
- archives_icon_state_off = "reaper_archives_off"
-
- runed_metal_icon_state = "sheet_runed_reaper"
- runed_metal_item_state = "sheet_runed_reaper"
-
- airlock_runed_icon_file = 'icons/obj/doors/airlocks/cult/runed/reaper.dmi'
- airlock_runed_overlays_file = 'icons/obj/doors/airlocks/cult/runed/reaper-overlays.dmi'
-
- airlock_unruned_icon_file = 'icons/obj/doors/airlocks/cult/unruned/reaper.dmi'
- airlock_unruned_overlays_file = 'icons/obj/doors/airlocks/cult/unruned/reaper-overlays.dmi'
-
-/datum/cult_info/proc/get_name(type_to_name)
- if(!type_to_name)
- return
- switch(type_to_name)
- if("god")
- return entity_name
- if("behemoth")
- return behemoth_name
- if("builder")
- return artificer_name
- if("juggernaut")
- return juggernaut_name
- if("harvester")
- return harvester_name
- if("wraith")
- return wraith_name
- if("proteon")
- return proteon_name
- if("shade")
- return shade_name
-
-/datum/cult_info/proc/get_icon(type_to_icon)
- if(!type_to_icon)
- return
- switch(type_to_icon)
- if("god")
- return entity_icon_state
- if("behemoth")
- return behemoth_icon_state
- if("builder")
- return artificer_icon_state
- if("juggernaut")
- return juggernaut_icon_state
- if("harvester")
- return harvester_icon_state
- if("wraith")
- return wraith_icon_state
- if("proteon")
- return proteon_icon_state
- if("shade")
- return shade_icon_state
- if("forge")
- return forge_icon_state
- if("forge_off")
- return forge_icon_state_off
- if("archives")
- return archives_icon_state
- if("archives_off")
- return archives_icon_state_off
- if("altar")
- return altar_icon_state
- if("altar_off")
- return altar_icon_state_off
- if("pylon")
- return pylon_icon_state
- if("pylon_off")
- return pylon_icon_state_off
diff --git a/code/game/gamemodes/cult/cult_mode.dm b/code/game/gamemodes/cult/cult_mode.dm
deleted file mode 100644
index 371eb3200f8a5..0000000000000
--- a/code/game/gamemodes/cult/cult_mode.dm
+++ /dev/null
@@ -1,54 +0,0 @@
-/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"
- config_tag = "cult"
- restricted_jobs = list("Chaplain", "AI", "Cyborg", "Internal Affairs Agent", "Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel", "Blueshield", "Nanotrasen Representative", "Magistrate", "Nanotrasen Career Trainer", "Nanotrasen Navy Officer", "Special Operations Officer", "Syndicate Officer", "Trans-Solar Federation General")
- protected_jobs = list()
- required_players = 30
- 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
-
-/datum/game_mode/cult/announce()
- to_chat(world, "The current game mode is - Cult!")
- to_chat(world, "Some crewmembers are attempting to start a cult! \nCultists - complete your objectives. Convert crewmembers to your cause by using the offer rune. Remember - there is no you, there is only the cult. \nPersonnel - Do not let the cult succeed in its mission. Brainwashing them with holy water reverts them to whatever CentComm-allowed faith they had.")
-
-/datum/game_mode/cult/pre_setup()
- if(GLOB.configuration.gamemode.prevent_mindshield_antags)
- restricted_jobs += protected_jobs
-
- var/list/cultists_possible = get_players_for_role(ROLE_CULTIST)
- for(var/cultists_number = 1 to max_cultists_to_start)
- if(!length(cultists_possible))
- break
- var/datum/mind/cultist = pick(cultists_possible)
- cultists_possible -= cultist
- pre_cult += cultist
- cultist.restricted_roles = restricted_jobs
- cultist.special_role = SPECIAL_ROLE_CULTIST
- return length(pre_cult)
-
-/datum/game_mode/cult/post_setup()
- new /datum/team/cult(pre_cult)
- ..()
-
-/datum/game_mode/cult/declare_completion()
- if(cult_team.cult_status == NARSIE_HAS_RISEN)
- SSticker.mode_result = "cult win - cult win"
- to_chat(world, "The cult wins! It has succeeded in summoning [GET_CULT_DATA(entity_name, "their god")]!")
- else if(cult_team.cult_status == NARSIE_HAS_FALLEN)
- SSticker.mode_result = "cult draw - narsie died, nobody wins"
- to_chat(world, "Nobody wins! [GET_CULT_DATA(entity_name, "the cult god")] was summoned, but banished!")
- else
- SSticker.mode_result = "cult loss - staff stopped the cult"
- to_chat(world, "The staff managed to stop the cult!")
-
- ..()
diff --git a/code/game/gamemodes/cult/cult_structures.dm b/code/game/gamemodes/cult/cult_structures.dm
deleted file mode 100644
index 26066e3b5bf1a..0000000000000
--- a/code/game/gamemodes/cult/cult_structures.dm
+++ /dev/null
@@ -1,344 +0,0 @@
-/// The amount of time necessary for a structure to be able to produce items after being built
-#define CULT_STRUCTURE_COOLDOWN 60 SECONDS
-
-/obj/structure/cult
- density = TRUE
- anchored = TRUE
- layer = BELOW_OBJ_LAYER
- icon = 'icons/obj/cult.dmi'
- light_power = 2
-
-//Noncult As we may have this on maps
-/obj/structure/cult/altar
- name = "Altar"
- desc = "A bloodstained altar."
- icon_state = "altar"
-
-/obj/structure/cult/forge
- name = "Daemon forge"
- desc = "A forge used in crafting unholy armors and weapons."
- icon_state = "forge"
- light_range = 2
- light_color = LIGHT_COLOR_LAVA
-
-/obj/structure/cult/pylon
- name = "Pylon"
- desc = "A floating crystal that hums with an unearthly energy."
- icon_state = "pylon"
- light_range = 1.5
- light_color = LIGHT_COLOR_RED
-
-//Cult versions cuase fuck map conflicts
-/obj/structure/cult/functional
- max_integrity = 100
- var/cooldowntime = 0
- var/death_message = "The structure falls apart." //The message shown when the structure is destroyed
- var/death_sound = 'sound/items/bikehorn.ogg'
- var/heathen_message = "You're a huge nerd, go away. Also, a coder forgot to put a message here."
- var/selection_title = "Oops"
- var/selection_prompt = "Choose your weapon, nerdwad"
- var/creation_delay = 2400
- var/list/choosable_items = list("A coder forgot to set this" = /obj/item/grown/bananapeel)
- var/creation_message = "A dank smoke comes out, and you pass out. When you come to, you notice a %ITEM%!"
-
-/obj/structure/cult/functional/obj_destruction()
- visible_message(death_message)
- playsound(src, death_sound, 50, TRUE)
- ..()
-
-/obj/structure/cult/functional/examine(mob/user)
- . = ..()
- if(IS_CULTIST(user) && cooldowntime > world.time)
- . += "The magic in [src] is weak, it will be ready to use again in [get_ETA()]."
- . += "[src] is [anchored ? "":"not "]secured to the floor."
-
-/obj/structure/cult/functional/attackby__legacy__attackchain(obj/item/I, mob/user, params)
- if(istype(I, /obj/item/melee/cultblade/dagger) && IS_CULTIST(user))
- if(user.holy_check())
- return
- anchored = !anchored
- to_chat(user, "You [anchored ? "":"un"]secure [src] [anchored ? "to":"from"] the floor.")
- if(!anchored)
- icon_state = GET_CULT_DATA(get_icon("[initial(icon_state)]_off"), "[initial(icon_state)]_off")
- else
- 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(!IS_CULTIST(user))
- to_chat(user, "[heathen_message]")
- return
- if(invisibility)
- to_chat(user, "The magic in [src] is being suppressed, reveal the structure first!")
- return
- if(HAS_TRAIT(user, TRAIT_HULK))
- to_chat(user, "You cannot seem to manipulate this structure with your bulky hands!")
- return
- if(!anchored)
- to_chat(user, "You need to anchor [src] to the floor with a dagger first.")
- return
- if(cooldowntime > world.time)
- to_chat(user, "The magic in [src] is weak, it will be ready to use again in [get_ETA()].")
- return
-
-
- var/list/pickable_items = get_choosable_items()
- var/choice = show_radial_menu(user, src, pickable_items, require_near = TRUE)
- var/picked_type = pickable_items[choice]
- if(!QDELETED(src) && picked_type && Adjacent(user) && !user.incapacitated() && cooldowntime <= world.time)
- cooldowntime = world.time + creation_delay
- var/obj/O = new picked_type
- if(isstructure(O) || !user.put_in_hands(O))
- O.forceMove(get_turf(src))
- to_chat(user, replacetext("[creation_message]", "%ITEM%", "[O.name]"))
-
-/**
- * Returns the items the cult can craft from this forge.
- *
- * Override on children for logic regarding game state.
- */
-/obj/structure/cult/functional/proc/get_choosable_items()
- return choosable_items.Copy() // Copied incase its modified on children
-
-/**
- * Returns the cooldown time in minutes and seconds
- */
-/obj/structure/cult/functional/proc/get_ETA()
- var/time = cooldowntime - world.time
- var/minutes = round(time / 600)
- var/seconds = round(time * 0.1, 1)
- var/message
- if(minutes)
- message = "[minutes] minute\s"
- seconds = seconds - (60 * minutes)
- if(seconds) // To avoid '2 minutes, 0 seconds.'
- message += "[minutes ? ", " : ""][seconds] second\s"
- return message
-
-/obj/structure/cult/functional/cult_conceal()
- density = FALSE
- visible_message("[src] fades away.")
- invisibility = INVISIBILITY_HIDDEN_RUNES
- alpha = 100 //To help ghosts distinguish hidden objs
- light_range = 0
- light_power = 0
- update_light()
-
-/obj/structure/cult/functional/cult_reveal()
- density = initial(density)
- invisibility = 0
- visible_message("[src] suddenly appears!")
- alpha = initial(alpha)
- light_range = initial(light_range)
- light_power = initial(light_power)
- update_light()
-
-/obj/structure/cult/functional/altar
- name = "altar"
- desc = "A bloodstained altar dedicated to a cult."
- icon_state = "altar"
- max_integrity = 150 //Sturdy
- death_message = "The altar breaks into splinters, releasing a cascade of spirits into the air!"
- death_sound = 'sound/effects/altar_break.ogg'
- heathen_message = "There is a foreboding aura to the altar and you want nothing to do with it."
- selection_prompt = "You study the rituals on the altar..."
- selection_title = "Altar"
- creation_message = "You kneel before the altar and your faith is rewarded with a %ITEM%!"
- choosable_items = list("Eldritch Whetstone" = /obj/item/whetstone/cult, "Flask of Unholy Water" = /obj/item/reagent_containers/drinks/bottle/unholywater,
- "Construct Shell" = /obj/structure/constructshell)
-
-/obj/structure/cult/functional/altar/Initialize(mapload)
- . = ..()
- icon_state = GET_CULT_DATA(altar_icon_state, "altar")
- cooldowntime = world.time + CULT_STRUCTURE_COOLDOWN
-
-/obj/structure/cult/functional/forge
- name = "daemon forge"
- desc = "A forge used in crafting the unholy weapons used by the armies of a cult."
- icon_state = "forge"
- light_range = 2
- light_color = LIGHT_COLOR_LAVA
- max_integrity = 300 //Made of metal
- death_message = "The forge falls apart, its lava cooling and winking away!"
- death_sound = 'sound/effects/forge_destroy.ogg'
- heathen_message = "Your hand feels like it's melting off as you try to touch the forge."
- selection_prompt = "You study the schematics etched on the forge..."
- selection_title = "Forge"
- creation_message = "You work the forge as dark knowledge guides your hands, creating a %ITEM%!"
- choosable_items = list("Shielded Robe" = /obj/item/clothing/suit/hooded/cultrobes/cult_shield, "Flagellant's Robe" = /obj/item/clothing/suit/hooded/cultrobes/flagellant_robe)
-
-/obj/structure/cult/functional/forge/get_choosable_items()
- . = ..()
- 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
-
-
-/obj/structure/cult/functional/forge/Initialize(mapload)
- . = ..()
- icon_state = GET_CULT_DATA(forge_icon_state, "forge")
-
-/obj/structure/cult/functional/forge/attackby__legacy__attackchain(obj/item/I, mob/user, params)
- if(istype(I, /obj/item/grab))
- var/obj/item/grab/G = I
- if(!iscarbon(G.affecting))
- return FALSE
- if(G.affecting == LAVA_PROOF)
- to_chat(user, "[G.affecting] is immune to lava!")
- return FALSE
- if(G.affecting.stat == DEAD)
- to_chat(user, "[G.affecting] is dead!")
- return FALSE
- var/mob/living/carbon/human/C = G.affecting
- var/obj/item/organ/external/head/head = C.get_organ("head")
- if(!head)
- to_chat(user, "[C] has no head!")
- return FALSE
-
- C.visible_message("[user] dunks [C]'s face into [src]'s lava!",
- "[user] dunks your face into [src]'s lava!")
- C.emote("scream")
- C.apply_damage(30, BURN, "head") // 30 fire damage because it's FUCKING LAVA
- head.disfigure() // Your face is unrecognizable because it's FUCKING LAVA
- C.UpdateDamageIcon()
- add_attack_logs(user, C, "Lava-dunked into [src]")
- user.changeNext_move(CLICK_CD_MELEE)
- return TRUE
- return ..()
-
-GLOBAL_LIST_INIT(blacklisted_pylon_turfs, typecacheof(list(
- /turf/simulated/floor/engine/cult,
- /turf/space,
- /turf/simulated/wall/indestructible,
- /turf/simulated/floor/lava,
- /turf/simulated/floor/chasm,
- /turf/simulated/wall/cult,
- /turf/simulated/wall/cult/artificer
- )))
-
-/obj/structure/cult/functional/pylon
- name = "pylon"
- desc = "A floating crystal that slowly heals those faithful to a cult."
- icon_state = "pylon"
- light_range = 1.5
- light_color = LIGHT_COLOR_RED
- max_integrity = 50 //Very fragile
- death_message = "The pylon's crystal vibrates and glows fiercely before violently shattering!"
- death_sound = 'sound/effects/pylon_shatter.ogg'
-
- var/heal_delay = 30
- var/last_heal = 0
- var/corrupt_delay = 50
- var/last_corrupt = 0
-
-/obj/structure/cult/functional/pylon/Initialize(mapload)
- . = ..()
- START_PROCESSING(SSobj, src)
- 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
-
-/obj/structure/cult/functional/pylon/Destroy()
- STOP_PROCESSING(SSobj, src)
- return ..()
-
-/obj/structure/cult/functional/pylon/cult_conceal()
- STOP_PROCESSING(SSobj, src)
- ..()
-
-/obj/structure/cult/functional/pylon/cult_reveal()
- START_PROCESSING(SSobj, src)
- ..()
-
-/obj/structure/cult/functional/pylon/process()
- if(!anchored)
- return
-
- if(last_heal <= world.time)
- last_heal = world.time + heal_delay
- for(var/mob/living/L in range(5, src))
- 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)
-
- if(ishuman(L))
- L.heal_overall_damage(1, 1, TRUE, FALSE, TRUE)
-
- else if(isshade(L) || isconstruct(L))
- var/mob/living/simple_animal/M = L
- if(M.health < M.maxHealth)
- M.adjustHealth(-1)
-
- if(ishuman(L) && L.blood_volume < BLOOD_VOLUME_NORMAL)
- L.blood_volume += 1
-
- if(!is_station_level(z) && last_corrupt <= world.time) //Pylons only convert tiles on offstation bases to help hide onstation cults from meson users
- var/list/validturfs = list()
- var/list/cultturfs = list()
- for(var/T in circleviewturfs(src, 5))
- if(istype(T, /turf/simulated/floor/engine/cult))
- cultturfs |= T
- continue
- if(is_type_in_typecache(T, GLOB.blacklisted_pylon_turfs))
- continue
- else
- validturfs |= T
-
- last_corrupt = world.time + corrupt_delay
-
- var/turf/T = safepick(validturfs)
- if(T)
- if(isfloorturf(T))
- T.ChangeTurf(/turf/simulated/floor/engine/cult)
- if(iswallturf(T))
- T.ChangeTurf(/turf/simulated/wall/cult/artificer)
- else
- var/turf/simulated/floor/engine/cult/F = safepick(cultturfs)
- if(F)
- new /obj/effect/temp_visual/cult/turf/open/floor(F)
- else
- // Are we in space or something? No cult turfs or
- // convertable turfs?
- last_corrupt = world.time + corrupt_delay * 2
-
-/obj/structure/cult/functional/archives
- name = "archives"
- desc = "A desk covered in arcane manuscripts and tomes in unknown languages. Looking at the text makes your skin crawl."
- icon_state = "archives"
- light_range = 1.5
- light_color = LIGHT_COLOR_FIRE
- max_integrity = 125 //Slightly sturdy
- death_message = "The desk breaks apart, its books falling to the floor."
- death_sound = 'sound/effects/wood_break.ogg'
- heathen_message = "What do you hope to seek?"
- selection_prompt = "You flip through the black pages of the archives..."
- selection_title = "Archives"
- creation_message = "You invoke the dark magic of the tomes creating a %ITEM%!"
- choosable_items = list("Shuttle Curse" = /obj/item/shuttle_curse, "Zealot's Blindfold" = /obj/item/clothing/glasses/hud/health/night/cultblind,
- "Veil Shifter" = /obj/item/cult_shift, "Reality sunderer" = /obj/item/portal_amulet, "Blank Tarot Card" = /obj/item/blank_tarot_card)
-
-/obj/structure/cult/functional/archives/Initialize(mapload)
- . = ..()
- icon_state = GET_CULT_DATA(archives_icon_state, "archives")
-
-/obj/effect/gateway
- name = "gateway"
- desc = "You're pretty sure that the abyss is staring back."
- icon = 'icons/obj/cult.dmi'
- icon_state = "hole"
- density = TRUE
- anchored = TRUE
-
-/obj/effect/gateway/singularity_act()
- return
-
-/obj/effect/gateway/singularity_pull()
- return
-
-/obj/effect/gateway/Bumped(atom/movable/AM)
- return
-
-#undef CULT_STRUCTURE_COOLDOWN
diff --git a/code/game/gamemodes/cult/ritual.dm b/code/game/gamemodes/cult/ritual.dm
deleted file mode 100644
index fd1e6f3f1438b..0000000000000
--- a/code/game/gamemodes/cult/ritual.dm
+++ /dev/null
@@ -1,203 +0,0 @@
-#define CULT_ELDERGOD "eldergod"
-#define CULT_SLAUGHTER "slaughter"
-
-/obj/item/melee/cultblade/dagger
- name = "ritual dagger"
- desc = "A strange dagger said to be used by sinister groups for \"preparing\" a corpse before sacrificing it to their dark gods."
- icon_state = "blood_dagger"
- item_state = "blood_dagger"
- w_class = WEIGHT_CLASS_SMALL
- force = 15
- throwforce = 25
- armour_penetration_flat = 35
- sprite_sheets_inhand = null // Override parent
- var/drawing_rune = FALSE
- var/scribe_multiplier = 1 // Lower is faster
-
-/obj/item/melee/cultblade/dagger/adminbus
- name = "ritual dagger of scribing, +1"
- desc = "VERY fast culto scribing at incredible high speed!"
- force = 16
- scribe_multiplier = 0.1
-
-/obj/item/melee/cultblade/dagger/Initialize(mapload)
- . = ..()
- 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(IS_CULTIST(user) || user.stat == DEAD)
- . += "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")]."
- . += "Striking another cultist with it will purge holy water from them."
- . += "Striking a noncultist will tear their flesh, additionally, if you recently downed them with cult magic it will stun them completely."
-
-/obj/item/melee/cultblade/dagger/pre_attack(atom/target, mob/living/user, params)
- if(..())
- return FINISH_ATTACK
-
- if(IS_CULTIST(target))
- if(target.reagents && target.reagents.has_reagent("holywater")) //allows cultists to be rescued from the clutches of ordained religion
- if(target == user) // Targeting yourself
- to_chat(user, "You can't remove holy water from yourself!")
-
- else // Targeting someone else
- to_chat(user, "You remove the taint from [target].")
- to_chat(target, "[user] removes the taint from your body.")
- target.reagents.del_reagent("holywater")
- add_attack_logs(user, target, "Hit with [src], removing the holy water from them")
-
- return FINISH_ATTACK
-
-/obj/item/melee/cultblade/dagger/activate_self(mob/user)
- if(..())
- return
-
- if(IS_CULTIST(user))
- scribe_rune(user)
- else
- to_chat(user, "[src] is covered in unintelligible shapes and markings.")
-
-/obj/item/melee/cultblade/dagger/proc/narsie_rune_check(mob/living/user, area/A)
- var/datum/game_mode/gamemode = SSticker.mode
-
- if(gamemode.cult_team.cult_status < NARSIE_NEEDS_SUMMONING)
- to_chat(user, "[GET_CULT_DATA(entity_name, "Your god")] is not ready to be summoned yet!")
- return FALSE
- if(gamemode.cult_team.cult_status == NARSIE_HAS_RISEN)
- to_chat(user, "\"I am already here. There is no need to try to summon me now.\"")
- return FALSE
-
- var/list/summon_areas = gamemode.cult_team.obj_summon.summon_spots
- if(!(A in summon_areas))
- to_chat(user, "[GET_CULT_DATA(entity_name, "Your god")] can only be summoned where the veil is weak - in [english_list(summon_areas)]!")
- 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 [GET_CULT_DATA(entity_name, "the cult")]!", "No"))
- if(user)
- if(confirm_final == "No" || confirm_final == null)
- to_chat(user, "You decide to prepare further before scribing the rune.")
- return FALSE
- else
- if(locate(/obj/effect/rune) in range(1, user))
- to_chat(user, "You need a space cleared of runes before you can summon [GET_CULT_DATA(entity_title1, "your god")]!")
- return FALSE
- else
- return TRUE
-
-/obj/item/melee/cultblade/dagger/proc/can_scribe(mob/living/user)
- if(!src || !user || loc != user || user.incapacitated())
- return FALSE
- if(drawing_rune)
- to_chat(user, "You're already drawing a rune!")
- return FALSE
-
- var/turf/T = get_turf(user)
- if(isspaceturf(T))
- return FALSE
- if((locate(/obj/effect/rune) in T) || (locate(/obj/effect/rune/narsie) in range(1, T)))
- to_chat(user, "There's already a rune here!")
- return FALSE
- return TRUE
-
-/obj/item/melee/cultblade/dagger/proc/scribe_rune(mob/living/user)
- var/list/shields = list()
- var/list/possible_runes = list()
- var/keyword
-
- if(!can_scribe(user)) // Check this before anything else
- return
-
- // Choosing a rune
- for(var/I in (subtypesof(/obj/effect/rune) - /obj/effect/rune/malformed))
- var/obj/effect/rune/R = I
- var/rune_name = initial(R.cultist_name)
- if(rune_name)
- possible_runes[rune_name] = R
- if(!length(possible_runes))
- return
-
- var/chosen_rune = tgui_input_list(user, "Choose a rite to scribe.", "Sigils of Power", possible_runes)
- if(!chosen_rune)
- return
- var/obj/effect/rune/rune = possible_runes[chosen_rune]
- var/narsie_rune = FALSE
- if(rune == /obj/effect/rune/narsie)
- narsie_rune = TRUE
- if(initial(rune.req_keyword))
- keyword = tgui_input_text(user, "Please enter a keyword for the rune.", "Enter Keyword")
- if(!keyword)
- return
-
- // Check everything again, in case they moved
- if(!can_scribe(user))
- return
-
- // Check if the rune is allowed
- var/area/A = get_area(src)
- var/turf/runeturf = get_turf(user)
- var/datum/game_mode/gamemode = SSticker.mode
- if(ispath(rune, /obj/effect/rune/summon))
- if(!is_station_level(runeturf.z) || isspacearea(A))
- to_chat(user, "The veil is not weak enough here to summon a cultist, you must be on station!")
- return
-
- if(ispath(rune, /obj/effect/rune/teleport))
- if(!is_level_reachable(user.z))
- to_chat(user, "You are too far away from the station to teleport!")
- return
-
- var/old_color = user.color // we'll temporarily redden the user for better feedback to fellow cultists. Store this to revert them back.
- if(narsie_rune)
- if(!narsie_rune_check(user, A))
- return // don't do shit
- 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, "The ritual can only begin where the veil is weak - in [english_list(summon_areas)]!")
- return
- GLOB.major_announcement.Announce("Figments from an eldritch god are being summoned into the [A.map_name] from an unknown dimension. Disrupt the ritual at all costs, before the station is destroyed! Space Law and SOP are suspended. The entire crew must kill cultists on sight.", "Central Command Higher Dimensional Affairs", 'sound/AI/cult_summon.ogg')
- for(var/I in spiral_range_turfs(1, user, 1))
- var/turf/T = I
- var/obj/machinery/shield/cult/narsie/N = new(T)
- shields |= N
- user.color = COLOR_RED
-
- // Draw the rune
- var/mob/living/carbon/human/H = user
- H.cult_self_harm(initial(rune.scribe_damage))
- var/others_message
- if(!narsie_rune)
- others_message = "[user] cuts [user.p_their()] body and begins writing in [user.p_their()] own blood!"
- else
- others_message = "[user] cuts [user.p_their()] body and begins writing something particularly ominous in [user.p_their()] own blood!"
- user.visible_message(others_message,
- "You slice open your body and begin drawing a sigil of [GET_CULT_DATA(entity_title3, "your god")].")
-
- drawing_rune = TRUE // Only one at a time
- var/scribe_successful = do_after(user, initial(rune.scribe_delay) * scribe_multiplier, target = runeturf)
- for(var/V in shields) // Only used for the 'Tear Veil' rune
- var/obj/machinery/shield/S = V
- if(S && !QDELETED(S))
- qdel(S)
- user.color = old_color
- drawing_rune = FALSE
- if(!scribe_successful)
- return
-
- user.visible_message("[user] creates a strange circle in [user.p_their()] own blood.",
- "You finish drawing the arcane markings of [GET_CULT_DATA(entity_title3, "your god")].")
-
- var/obj/effect/rune/R = new rune(runeturf, keyword)
- if(narsie_rune)
- for(var/obj/effect/rune/I in orange(1, R))
- qdel(I)
- SSblackbox.record_feedback("tally", "runes_scribed", 1, "[R.cultist_name]")
- R.blood_DNA = list()
- R.blood_DNA[H.dna.unique_enzymes] = H.dna.blood_type
- R.add_hiddenprint(H)
- R.color = H.dna.species.blood_color
- R.rune_blood_color = H.dna.species.blood_color
- to_chat(user, "The [lowertext(initial(rune.cultist_name))] rune [initial(rune.cultist_desc)]")
-
-#undef CULT_ELDERGOD
-#undef CULT_SLAUGHTER
diff --git a/code/game/gamemodes/cult/runes.dm b/code/game/gamemodes/cult/runes.dm
deleted file mode 100644
index 6639c11dcc1e3..0000000000000
--- a/code/game/gamemodes/cult/runes.dm
+++ /dev/null
@@ -1,1107 +0,0 @@
-GLOBAL_LIST_EMPTY(sacrificed) // A mixed list of minds and mobs
-GLOBAL_LIST_EMPTY(teleport_runes) // I'll give you two guesses
-
-/*
-This file contains runes.
-Runes are used by the cult to cause many different effects and are paramount to their success.
-They are drawn with a ritual dagger in blood, and are distinguishable to cultists and normal crew by examining.
-Fake runes can be drawn in crayon to fool people.
-Runes can either be invoked by one's self or with many different cultists. Each rune has a specific incantation that the cultists will say when invoking it.
-To draw a rune, use a ritual dagger.
-*/
-
-/obj/effect/rune
- /// Name non-cultists see
- name = "rune"
- /// Name that cultists see
- var/cultist_name = "basic rune"
- /// Description that non-cultists see
- desc = "An odd collection of symbols drawn in what seems to be blood."
- /// Description that cultists see
- var/cultist_desc = "a basic rune with no function." //This is shown to cultists who examine the rune in order to determine its true purpose.
- anchored = TRUE
- icon = 'icons/obj/rune.dmi'
- icon_state = "1"
- resistance_flags = FIRE_PROOF | UNACIDABLE | ACID_PROOF
- mouse_opacity = MOUSE_OPACITY_OPAQUE // So that runes aren't so hard to click
- var/visibility = 0
- var/view_range = 7
- invisibility = INVISIBILITY_RUNES
- layer = SIGIL_LAYER
- color = COLOR_BLOOD_BASE
-
- /// What is said by cultists when the rune is invoked
- var/invocation = "Aiy ele-mayo!"
- ///The amount of cultists required around the rune to invoke it. If only 1, any cultist can invoke it.
- var/req_cultists = 1
- /// Used for some runes, this is for when you want a rune to not be usable when in use.
- var/rune_in_use = FALSE
-
- /// How long the rune takes to create (Currently only different for the Nar'Sie rune)
- var/scribe_delay = 5 SECONDS
- /// How much damage you take from drawing the rune
- var/scribe_damage = 1
-
- /// If nearby cultists will also chant when invoked
- var/allow_excess_invokers = FALSE
- /// If constructs can invoke it
- var/construct_invoke = TRUE
-
- /// If the rune requires a keyword (e.g. Teleport runes)
- var/req_keyword = FALSE
- /// The actual keyword for the rune
- var/keyword
-
- /// How much damage cultists take when invoking it (This includes constructs)
- var/invoke_damage = 0
- /// The color of the rune. (Based on species blood color)
- var/rune_blood_color = COLOR_BLOOD_BASE
-
-/obj/effect/rune/Initialize(mapload, set_keyword)
- . = ..()
- if(set_keyword)
- keyword = set_keyword
- var/image/blood = image(loc = src)
- blood.override = 1
- for(var/mob/living/silicon/ai/AI in GLOB.ai_list)
- AI.client.images += blood
-
-/obj/effect/rune/examine(mob/user)
- . = ..()
- if(IS_CULTIST(user) || isobserver(user)) //If they're a cultist or a ghost, tell them the effects
- . += "Name: [cultist_name]"
- . += "Effects: [capitalize(cultist_desc)]"
- . += "Required Acolytes: [req_cultists]"
- if(req_keyword && keyword)
- . += "Keyword: [keyword]"
-
-/obj/effect/rune/attackby__legacy__attackchain(obj/I, mob/user, params)
- if(istype(I, /obj/item/melee/cultblade/dagger) && IS_CULTIST(user))
- if(!can_dagger_erase_rune(user))
- return
-
- var/obj/item/melee/cultblade/dagger/D = I
- user.visible_message("[user] begins to erase [src] with [I].")
- if(do_after(user, initial(scribe_delay) * D.scribe_multiplier, target = src))
- to_chat(user, "You carefully erase the [lowertext(cultist_name)] rune.")
- qdel(src)
- return
- if(istype(I, /obj/item/nullrod))
- if(IS_CULTIST(user))//cultist..what are doing..cultist..staph...
- user.drop_item()
- user.visible_message("[I] suddenly glows with a white light, forcing [user] to drop it in pain!", \
- "[I] suddenly glows with a white light that sears your hand, forcing you to drop it!") // TODO: Make this actually burn your hand
- return
- to_chat(user,"You disrupt the magic of [src] with [I].")
- qdel(src)
- return
- return ..()
-
-/obj/effect/rune/proc/can_dagger_erase_rune(mob/user)
- return TRUE
-
-/obj/effect/rune/attack_hand(mob/living/user)
- user.Move_Pulled(src) // So that you can still drag things onto runes
- if(!IS_CULTIST(user))
- to_chat(user, "You aren't able to understand the words of [src].")
- return
- var/list/invokers = can_invoke(user)
- if(length(invokers) >= req_cultists)
- invoke(invokers)
- else
- fail_invoke()
-
-/obj/effect/rune/attack_animal(mob/living/simple_animal/M)
- if(isshade(M) || isconstruct(M))
- 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, "You are unable to invoke the rune!")
-
-/obj/effect/rune/cult_conceal() //for concealing spell
- visible_message("[src] fades away.")
- invisibility = INVISIBILITY_HIDDEN_RUNES
- alpha = 100 //To help ghosts distinguish hidden runes
-
-/obj/effect/rune/cult_reveal() //for revealing spell
- invisibility = initial(invisibility)
- visible_message("[src] suddenly appears!")
- alpha = initial(alpha)
-
-/obj/effect/rune/is_cleanable()
- return TRUE
-
-/obj/effect/rune/cleaning_act(mob/user, atom/cleaner, cleanspeed = 5 SECONDS, text_verb = "scrub out", text_description = " with [cleaner].")
- if(issimulatedturf(loc))
- var/turf/simulated/T = get_turf(src)
- T.cleaning_act(user, cleaner, cleanspeed = cleanspeed, text_verb = text_verb, text_description = text_description, text_targetname = name) //Strings are deliberately "A = A" to avoid overrides
- return
- else
- ..()
-
-
-/*
-There are a few different procs each rune runs through when a cultist activates it.
-can_invoke() is called when a cultist activates the rune with an empty hand. If there are multiple cultists, this rune determines if the required amount is nearby.
-invoke() is the rune's actual effects.
-fail_invoke() is called when the rune fails, via not enough people around or otherwise. Typically this just has a generic 'fizzle' effect.
-structure_check() searches for nearby cultist structures required for the invocation. Proper structures are pylons, forges, archives, and altars.
-*/
-/obj/effect/rune/proc/can_invoke(mob/living/user)
- if(user.holy_check())
- return
- //This proc determines if the rune can be invoked at the time. If there are multiple required cultists, it will find all nearby cultists.
- var/list/invokers = list() //people eligible to invoke the rune
- var/list/chanters = list() //people who will actually chant the rune when passed to invoke()
- if(invisibility == INVISIBILITY_HIDDEN_RUNES)//hidden rune
- return
- // Get the user
- if(user)
- chanters |= user
- invokers |= user
- // Get anyone nearby
- if(req_cultists > 1 || allow_excess_invokers)
- for(var/mob/living/L in range(1, src))
- if(IS_CULTIST(L))
- if(L == user)
- continue
- if(L.stat)
- continue
- invokers |= L
-
- if(length(invokers) >= req_cultists) // If there's enough invokers
- if(allow_excess_invokers)
- chanters |= invokers // Let the others join in too
- else
- invokers -= user
- shuffle(invokers)
- for(var/i in 0 to req_cultists)
- var/L = pick_n_take(invokers)
- chanters |= L
- return chanters
-
-/obj/effect/rune/proc/invoke(list/invokers)
- //This proc contains the effects of the rune as well as things that happen afterwards. If you want it to spawn an object and then delete itself, have both here.
- SHOULD_CALL_PARENT(TRUE)
- var/ghost_invokers = 0
- for(var/M in invokers)
- var/mob/living/L = M
- if(!L)
- return
- if(L.has_status_effect(STATUS_EFFECT_SUMMONEDGHOST))
- ghost_invokers++
- if(invocation)
- if(!L.IsVocal() || L.cannot_speak_loudly())
- L.custom_emote(EMOTE_VISIBLE, message = pick("draws arcane sigils in the air.","gestures ominously.","silently mouths out an invocation.","places their hands on the rune, activating it."))
- else
- L.say(invocation)
- L.changeNext_move(CLICK_CD_MELEE)//THIS IS WHY WE CAN'T HAVE NICE THINGS
- if(invoke_damage)
- L.apply_damage(invoke_damage, BRUTE)
- to_chat(L, "[src] saps your strength!")
- do_invoke_glow()
- 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.
-
-/**
- * Spawns the phase in/out effects for a cult teleport.
- *
- * Arguments:
- * * user - Mob to teleport
- * * location - Location to teleport from
- * * target - Location to teleport to
- */
-/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
- user.notransform = TRUE
- user.invisibility = INVISIBILITY_MAXIMUM
- sleep(12)
- user.notransform = FALSE
- user.invisibility = 0
-
-/obj/effect/rune/proc/do_invoke_glow()
- var/oldtransform = transform
- animate(src, transform = matrix() * 2, alpha = 0, time = 5) // Fade out
- animate(transform = oldtransform, alpha = 255, time = 0)
-
-/obj/effect/rune/proc/fail_invoke(show_message = TRUE)
- //This proc contains the effects of a rune if it is not invoked correctly, through either invalid wording or not enough cultists. By default, it's just a basic fizzle.
- if(!invisibility && show_message) // No visible messages if not visible
- visible_message("The markings pulse with a small flash of red light, then fall dark.")
- animate(src, color = rgb(255, 0, 0), time = 0)
- animate(src, color = rune_blood_color, time = 5)
-
-//Malformed Rune: This forms if a rune is not drawn correctly. Invoking it does nothing but hurt the user.
-/obj/effect/rune/malformed
- cultist_name = "Malformed"
- cultist_desc = "a senseless rune written in gibberish. No good can come from invoking this."
- invocation = "Ra'sha yoka!"
- invoke_damage = 30
-
-/obj/effect/rune/malformed/invoke(list/invokers)
- ..()
- for(var/M in invokers)
- var/mob/living/L = M
- to_chat(L, "You feel your life force draining. [GET_CULT_DATA(entity_title3, "Your god")] is displeased.")
- qdel(src)
-
-/mob/proc/null_rod_check() //The null rod, if equipped, will protect the holder from the effects of most runes
- if(can_block_magic(MAGIC_RESISTANCE_HOLY))
- return TRUE
- return FALSE
-
-//Rite of Enlightenment: Converts a normal crewmember to the cult, or offer them as sacrifice if cant be converted.
-/obj/effect/rune/convert
- cultist_name = "Offer"
- cultist_desc = "offers non-cultists on top of it to the Dark One, either converting or sacrificing them. Sacrifices with a soul will result in a captured soulshard. This can be done with brains as well."
- invocation = "Mah'weyh pleggh at e'ntrath!"
- icon_state = "offering"
- req_cultists = 1
- allow_excess_invokers = TRUE
- rune_in_use = FALSE
-
-/obj/effect/rune/convert/invoke(list/invokers)
- if(rune_in_use)
- return
-
- var/list/offer_targets = list()
- var/turf/T = get_turf(src)
- for(var/mob/living/M in T)
- if(!IS_CULTIST(M) || (M.mind && IS_SACRIFICE_TARGET(M.mind)))
- if(isconstruct(M)) // No offering constructs please
- continue
- offer_targets += M
-
- // Offering a head/brain
- for(var/obj/item/organ/O in T)
- var/mob/living/brain/b_mob
- if(istype(O, /obj/item/organ/external/head)) // Offering a head
- var/obj/item/organ/external/head/H = O
- for(var/obj/item/organ/internal/brain/brain in H.contents)
- b_mob = brain.brainmob
- brain.forceMove(T)
-
- else if(istype(O, /obj/item/organ/internal/brain)) // Offering a brain
- var/obj/item/organ/internal/brain/brain = O
- b_mob = brain.brainmob
-
- if(b_mob && b_mob.mind && (!IS_CULTIST(b_mob) || IS_SACRIFICE_TARGET(b_mob.mind)))
- offer_targets += b_mob
-
- if(!length(offer_targets))
- fail_invoke()
- log_game("Offer rune failed - no eligible targets")
- rune_in_use = FALSE
- return
-
- rune_in_use = TRUE
- var/mob/living/L = pick(offer_targets)
- if(HAS_TRAIT(L, TRAIT_CULT_IMMUNITY))
- fail_invoke(FALSE)
- for(var/I in invokers)
- to_chat(I, "This sacrifice was already converted recently. Wait a minute before trying again!")
- rune_in_use = FALSE
- return
-
- if(L.stat != DEAD && SSticker.mode.cult_team.is_convertable_to_cult(L.mind))
- ..()
- do_convert(L, invokers)
- else
- invocation = "Barhah hra zar'garis!"
- ..()
- do_sacrifice(L, invokers)
- rune_in_use = FALSE
-
-/obj/effect/rune/convert/proc/do_convert(mob/living/convertee, list/invokers)
- if(length(invokers) < 2)
- fail_invoke()
- for(var/I in invokers)
- to_chat(I, "You need at least two invokers to convert!")
- return
-
- convertee.visible_message("[convertee] writhes in pain as the markings below them glow a bloody red!", \
- "AAAAAAAAAAAAAA-")
- convertee.mind.add_antag_datum(/datum/antagonist/cultist)
- to_chat(convertee, "Your blood pulses. Your head throbs. The world goes red. All at once you are aware of a horrible, horrible, truth. The veil of reality has been ripped away \
- and something evil takes root.")
- to_chat(convertee, "Assist your new compatriots in their dark dealings. Your goal is theirs, and theirs is yours. You serve [GET_CULT_DATA(entity_title3, "your god")] above all else. Bring it back.\
- ")
-
- 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("A dark force repairs [convertee]!",
- "Your damage has been repaired. Now spread the blood to others.")
- else
- H.visible_message("[convertee]'s wounds heal and close!",
- "Your wounds have been healed. Now spread the blood to others.")
- 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.clear_restraints()
- H.Silence(6 SECONDS) //Prevent "HALP MAINT CULT" before you realise you're converted
- if(H.reagents?.has_reagent("holywater"))
- H.reagents.del_reagent("holywater") // Also prevent fill stomach with holy water and "forgot" about it after converting
-
- var/obj/item/melee/cultblade/dagger/D = new(get_turf(src))
- if(H.equip_to_slot_if_possible(D, ITEM_SLOT_IN_BACKPACK, FALSE, TRUE))
- to_chat(H, "You have a dagger in your backpack. Use it to do [GET_CULT_DATA(entity_title1, "your god")]'s bidding.")
- else
- to_chat(H, "There is a dagger on the floor. Use it to do [GET_CULT_DATA(entity_title1, "your god")]'s bidding.")
-
-/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(length(invokers) < 3)
- for(var/M in invokers)
- to_chat(M, "[offering] is too greatly linked to the world! You need three acolytes!")
- fail_invoke()
- log_game("Sacrifice rune failed - not enough acolytes and target is living")
- return
-
- var/sacrifice_fulfilled
- var/worthless = FALSE
-
- if(isliving(offering) && !isbrain(offering))
- var/mob/living/L = offering
- if(isrobot(L) || ismachineperson(L))
- L.adjustBruteLoss(250)
- else
- L.adjustCloneLoss(120)
- L.death(FALSE)
-
- if(offering.mind)
- GLOB.sacrificed += offering.mind
- if(IS_SACRIFICE_TARGET(offering.mind))
- sacrifice_fulfilled = TRUE
- else
- GLOB.sacrificed += offering
-
- new /obj/effect/temp_visual/cult/sac(loc)
- for(var/M in invokers)
- if(sacrifice_fulfilled)
- to_chat(M, "\"Yes! This is the one I desire! You have done well.\"")
- if(!SSticker.mode.cult_team.mirror_shields_active) // Only show once
- to_chat(M, "You are now able to construct mirror shields inside the daemon forge.")
- SSticker.mode.cult_team.mirror_shields_active = TRUE
- else
- if((ishuman(offering) && offering.mind?.offstation_role && offering.mind.special_role != SPECIAL_ROLE_ERT) || HAS_MIND_TRAIT(offering, TRAIT_XENOBIO_SPAWNED_HUMAN)) //If you try it on a ghost role, or an envolved caterpillar/nymph, you get nothing
- to_chat(M, "\"This soul is of no use to either of us.\"")
- worthless = TRUE
- else if(ishuman(offering) || isrobot(offering))
- to_chat(M, "\"I accept this sacrifice.\"")
- else
- to_chat(M, "\"I accept this meager sacrifice.\"")
- playsound(offering, 'sound/misc/demon_consume.ogg', 100, TRUE, SOUND_RANGE_SET(10))
-
- if(((ishuman(offering) || isrobot(offering) || isbrain(offering)) && offering.mind) && !worthless)
- var/obj/item/soulstone/stone = new /obj/item/soulstone(get_turf(src))
- stone.invisibility = INVISIBILITY_MAXIMUM // So it's not picked up during transfer_soul()
- stone.transfer_soul("FORCE", offering, user) // If it cannot be added
- stone.invisibility = 0
- var/put_in_hands = user.put_in_any_hand_if_possible(stone)
- if(put_in_hands)
- to_chat(user, "A glowing crimson shard appears in your hand - your new ally contained within.")
- else
- if(isrobot(offering))
- offering.dust() //To prevent the MMI from remaining
- else
- offering.gib()
- playsound(offering, 'sound/magic/disintegrate.ogg', 100, TRUE, SOUND_RANGE_SET(10))
- if(sacrifice_fulfilled)
- SSticker.mode.cult_team.successful_sacrifice()
- return TRUE
-
-/obj/effect/rune/teleport
- cultist_name = "Teleport"
- cultist_desc = "warps everything above it to another chosen teleport rune."
- invocation = "Sas'so c'arta forbici!"
- icon_state = "teleport"
- req_keyword = TRUE
- light_power = 4
- var/obj/effect/temp_visual/cult/portal/inner_portal //The portal "hint" for off-station teleportations
- var/obj/effect/temp_visual/cult/rune_spawn/rune2/outer_portal
- var/listkey
-
-/obj/effect/rune/teleport/Initialize(mapload, set_keyword)
- . = ..()
- var/area/A = get_area(src)
- var/locname = initial(A.name)
- listkey = set_keyword ? "[set_keyword] [locname]":"[locname]"
- GLOB.teleport_runes += src
-
-/obj/effect/rune/teleport/Destroy()
- GLOB.teleport_runes -= src
- QDEL_NULL(inner_portal)
- QDEL_NULL(outer_portal)
- return ..()
-
-/obj/effect/rune/teleport/can_dagger_erase_rune(mob/user)
- // Can't erase telerunes if they have a portal open
- if(inner_portal || outer_portal)
- to_chat(user, "The portal needs to close first!")
- return FALSE
- return TRUE
-
-/obj/effect/rune/teleport/invoke(list/invokers)
- var/mob/living/user = invokers[1] //the first invoker is always the user
- var/list/potential_runes = list()
- var/list/teleportnames = list()
- var/list/duplicaterunecount = list()
-
- for(var/I in GLOB.teleport_runes)
- var/obj/effect/rune/teleport/R = I
- var/resultkey = R.listkey
- if(resultkey in teleportnames)
- duplicaterunecount[resultkey]++
- resultkey = "[resultkey] ([duplicaterunecount[resultkey]])"
- else
- teleportnames += resultkey
- duplicaterunecount[resultkey] = 1
- if(R != src && is_level_reachable(R.z))
- potential_runes[resultkey] = R
-
- if(!length(potential_runes))
- to_chat(user, "There are no valid runes to teleport to!")
- log_game("Teleport rune failed - no other teleport runes")
- fail_invoke()
- return
-
- if(!is_level_reachable(user.z))
- to_chat(user, "You are too far away from the station to teleport!")
- log_game("Teleport rune failed - user in away mission")
- fail_invoke()
- return
-
- var/input_rune_key = tgui_input_list(user, "Choose a rune to teleport to.", "Rune to Teleport to", potential_runes) //we know what key they picked
- var/obj/effect/rune/teleport/actual_selected_rune = potential_runes[input_rune_key] //what rune does that key correspond to?
- if(QDELETED(src) || QDELETED(actual_selected_rune) ||!Adjacent(user) || user.incapacitated())
- fail_invoke()
- return
-
- var/turf/T = get_turf(src)
- var/turf/target = get_turf(actual_selected_rune)
- var/movedsomething = FALSE
- var/moveuser = FALSE
- for(var/atom/movable/A in T)
- if(SEND_SIGNAL(A, COMSIG_MOVABLE_TELEPORTING, target) & COMPONENT_BLOCK_TELEPORT)
- continue
- if(ishuman(A))
- if(A != user) // Teleporting someone else
- INVOKE_ASYNC(src, PROC_REF(teleport_effect), A, T, target)
- else // Teleporting yourself
- INVOKE_ASYNC(src, PROC_REF(teleport_effect), user, T, target)
- if(A.move_resist == INFINITY)
- continue //object cant move, shouldnt teleport
- if(A == user)
- moveuser = TRUE
- movedsomething = TRUE
- continue
- if(!A.anchored)
- movedsomething = TRUE
- A.forceMove(target)
-
- if(movedsomething)
- ..()
- if(is_mining_level(z) && !is_mining_level(target.z)) //No effect if you stay on lavaland
- actual_selected_rune.handle_portal("lava")
- else if(!is_station_level(z) || isspacearea(get_area(src)))
- actual_selected_rune.handle_portal("space", T)
- user.visible_message("There is a sharp crack of inrushing air, and everything above the rune disappears!",
- "You[moveuser ? "r vision blurs, and you suddenly appear somewhere else":" send everything above the rune away"].")
- if(moveuser)
- user.forceMove(target)
- else
- fail_invoke()
-
-/obj/effect/rune/teleport/proc/handle_portal(portal_type, turf/origin)
- var/turf/T = get_turf(src)
- if(inner_portal || outer_portal)
- close_portal() // To avoid stacking descriptions/animations
- playsound(T, pick('sound/effects/sparks1.ogg', 'sound/effects/sparks2.ogg', 'sound/effects/sparks3.ogg', 'sound/effects/sparks4.ogg'), 100, TRUE, 14)
- inner_portal = new /obj/effect/temp_visual/cult/portal(T)
-
- if(portal_type == "space")
- light_color = color
- desc += " A tear in reality reveals a black void interspersed with dots of light... something recently teleported here from space. "
-
- // Space base near the station
- if(is_station_level(origin.z))
- desc += "The void feels like it's trying to pull you to the [dir2text(get_dir(T, origin))], near the station!"
- // Space base on another Z-level
- else
- desc += "The void feels like it's trying to pull you to the [dir2text(get_dir(T, origin))], in the direction of space sector [origin.z]!"
-
- else
- inner_portal.icon_state = "lava"
- light_color = LIGHT_COLOR_FIRE
- desc += " A tear in reality reveals a coursing river of lava... something recently teleported here from the Lavaland Mines!"
-
- outer_portal = new(T, 60 SECONDS, color)
- light_range = 4
- update_light()
- addtimer(CALLBACK(src, PROC_REF(close_portal)), 60 SECONDS, TIMER_UNIQUE)
-
-/obj/effect/rune/teleport/proc/close_portal()
- qdel(inner_portal)
- qdel(outer_portal)
- desc = initial(desc)
- light_range = 0
- update_light()
-
-
-//Rune of Empowering : Enables carrying 4 blood spells, greatly reduce blood cost
-/obj/effect/rune/empower
- cultist_name = "Empower"
- cultist_desc = "allows cultists to prepare greater amounts of blood magic at far less of a cost."
- invocation = "H'drak v'loso, mir'kanas verbot!"
- icon_state = "empower"
- construct_invoke = FALSE
-
-/obj/effect/rune/empower/invoke(list/invokers)
- . = ..()
- var/mob/living/user = invokers[1] //the first invoker is always the user
- for(var/datum/action/innate/cult/blood_magic/BM in user.actions)
- BM.Activate()
-
-//Rite of Resurrection: Requires a dead or inactive cultist. When reviving the dead, you can only perform one revival for every three sacrifices your cult has carried out.
-/obj/effect/rune/raise_dead
- cultist_name = "Revive"
- cultist_desc = "requires a dead, alive, mindless, or inactive cultist placed upon the rune. For each three bodies sacrificed to the dark patron, one body will be mended and their mind awoken. Mending living cultist requires two cultists at the rune"
- invocation = "Pasnar val'keriam usinar. Savrae ines amutan. Yam'toth remium il'tarat!" //Depends on the name of the user - see below
- icon_state = "revive"
- var/static/sacrifices_used = -SOULS_TO_REVIVE // Cultists get one "free" revive
- allow_excess_invokers = TRUE
-
-/obj/effect/rune/raise_dead/examine(mob/user)
- . = ..()
- if(IS_CULTIST(user) || user.stat == DEAD)
- . += "Sacrifices unrewarded: [length(GLOB.sacrificed) - sacrifices_used]"
- . += "Sacrifice cost per ressurection: [SOULS_TO_REVIVE]"
-
-/obj/effect/rune/raise_dead/proc/revive_alive(mob/living/target)
- target.visible_message("Dark magic begins to surround [target], regenerating their body.")
- if(!do_after(target, 10 SECONDS, FALSE, target, allow_moving = FALSE, progress = TRUE))
- target.visible_message("Dark magic silently disappears.")
- return FALSE
- target.revive()
- return TRUE
-
-/obj/effect/rune/raise_dead/proc/revive_dead(mob/living/target)
- target.revive()
- if(target.ghost_can_reenter())
- target.grab_ghost()
- return TRUE
-
-/obj/effect/rune/raise_dead/invoke(list/invokers)
- var/turf/T = get_turf(src)
- var/mob/living/mob_to_revive
- var/list/potential_revive_mobs = list()
- var/mob/living/user = invokers[1]
- if(rune_in_use)
- return
- rune_in_use = TRUE
- var/diff = length(GLOB.sacrificed) - SOULS_TO_REVIVE - sacrifices_used
- var/revived_from_dead = FALSE
- if(diff < 0)
- to_chat(user, "Your cult must carry out [abs(diff)] more sacrifice\s before it can revive another cultist!")
- fail_invoke()
- return
- for(var/mob/living/M in T.contents)
- if(!IS_CULTIST(M))
- continue
- potential_revive_mobs |= M
- if(!length(potential_revive_mobs))
- to_chat(user, "There are no cultists on the rune!")
- log_game("Raise Dead rune failed - no cultists to revive")
- fail_invoke()
- return
- if(length(potential_revive_mobs) > 1)
- mob_to_revive = tgui_input_list(user, "Choose a cultist to revive.", "Cultist to Revive", potential_revive_mobs)
- else // If there's only one, no need for a menu
- mob_to_revive = potential_revive_mobs[1]
- if(!validness_checks(mob_to_revive, user))
- fail_invoke()
- return
-
- if(mob_to_revive.stat != DEAD && length(invokers) < 2)
- to_chat(user, "You need at least two cultists to heal cultist!")
- log_game("Raise Dead rune failed - not enough cultists to heal alive")
- fail_invoke()
- return
-
- if(mob_to_revive.stat != DEAD)
- if(!revive_alive(mob_to_revive))
- fail_invoke()
- return
- else
- if(!revive_dead(mob_to_revive))
- fail_invoke()
- return
- revived_from_dead = TRUE
- ..()
- sacrifices_used += SOULS_TO_REVIVE
-
- if(!mob_to_revive.get_ghost() && (!mob_to_revive.client || mob_to_revive.client.is_afk()))
- set waitfor = FALSE
- to_chat(user, "[mob_to_revive] was revived, but their mind is lost! Seeking a lost soul to replace it.")
- var/list/mob/dead/observer/candidates = SSghost_spawns.poll_candidates("Would you like to play as a revived Cultist?", ROLE_CULTIST, TRUE, poll_time = 20 SECONDS, source = /obj/item/melee/cultblade/dagger)
- if(length(candidates) && !QDELETED(mob_to_revive))
- var/mob/dead/observer/C = pick(candidates)
- to_chat(mob_to_revive, "Your physical form has been taken over by another soul due to your inactivity! Ahelp if you wish to regain your form.")
- message_admins("[key_name_admin(C)] has taken control of ([key_name_admin(mob_to_revive)]) to replace an AFK player.")
- mob_to_revive.ghostize(FALSE)
- mob_to_revive.key = C.key
- dust_if_respawnable(C)
- else
- fail_invoke()
- return
- if(!revived_from_dead)
- mob_to_revive.visible_message("[mob_to_revive] draws in a huge breath, red light shining from [mob_to_revive.p_their()] eyes.", \
- "All your injuries are now gone!")
- rune_in_use = FALSE
- return
- SEND_SOUND(mob_to_revive, sound('sound/ambience/antag/bloodcult.ogg'))
- to_chat(mob_to_revive, "\"PASNAR SAVRAE YAM'TOTH. Arise.\"")
- mob_to_revive.visible_message("[mob_to_revive] draws in a huge breath, red light shining from [mob_to_revive.p_their()] eyes.", \
- "You awaken suddenly from the void. You're alive!")
- rune_in_use = FALSE
-
-/obj/effect/rune/raise_dead/proc/validness_checks(mob/living/target_mob, mob/living/user)
- if(QDELETED(src))
- return FALSE
- if(QDELETED(user))
- return FALSE
- if(!Adjacent(user) || user.incapacitated())
- return FALSE
- if(QDELETED(target_mob))
- return FALSE
- var/turf/T = get_turf(src)
- if(target_mob.loc != T)
- to_chat(user, "The cultist to revive has been moved!")
- log_game("Raise Dead rune failed - revival target moved")
- return FALSE
- return TRUE
-
-/obj/effect/rune/raise_dead/fail_invoke()
- ..()
- rune_in_use = FALSE
- for(var/mob/living/M in range(0, src))
- if(IS_CULTIST(M) && M.stat == DEAD)
- M.visible_message("[M] twitches.")
-
-//Rite of the Corporeal Shield: When invoked, becomes solid and cannot be passed. Invoke again to undo.
-/obj/effect/rune/wall
- cultist_name = "Barrier"
- cultist_desc = "when invoked makes a temporary wall to block passage. Can be destroyed by brute force. Can be invoked again to reverse this."
- invocation = "Khari'd! Eske'te tannin!"
- icon_state = "barrier"
- ///The barrier summoned by the rune when invoked. Tracked as a variable to prevent refreshing the barrier's integrity. shieldgen.dm
- var/obj/machinery/shield/cult/barrier/B
-
-/obj/effect/rune/wall/Initialize(mapload)
- . = ..()
- B = new /obj/machinery/shield/cult/barrier(loc)
- B.parent_rune = src
-
-/obj/effect/rune/wall/Destroy()
- if(B && !QDELETED(B))
- QDEL_NULL(B)
- return ..()
-
-/obj/effect/rune/wall/invoke(list/invokers)
- var/mob/living/user = invokers[1]
- ..()
- var/amount = 1
- if(B.Toggle()) // Toggling on
- for(var/obj/effect/rune/wall/rune in orange(1, src)) // Chaining barriers
- if(!rune.B.density) // Barrier is currently invisible
- amount++ // Count the invoke damage for each rune
- rune.do_invoke_glow()
- rune.B.Toggle()
- if(iscarbon(user))
- var/mob/living/carbon/C = user
- C.cult_self_harm(2 * amount)
-
-//Rite of Joined Souls: Summons a single cultist.
-/obj/effect/rune/summon
- cultist_name = "Summon Cultist"
- cultist_desc = "summons a single cultist to the rune. (Cannot summon restrained cultists!)"
- invocation = "N'ath reth sh'yro eth d'rekkathnor!"
- req_cultists = 2
- invoke_damage = 10
- icon_state = "summon"
-
-/obj/effect/rune/summon/invoke(list/invokers)
- var/mob/living/user = invokers[1]
- var/list/cultists = list()
-
- 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)
- var/mob/living/cultist_to_summon = cultists[input]
- if(QDELETED(src) || !Adjacent(user) || user.incapacitated())
- return
- if(!cultist_to_summon)
- log_game("Summon Cultist rune failed - no target")
- return
- if(cultist_to_summon.stat == DEAD)
- to_chat(user, "[cultist_to_summon] has died!")
- fail_invoke()
- log_game("Summon Cultist rune failed - target died")
- return
- if(cultist_to_summon.pulledby || cultist_to_summon.buckled)
- to_chat(user, "[cultist_to_summon] is being held in place!")
- to_chat(cultist_to_summon, "You feel a tugging sensation, but you are being held in place!")
- fail_invoke()
- log_game("Summon Cultist rune failed - target restrained")
- return
- if(!IS_CULTIST(cultist_to_summon))
- to_chat(user, "[cultist_to_summon] is not a follower of [GET_CULT_DATA(entity_title3, "our god")]!")
- fail_invoke()
- log_game("Summon Cultist rune failed - target was deconverted")
- return
- if(is_away_level(cultist_to_summon.z))
- to_chat(user, "[cultist_to_summon] is not in our dimension!")
- fail_invoke()
- log_game("Summon Cultist rune failed - target in away mission")
- return
- if(SEND_SIGNAL(cultist_to_summon, COMSIG_MOVABLE_TELEPORTING, get_turf(src)) & COMPONENT_BLOCK_TELEPORT)
- to_chat(user, "[cultist_to_summon] is anchored in bluespace!")
- fail_invoke()
- log_game("Summon Cultist rune failed - anchored in bluespace")
- return
-
- cultist_to_summon.visible_message("[cultist_to_summon] suddenly disappears in a flash of red light!", \
- "Overwhelming vertigo consumes you as you are hurled through the air!")
- ..()
- INVOKE_ASYNC(src, PROC_REF(teleport_effect), cultist_to_summon, get_turf(cultist_to_summon), get_turf(src))
- visible_message("[src] begins to bubble and rises into the form of [cultist_to_summon]!")
- cultist_to_summon.forceMove(get_turf(src))
- qdel(src)
-
-/**
- * # Blood Boil Rune
- *
- * When invoked deals up to 30 burn damage to nearby non-cultists and sets them on fire.
- *
- * On activation the rune charges for six seconds, changing colour, glowing, and giving out a warning to all nearby mobs.
- * After the charging period the rune burns any non-cultists in view and sets them on fire. After another short wait it does the same again with slightly higher damage.
- * If the cultists channeling the rune move away or are stunned at any point, the rune is deleted. So it can be countered pretty easily with flashbangs.
- */
-/obj/effect/rune/blood_boil
- cultist_name = "Boil Blood"
- cultist_desc = "boils the blood of non-believers who can see the rune, rapidly dealing extreme amounts of damage. Requires 2 invokers channeling the rune."
- invocation = "Dedo ol'btoh!"
- icon_state = "blood_boil"
- light_color = LIGHT_COLOR_LAVA
- req_cultists = 2
- invoke_damage = 15
- construct_invoke = FALSE
- var/tick_damage = 10 // 30 burn damage total + damage taken by being on fire/overheating
- rune_in_use = FALSE
-
-/obj/effect/rune/blood_boil/invoke(list/invokers)
- if(rune_in_use)
- return
- ..()
- rune_in_use = TRUE
- var/turf/T = get_turf(src)
- var/list/targets = list()
- for(var/mob/living/L in viewers(T))
- if(!IS_CULTIST(L) && L.blood_volume && !ismachineperson(L))
- var/atom/I = L.null_rod_check()
- if(I)
- if(isitem(I))
- to_chat(L, "[I] suddenly burns hotly before returning to normal!")
- continue
- targets += L
-
- // Six seconds buildup
- visible_message("A haze begins to form above [src]!")
- animate(src, color = "#FC9A6D", time = 6 SECONDS)
- set_light(6, 1, color)
- sleep(6 SECONDS)
- visible_message("[src] turns a bright, burning orange!")
- if(!burn_check())
- return
-
- for(var/I in targets)
- to_chat(I, "Your blood boils in your veins!")
- do_area_burn(T, 1)
- animate(src, color = "#FFDF80", time = 5 SECONDS)
- sleep(5 SECONDS)
- if(!burn_check())
- return
-
- do_area_burn(T, 2)
- animate(src, color = "#FFFFFF", time = 5 SECONDS)
- sleep(5 SECONDS)
- if(!burn_check())
- return
-
- do_area_burn(T, 3)
- qdel(src)
-
-/obj/effect/rune/blood_boil/proc/do_area_burn(turf/T, iteration)
- 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(!IS_CULTIST(L) && L.blood_volume && !ismachineperson(L))
- if(L.null_rod_check())
- continue
- L.take_overall_damage(0, tick_damage * multiplier)
- L.adjust_fire_stacks(2)
- L.IgniteMob()
- playsound(src, 'sound/effects/bamf.ogg', 100, TRUE)
- do_invoke_glow()
- sleep(0.6 SECONDS) // Only one 'animate()' can play at once, so this waits for the pulse to finish
-
-/obj/effect/rune/blood_boil/proc/burn_check()
- . = TRUE
- if(QDELETED(src))
- return FALSE
- var/list/cultists = list()
- for(var/mob/living/M in range(1, src)) // Get all cultists currently in range
- if(IS_CULTIST(M) && !M.incapacitated())
- cultists += M
-
- if(length(cultists) < req_cultists) // Stop the rune there's not enough invokers
- visible_message("[src] loses its glow and dissipates!")
- qdel(src)
-
-/obj/effect/rune/manifest
- cultist_name = "Spirit Realm"
- cultist_desc = "manifests a spirit servant of the Dark One and allows you to ascend as a spirit yourself. The invoker must not move from atop the rune, and will take damage for each summoned spirit."
- invocation = "Gal'h'rfikk harfrandid mud'gib!" //how the fuck do you pronounce this
- icon_state = "spirit_realm"
- construct_invoke = FALSE
- var/mob/dead/observer/ghost = null //The cult ghost of the user
- var/default_ghost_limit = 4 //Lowered by the amount of cult objectives done
- var/minimum_ghost_limit = 2 //But cant go lower than this
- var/ghosts = 0
-
-/obj/effect/rune/manifest/examine(mob/user)
- . = ..()
- if(IS_CULTIST(user) || user.stat == DEAD)
- . += "Amount of ghosts summoned: [ghosts]"
- . += "Maximum amount of ghosts: [clamp(default_ghost_limit - SSticker.mode.cult_team.sacrifices_done, minimum_ghost_limit, default_ghost_limit)]"
- . += "Lowers to a minimum of [minimum_ghost_limit] for each objective accomplished."
-
-/obj/effect/rune/manifest/invoke(list/invokers)
- . = ..()
- var/mob/living/user = invokers[1]
- var/turf/T = get_turf(src)
- if(!(user in get_turf(src)))
- to_chat(user, "You must be standing on [src]!")
- fail_invoke()
- log_game("Manifest rune failed - user not standing on rune")
- return
- if(user.has_status_effect(STATUS_EFFECT_SUMMONEDGHOST))
- to_chat(user, "Ghosts can't summon more ghosts!")
- fail_invoke()
- log_game("Manifest rune failed - user is a ghost")
- return
-
- var/choice = tgui_alert(user, "You tear open a connection to the spirit realm...", "Invoke", list("Summon a Cult Ghost", "Ascend as a Dark Spirit", "Cancel"))
- if(choice == "Summon a Cult Ghost")
- if(!is_station_level(z) || isspacearea(get_area(src)))
- to_chat(user, "The veil is not weak enough here to manifest spirits, you must be on station!")
- fail_invoke()
- log_game("Manifest rune failed - not on station")
- return
- if(user.health <= 40)
- to_chat(user, "Your body is too weak to manifest spirits, heal yourself first.")
- fail_invoke()
- log_game("Manifest rune failed - not enough health")
- return list()
- if(ghosts >= clamp(default_ghost_limit - SSticker.mode.cult_team.sacrifices_done, minimum_ghost_limit, default_ghost_limit))
- to_chat(user, "You are sustaining too many ghosts to summon more!")
- fail_invoke()
- log_game("Manifest rune failed - too many summoned ghosts")
- return list()
- summon_ghosts(user, T)
-
- else if(choice == "Ascend as a Dark Spirit")
- ghostify(user, T)
-
-
-/obj/effect/rune/manifest/proc/summon_ghosts(mob/living/user, turf/T)
- notify_ghosts("Manifest rune created in [get_area(src)].", ghost_sound = 'sound/effects/ghost2.ogg', source = src)
- var/list/ghosts_on_rune = list()
- for(var/mob/dead/observer/O in T)
- if(!O.client)
- continue
- if(IS_CULTIST(O) || jobban_isbanned(O, ROLE_CULTIST))
- continue
- if(!HAS_TRAIT(O, TRAIT_RESPAWNABLE) || QDELETED(src) || QDELETED(O))
- continue
- if(!O.mind)
- continue
- if(O.mind.current && HAS_TRAIT(O.mind.current, SCRYING))
- continue
- ghosts_on_rune += O
- if(!length(ghosts_on_rune))
- to_chat(user, "There are no spirits near [src]!")
- fail_invoke()
- log_game("Manifest rune failed - no nearby ghosts")
- return list()
-
- var/mob/dead/observer/ghost_to_spawn = pick(ghosts_on_rune)
- var/mob/living/carbon/human/new_human = new(T)
- new_human.real_name = ghost_to_spawn.real_name
- new_human.key = ghost_to_spawn.key
- new_human.gender = ghost_to_spawn.gender
- new_human.alpha = 150 //Makes them translucent
- new_human.equipOutfit(/datum/outfit/ghost_cultist) //give them armor
- new_human.apply_status_effect(STATUS_EFFECT_SUMMONEDGHOST, user) //ghosts can't summon more ghosts, also lets you see actual ghosts
- for(var/obj/item/organ/external/current_organ in new_human.bodyparts)
- current_organ.limb_flags |= CANNOT_DISMEMBER //you can't chop of the limbs of a ghost, silly
- ghosts++
- playsound(src, 'sound/misc/exit_blood.ogg', 50, TRUE, SOUND_RANGE_SET(10))
- user.visible_message("A cloud of red mist forms above [src], and from within steps... a [new_human.gender == FEMALE ? "wo" : ""]man.",
- "Your blood begins flowing into [src]. You must remain in place and conscious to maintain the forms of those summoned. This will hurt you slowly but surely...")
-
- var/obj/machinery/shield/cult/weak/shield = new(T)
- new_human.mind.add_antag_datum(/datum/antagonist/cultist)
- to_chat(new_human, "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.")
-
- while(!QDELETED(src) && !QDELETED(user) && !QDELETED(new_human) && (user in T))
- if(new_human.InCritical())
- to_chat(user, "You feel your connection to [new_human.real_name] severs as they are destroyed.")
- if(ghost)
- to_chat(ghost, "You feel your connection to [new_human.real_name] severs as they are destroyed.")
- break
- if(user.stat || user.health <= 40)
- to_chat(user, "Your body can no longer sustain the connection, and your link to the spirit realm fades.")
- if(ghost)
- to_chat(ghost, "Your body is damaged and your connection to the spirit realm weakens, any ghost you may have manifested are destroyed.")
- break
- user.apply_damage(0.1, BRUTE)
- user.apply_damage(0.1, BURN)
- sleep(2) //Takes two pylons to sustain the damage taken by summoning one ghost
-
- qdel(shield)
- ghosts--
- if(new_human)
- new_human.visible_message("[new_human] suddenly dissolves into bones and ashes.",
- "Your link to the world fades. Your form breaks apart.")
- for(var/obj/item/I in new_human.get_all_slots())
- new_human.drop_item_to_ground(I)
- 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)
- ADD_TRAIT(user, SCRYING, CULT_TRAIT)
- user.visible_message("[user] freezes statue-still, glowing an unearthly red.",
- "You see what lies beyond. All is revealed. In this form you find that your voice booms above all others.")
- ghost = user.ghostize(TRUE, RUNE_COLOR_DARKRED, "Dark Spirit of [user.name]")
- var/datum/action/innate/cult/comm/spirit/CM = new
- var/datum/action/innate/cult/check_progress/V = new
- //var/datum/action/innate/cult/ghostmark/GM = new
- CM.Grant(ghost)
- V.Grant(ghost)
- //GM.Grant(ghost)
- while(!QDELETED(user))
- if(user.key || QDELETED(src))
- user.visible_message("[user] slowly relaxes, the glow around [user.p_them()] dimming.",
- "You are re-united with your physical form. [src] releases its hold over you.")
- user.Weaken(6 SECONDS)
- break
- if(user.health <= 10)
- to_chat(ghost, "Your body can no longer sustain the connection!")
- break
- if(!(user in T))
- user.visible_message("A spectral tendril wraps around [user] and pulls [user.p_them()] back to the rune!")
- Beam(user, icon_state = "drainbeam", time = 2)
- user.forceMove(get_turf(src)) //NO ESCAPE :^)
- sleep(5)
- if(user.grab_ghost())
- CM.Remove(ghost)
- V.Remove(ghost)
- //GM.Remove(ghost)
- REMOVE_TRAIT(user, SCRYING, CULT_TRAIT)
- user.remove_atom_colour(ADMIN_COLOUR_PRIORITY, RUNE_COLOR_DARKRED)
- user = null
- rune_in_use = FALSE
-
-
-//Ritual of Dimensional Rending: Calls forth the avatar of Nar'Sie upon the station.
-/obj/effect/rune/narsie
- cultist_name = "Tear Veil"
- cultist_desc = "tears apart dimensional barriers, calling forth your god."
- invocation = "TOK-LYR RQA-NAP G'OLT-ULOFT!!"
- req_cultists = 9
- icon = 'icons/effects/96x96.dmi'
- icon_state = "rune_large"
- pixel_x = -32 //So the big ol' 96x96 sprite shows up right
- pixel_y = -32
- mouse_opacity = MOUSE_OPACITY_ICON //we're huge and easy to click
- scribe_delay = 45 SECONDS //how long the rune takes to create
- scribe_damage = 10 //how much damage you take doing it
- var/used = FALSE
-
-/obj/effect/rune/narsie/Initialize(mapload)
- . = ..()
- 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
-
-/obj/effect/rune/narsie/is_cleanable() //No, you can't just yeet a cleaning grenade to remove it.
- return FALSE
-
-/obj/effect/rune/narsie/invoke(list/invokers)
- if(used)
- return
- var/mob/living/user = invokers[1]
- 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(SSticker.mode.cult_team.cult_status == NARSIE_HAS_RISEN)
- for(var/M in invokers)
- to_chat(M, "\"I am already here. There is no need to try to summon me now.\"")
- log_game("Summon god rune failed - already summoned")
- return
-
- //BEGIN THE SUMMONING
- SSticker.mode.cult_team.successful_summon()
- used = TRUE
- color = COLOR_RED
- ..()
-
- for(var/mob/M in GLOB.player_list)
- if(!isnewplayer(M)) // exclude people in the lobby
- SEND_SOUND(M, sound('sound/effects/dimensional_rend.ogg'))
- to_chat(M, "The veil... is... TORN!!!--")
-
- icon_state = "rune_large_distorted"
- var/turf/T = get_turf(src)
- sleep(40)
- new /obj/singularity/narsie/large(T) //Causes Nar'Sie to spawn even if the rune has been removed
-
-/obj/effect/rune/narsie/attackby__legacy__attackchain(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) && 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.
- log_game("Summon Narsie rune erased by [key_name(user)] using a null rod")
- message_admins("[key_name_admin(user)] erased a Narsie rune with a null rod")
- return ..()
diff --git a/code/game/gamemodes/miniantags/abduction/abductee_objectives.dm b/code/game/gamemodes/miniantags/abduction/abductee_objectives.dm
deleted file mode 100644
index 6d2273bc087f8..0000000000000
--- a/code/game/gamemodes/miniantags/abduction/abductee_objectives.dm
+++ /dev/null
@@ -1,215 +0,0 @@
-/datum/objective/abductee
- completed = TRUE
- needs_target = FALSE
-
-/datum/objective/abductee/steal
- explanation_text = "Steal all"
-
-/datum/objective/abductee/steal/New()
- ..()
- var/target = pick("pets","lights","monkeys","fruits","shoes","bars of soap", "weapons", "computers", "organs")
- explanation_text +=" [target]."
-
-/datum/objective/abductee/paint
- explanation_text = "The station is hideous. You must color it all"
-
-/datum/objective/abductee/paint/New()
- ..()
- var/color = pick("red", "blue", "green", "yellow", "orange", "purple", "black", "in rainbows", "in blood")
- explanation_text += " [color]!"
-
-/datum/objective/abductee/speech
- explanation_text = "Your brain is broken... you can only communicate in"
-
-/datum/objective/abductee/speech/New()
- ..()
- var/style = pick("pantomime", "rhyme", "haiku", "extended metaphors", "riddles", "extremely literal terms", "sound effects", "military jargon")
- explanation_text += " [style]."
-
-/datum/objective/abductee/capture
- explanation_text = "Capture"
-
-/datum/objective/abductee/capture/New()
- ..()
- var/list/jobs = SSjobs.occupations.Copy()
- for(var/datum/job/J in jobs)
- if(J.current_positions < 1)
- jobs -= J
- if(length(jobs) > 0)
- var/datum/job/target = pick(jobs)
- explanation_text += " a [target.title]."
- else
- explanation_text += " someone."
-
-/datum/objective/abductee/shuttle
- explanation_text = "You must escape the station! Get the shuttle called!"
-
-/datum/objective/abductee/noclone
- explanation_text = "Don't allow anyone to be cloned."
-
-/datum/objective/abductee/oxygen
- explanation_text = "The oxygen is killing them all and they don't even know it. Make sure no oxygen is on the station."
-
-/datum/objective/abductee/blazeit
- explanation_text = "Your body must be improved. Ingest as many drugs as you can."
-
-/datum/objective/abductee/yumyum
- explanation_text = "You are hungry. Eat as much food as you can find."
-
-/datum/objective/abductee/insane
- explanation_text = "You see you see what they cannot you see the open door you seeE you SEeEe you SEe yOU seEee SHOW THEM ALL"
-
-/datum/objective/abductee/cannotmove
- explanation_text = "Convince the crew that you are a paraplegic."
-
-/datum/objective/abductee/deadbodies
- explanation_text = "Start a collection of corpses. Don't kill people to get these corpses."
-
-/datum/objective/abductee/floors
- explanation_text = "Replace all the floor tiles with wood, carpeting, grass or bling."
-
-/datum/objective/abductee/powerunlimited
- explanation_text = "Flood the station's powernet with as much electricity as you can."
-
-/datum/objective/abductee/pristine
- explanation_text = "The CEO of Nanotrasen is coming! Ensure the station is in absolutely pristine condition."
-
-/datum/objective/abductee/nowalls
- explanation_text = "The crew must get to know one another better. Break down the walls inside the station!"
-
-/datum/objective/abductee/nations
- explanation_text = "Ensure your department prospers over all else."
-
-/datum/objective/abductee/abductception
- explanation_text = "You have been changed forever. Find the ones that did this to you and give them a taste of their own medicine."
-
-/datum/objective/abductee/summon
- explanation_text = "The elder gods hunger. Gather a cult and conduct a ritual to summon one."
-
-/datum/objective/abductee/machine
- explanation_text = "You are secretly an android. Interface with as many machines as you can to boost your own power so the AI may acknowledge you at last."
-
-/datum/objective/abductee/calling
- explanation_text = "Call forth a spirit from the other side."
-
-/datum/objective/abductee/calling/New()
- ..()
- var/mob/dead/D = pick(GLOB.dead_mob_list)
- if(D)
- explanation_text = "You know that [D] has perished. Hold a seance to call them from the spirit realm."
-
-/datum/objective/abductee/social_experiment
- explanation_text = "This is a secret social experiment conducted by Nanotrasen. Convince the crew that this is the truth."
-
-/datum/objective/abductee/vr
- explanation_text = "It's all an entirely virtual simulation within an underground vault. Convince the crew to escape the shackles of VR."
-
-/datum/objective/abductee/pets
- explanation_text = "Nanotrasen is abusing the animals! Save as many as you can!"
-
-/datum/objective/abductee/defect
- explanation_text = "Fuck the system! Defect from the station and start an independent colony in space, Mining Outpost or the derelict. Recruit crewmates if you can."
-
-/datum/objective/abductee/promote
- explanation_text = "Climb the corporate ladder all the way to the top!"
-
-/datum/objective/abductee/science
- explanation_text = "So much lies undiscovered. Look deeper into the machinations of the universe."
-
-/datum/objective/abductee/build
- explanation_text = "Expand the station."
-
-/datum/objective/abductee/engine
- explanation_text = "Go have a good conversation with the singularity/tesla/supermatter crystal. Bonus points if it responds."
-
-/datum/objective/abductee/music
- explanation_text = "You burn with passion for music. Share your vision. If anyone hates it, beat them on the head with your instrument!"
-
-/datum/objective/abductee/clown
- explanation_text = "The clown is not funny. You can do better! Steal his audience and make the crew laugh!"
-
-/datum/objective/abductee/party
- explanation_text = "You're throwing a huge rager. Make it as awesome as possible so the whole crew comes... OR ELSE!"
-
-/datum/objective/abductee/cooler_pets
- explanation_text = "All the pets around here suck. You need to make them cooler. Replace them with exotic beasts!"
-
-/datum/objective/abductee/conspiracy
- explanation_text = "The leaders of this station are hiding a grand, evil conspiracy. Only you can learn what it is, and expose it to the people!"
-
-/datum/objective/abductee/stalker
- explanation_text = "The Syndicate has hired you to compile dossiers on all important members of the crew. Be sure they don't know you're doing it."
-
-/datum/objective/abductee/narrator
- explanation_text = "You're the narrator of this tale. Follow around the protagonists to tell their story."
-
-/datum/objective/abductee/lurve
- explanation_text = "You are doomed to feel woefully incomplete forever... until you find your true love on this station. They're waiting for you!"
-
-/datum/objective/abductee/sixthsense
- explanation_text = "You died back there and went to heaven... or is it hell? No one here seems to know they're dead. Convince them, and maybe you can escape this limbo."
-
-/datum/objective/abductee/forbiddennumber
- explanation_text = "Ignore anything in a set number of"
-
-/datum/objective/abductee/forbiddennumber/New()
- ..()
- var/number = pick("two", "three", "four", "five", "six", "seven", "eight", "nine", "ten")
- explanation_text +=" [number], they don't exist."
-
-/datum/objective/abductee/buddy
- explanation_text = "Being alone and in large groups are both frightening. Try to be alone with only one other person whenever possible."
-
-/datum/objective/abductee/finality
- explanation_text = "Death should be final and modern medicine disrupts the natural order. Don't allow anyone to be revived."
-
-/datum/objective/abductee/mispronounced
- explanation_text = "No matter how they say it, other people keep mispronouncing your name. Be sure to correct them whenever possible."
-
-/datum/objective/abductee/bald
- explanation_text = "There are alien parasites masquerading as people's hair. Save people from this invasion."
-
-/datum/objective/abductee/one
- explanation_text = "There is only one other person in existence, they are just really good at pretending to be multiple people."
-
-/datum/objective/abductee/outlaw
- explanation_text = "You have infiltrated this station as a space-renowned outlaw, commit as many minor crimes as possible while remaining unnoticed by security."
-
-/datum/objective/abductee/rot
- explanation_text = "Your flesh is rotting from your body. Fight the inevitable, and replace your tainted limbs with entirely new ones."
-
-/datum/objective/abductee/blind
- explanation_text = "You are blind. Perhaps a new pair of eyes can help."
-
-/datum/objective/abductee/ill
- explanation_text = "The station is ill. Medicate them until they're cured."
-
-/datum/objective/abductee/game
- explanation_text = "Convince the crew that we are in a game, without explicitly telling them we are in a game."
-
-/datum/objective/abductee/instructor
- explanation_text = "You are a military instructor. You must make sure the crew is in top shape for the war against the syndicate!"
-
-/datum/objective/abductee/actor
- explanation_text = "You are in an action movie. You must say as many cheesy one-liners as possible."
-
-/datum/objective/abductee/writer
- explanation_text = "You are a writer. Convince the crew of your superb writing skills!"
-
-/datum/objective/abductee/doomed
- explanation_text = "You know something bad is about to happen to this station. Convince the crew to get off of it while they still can!"
-
-/datum/objective/abductee/proof
- explanation_text = "You think you were kidnapped by aliens! Ensure that"
-
-/datum/objective/abductee/proof/New()
- ..()
- var/list/jobs = SSjobs.occupations.Copy()
- for(var/datum/job/J in jobs)
- if(J.current_positions < 1)
- jobs -= J
- if(length(jobs))
- var/datum/job/target = pick(jobs)
- explanation_text += " a [target.title] is also experimented on by an Abductor so they believe you!"
- else
- explanation_text += " someone is also experimented on by an Abductor so they believe you!"
diff --git a/code/game/gamemodes/miniantags/abduction/abduction.dm b/code/game/gamemodes/miniantags/abduction/abduction.dm
deleted file mode 100644
index 7052199a26ce9..0000000000000
--- a/code/game/gamemodes/miniantags/abduction/abduction.dm
+++ /dev/null
@@ -1,136 +0,0 @@
-/datum/game_mode/abduction
- name = "abduction"
- config_tag = "abduction"
- recommended_enemies = 2
- required_players = 15
- abductor_teams = 1
- single_antag_positions = list()
- var/max_teams = 4
- var/finished = FALSE
-
-/datum/game_mode/abduction/announce()
- to_chat(world, "The current game mode is - Abduction!")
- to_chat(world, "There are alien abductors sent to [world.name] to perform nefarious experiments!")
- to_chat(world, "Abductors - kidnap the crew and replace their organs with experimental ones.")
- to_chat(world, "Crew - don't get abducted and stop the abductors.")
-
-/datum/game_mode/abduction/pre_setup()
- var/possible_abductors = get_players_for_role(ROLE_ABDUCTOR)
- if(!length(possible_abductors))
- return FALSE
-
- abductor_teams = clamp(min(round(num_players() / 15), round(length(possible_abductors) / 2)), 1, max_teams)
-
- for(var/i in 1 to abductor_teams)
- var/datum/mind/mind_1 = pick_n_take(possible_abductors)
- var/datum/mind/mind_2 = pick_n_take(possible_abductors)
- if(!mind_1 || !mind_2)
- break
- new /datum/team/abductor(list(mind_1, mind_2))
-
- // Add a special role so they dont pick up any other antagonist stuff
- mind_1.assigned_role = SPECIAL_ROLE_ABDUCTOR_AGENT
- mind_1.special_role = SPECIAL_ROLE_ABDUCTOR_AGENT
- mind_1.offstation_role = TRUE
-
- mind_2.assigned_role = SPECIAL_ROLE_ABDUCTOR_SCIENTIST
- mind_2.special_role = SPECIAL_ROLE_ABDUCTOR_SCIENTIST
- mind_2.offstation_role = TRUE
- ..()
- return TRUE
-
-/datum/game_mode/abduction/post_setup()
- for(var/datum/team/abductor/team in actual_abductor_teams)
- team.create_agent()
- team.create_scientist()
- return ..()
-
-/datum/game_mode/abduction/proc/get_team_console(team_number)
- for(var/obj/machinery/abductor/console/C in GLOB.machines)
- if(C.team == team_number)
- return C
-
-/datum/game_mode/abduction/check_finished()
- if(!finished)
- for(var/datum/team/abductor/team in actual_abductor_teams)
- var/obj/machinery/abductor/console/con = get_team_console(team.team_number)
- if(con.experiment.points >= team.experiment_objective.target_amount)
- SSshuttle.emergency.request(null, 0.5, reason = "Large amount of abnormal thought patterns detected. All crew are recalled for mandatory evaluation and reconditioning.")
- SSshuttle.emergency.canRecall = FALSE
- finished = TRUE
- return ..()
- return ..()
-
-/datum/game_mode/abduction/declare_completion()
- for(var/datum/team/abductor/team in actual_abductor_teams)
- var/obj/machinery/abductor/console/console = get_team_console(team.team_number)
- if(console.experiment.points >= team.experiment_objective.target_amount)
- to_chat(world, "[team.name] team fulfilled its mission!")
- else
- to_chat(world, "[team.name] team failed its mission.")
- ..()
- return 1
-
-/datum/game_mode/proc/auto_declare_completion_abduction()
- var/list/text = list()
- if(length(abductors))
- text += " The abductors were: "
- for(var/datum/mind/abductor_mind in abductors)
- text += printplayer(abductor_mind)
- text += " "
- text += printobjectives(abductor_mind)
- text += " "
- if(length(abductees))
- text += " The abductees were: "
- for(var/datum/mind/abductee_mind in abductees)
- text += printplayer(abductee_mind)
- text += " "
- text += printobjectives(abductee_mind)
- text += " "
- return text.Join("")
-
-//Landmarks
-// TODO: Split into seperate landmarks for prettier ships
-/obj/effect/landmark/abductor
- icon = 'icons/effects/spawner_icons.dmi'
- icon_state = "Abductor"
- var/team = 1
-
-/obj/effect/landmark/abductor/agent
-/obj/effect/landmark/abductor/scientist
-
-
-// OBJECTIVES
-//No check completion, it defaults to being completed unless an admin sets it to failed.
-/datum/objective/stay_hidden
- explanation_text = "Limit contact with your targets outside of conducting your experiments and abduction."
- completed = TRUE
- needs_target = FALSE
-
-/datum/objective/experiment
- explanation_text = "Experiment on some humans."
- target_amount = 6
- needs_target = FALSE
- /// Which abductor team number does this belong to.
- var/abductor_team_number
-
-/datum/objective/experiment/New()
- ..()
- explanation_text = "Experiment on [target_amount] humans."
-
-/datum/objective/experiment/check_completion()
- var/ab_team = abductor_team_number
- var/list/owners = get_owners()
- for(var/datum/mind/M in owners)
- if(!M.current || !ishuman(M.current) || !isabductor(M.current))
- return FALSE
- var/mob/living/carbon/human/H = M.current
- var/datum/species/abductor/S = H.dna.species
- ab_team = S.team
- for(var/obj/machinery/abductor/experiment/E in GLOB.machines)
- if(E.team == ab_team)
- if(E.points >= target_amount)
- return TRUE
- else
- return FALSE
- return FALSE
diff --git a/code/game/gamemodes/miniantags/abduction/abduction_gear.dm b/code/game/gamemodes/miniantags/abduction/abduction_gear.dm
deleted file mode 100644
index 02bc96a04065a..0000000000000
--- a/code/game/gamemodes/miniantags/abduction/abduction_gear.dm
+++ /dev/null
@@ -1,930 +0,0 @@
-#define GIZMO_SCAN 1
-#define GIZMO_MARK 2
-#define MIND_DEVICE_MESSAGE 1
-#define MIND_DEVICE_CONTROL 2
-
-#define BATON_STUN 0
-#define BATON_SLEEP 1
-#define BATON_CUFF 2
-#define BATON_PROBE 3
-#define BATON_MODES 4
-
-/*
-CONTENTS:
-1. AGENT GEAR
-2. SCIENTIST GEAR
-3. ENGINEERING TOOLS
-4. MEDICAL TOOLS
-5. JANITORIAL TOOLS
-6. STRUCTURES
-*/
-
-// Setting up abductor exclusivity.
-/obj/item/abductor
- name = "generic abductor item"
- icon = 'icons/obj/abductor.dmi'
- desc = "You are not supposed to be able to see this. If you can see this, please make an issue report on GitHub."
-
-/obj/item/abductor/proc/AbductorCheck(user)
- if(isabductor(user))
- return TRUE
- to_chat(user, "You can't figure how this works!")
- return FALSE
-
-/obj/item/abductor/proc/ScientistCheck(user)
- if(!AbductorCheck(user))
- return FALSE
-
- var/mob/living/carbon/human/H = user
- var/datum/species/abductor/S = H.dna.species
- if(S.scientist)
- return TRUE
- to_chat(user, "You're not trained to use this!")
- return FALSE
-
-/////////////////////////////////////////
-/////////////// AGENT GEAR //////////////
-/////////////////////////////////////////
-/obj/item/clothing/head/helmet/abductor
- name = "agent headgear"
- desc = "Abduct with style - spiky style. Prevents digital tracking."
- icon_state = "alienhelmet"
- item_state = "alienhelmet"
- blockTracking = 1
- origin_tech = "materials=7;magnets=4;abductor=3"
- flags = BLOCKHAIR
- flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE
-
- sprite_sheets = list(
- "Vox" = 'icons/mob/clothing/species/vox/head.dmi'
- )
-
-/obj/item/clothing/suit/armor/abductor/vest
- name = "agent vest"
- desc = "A vest outfitted with advanced stealth technology. It has two modes - combat and stealth."
- icon = 'icons/obj/abductor.dmi'
- icon_state = "vest_stealth"
- item_state = "armor"
- blood_overlay_type = "armor"
- origin_tech = "magnets=7;biotech=4;powerstorage=4;abductor=4"
- armor = list(MELEE = 10, BULLET = 10, LASER = 10, ENERGY = 10, BOMB = 10, RAD = 10, FIRE = 115, ACID = 115)
- actions_types = list(/datum/action/item_action/hands_free/activate)
- allowed = list(/obj/item/abductor, /obj/item/abductor_baton, /obj/item/melee/baton, /obj/item/gun/energy, /obj/item/restraints/handcuffs)
- var/mode = ABDUCTOR_VEST_STEALTH
- var/stealth_active = 0
- var/combat_cooldown = 10
- var/datum/icon_snapshot/disguise
- var/stealth_armor = list(MELEE = 10, BULLET = 10, LASER = 10, ENERGY = 10, BOMB = 10, RAD = 10, FIRE = 115, ACID = 115)
- var/combat_armor = list(MELEE = 50, BULLET = 50, LASER = 50, ENERGY = 50, BOMB = 50, RAD = 50, FIRE = 450, ACID = 450)
- sprite_sheets = null
-
-/obj/item/clothing/suit/armor/abductor/vest/Initialize(mapload)
- . = ..()
- stealth_armor = getArmor(arglist(stealth_armor))
- combat_armor = getArmor(arglist(combat_armor))
-
-/obj/item/clothing/suit/armor/abductor/vest/proc/toggle_nodrop()
- flags ^= NODROP
- if(ismob(loc))
- to_chat(loc, "Your vest is now [flags & NODROP ? "locked" : "unlocked"].")
-
-/obj/item/clothing/suit/armor/abductor/vest/proc/flip_mode()
- switch(mode)
- if(ABDUCTOR_VEST_STEALTH)
- mode = ABDUCTOR_VEST_COMBAT
- DeactivateStealth()
- armor = combat_armor
- icon_state = "vest_combat"
- if(ABDUCTOR_VEST_COMBAT)// TO STEALTH
- mode = ABDUCTOR_VEST_STEALTH
- armor = stealth_armor
- icon_state = "vest_stealth"
- if(ishuman(loc))
- var/mob/living/carbon/human/H = loc
- H.update_inv_wear_suit()
- for(var/X in actions)
- var/datum/action/A = X
- A.UpdateButtons()
-
-/obj/item/clothing/suit/armor/abductor/vest/item_action_slot_check(slot, mob/user)
- if(slot == ITEM_SLOT_OUTER_SUIT) //we only give the mob the ability to activate the vest if he's actually wearing it.
- return 1
-
-/obj/item/clothing/suit/armor/abductor/vest/proc/SetDisguise(datum/icon_snapshot/entry)
- disguise = entry
-
-/obj/item/clothing/suit/armor/abductor/vest/proc/ActivateStealth()
- if(disguise == null)
- return
- stealth_active = 1
- if(ishuman(loc))
- var/mob/living/carbon/human/M = loc
- new /obj/effect/temp_visual/dir_setting/ninja/cloak(get_turf(M), M.dir)
- M.name_override = disguise.name
- M.icon = disguise.icon
- M.icon_state = disguise.icon_state
- M.overlays = disguise.overlays
- M.update_inv_r_hand()
- M.update_inv_l_hand()
- SEND_SIGNAL(M, COMSIG_CARBON_REGENERATE_ICONS)
-
-/obj/item/clothing/suit/armor/abductor/vest/proc/DeactivateStealth()
- if(!stealth_active)
- return
- stealth_active = 0
- if(ishuman(loc))
- var/mob/living/carbon/human/M = loc
- new /obj/effect/temp_visual/dir_setting/ninja(get_turf(M), M.dir)
- M.name_override = null
- M.overlays.Cut()
- M.regenerate_icons()
-
-/obj/item/clothing/suit/armor/abductor/vest/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK)
- DeactivateStealth()
-
-/obj/item/clothing/suit/armor/abductor/vest/IsReflect()
- DeactivateStealth()
- return 0
-
-/obj/item/clothing/suit/armor/abductor/vest/ui_action_click()
- switch(mode)
- if(ABDUCTOR_VEST_COMBAT)
- Adrenaline()
- if(ABDUCTOR_VEST_STEALTH)
- if(stealth_active)
- DeactivateStealth()
- else
- ActivateStealth()
-
-/obj/item/clothing/suit/armor/abductor/vest/proc/Adrenaline()
- if(ishuman(loc))
- if(combat_cooldown != initial(combat_cooldown))
- to_chat(loc, "Combat injection is still recharging.")
- return
- var/mob/living/carbon/human/M = loc
- M.adjustStaminaLoss(-75)
- M.SetParalysis(0)
- M.SetStunned(0)
- M.SetWeakened(0)
- M.SetKnockDown(0)
- M.stand_up(TRUE)
- combat_cooldown = 0
- START_PROCESSING(SSobj, src)
-
-/obj/item/clothing/suit/armor/abductor/vest/process()
- combat_cooldown++
- if(combat_cooldown==initial(combat_cooldown))
- STOP_PROCESSING(SSobj, src)
-
-/obj/item/clothing/suit/armor/abductor/Destroy()
- STOP_PROCESSING(SSobj, src)
- for(var/obj/machinery/abductor/console/C in GLOB.machines)
- if(C.vest == src)
- C.vest = null
- break
- return ..()
-
-/obj/item/abductor/silencer
- name = "abductor silencer"
- desc = "A compact device used to shut down communications equipment."
- icon_state = "silencer"
- item_state = "silencer"
- origin_tech = "materials=4;programming=7;abductor=3"
-
-/obj/item/abductor/silencer/attack__legacy__attackchain(mob/living/M, mob/user)
- if(!AbductorCheck(user))
- return
- radio_off(M, user)
-
-/obj/item/abductor/silencer/afterattack__legacy__attackchain(atom/target, mob/living/user, flag, params)
- if(flag)
- return
- if(!AbductorCheck(user))
- return
- radio_off(target, user)
-
-/obj/item/abductor/silencer/proc/radio_off(atom/target, mob/living/user)
- if(!(user in (viewers(7, target))))
- return
-
- var/turf/targloc = get_turf(target)
-
- var/mob/living/carbon/human/M
- for(M in view(2,targloc))
- if(M == user)
- continue
- to_chat(user, "You silence [M]'s radio devices.")
- radio_off_mob(M)
-
-/obj/item/abductor/silencer/proc/radio_off_mob(mob/living/carbon/human/M)
- var/list/all_items = M.GetAllContents()
-
- for(var/obj/I in all_items)
- if(isradio(I))
- var/obj/item/radio/R = I
- R.listening = FALSE // Prevents the radio from buzzing due to the EMP, preserving possible stealthiness.
- R.emp_act(EMP_HEAVY)
-
-/obj/item/gun/energy/alien
- name = "alien pistol"
- desc = "A complicated gun that fires bursts of high-intensity radiation."
- ammo_type = list(/obj/item/ammo_casing/energy/declone)
- restricted_species = list(/datum/species/abductor)
- icon_state = "alienpistol"
- item_state = "alienpistol"
- origin_tech = "combat=4;magnets=7;powerstorage=3;abductor=3"
- trigger_guard = TRIGGER_GUARD_ALLOW_ALL
- can_holster = TRUE
-
-/obj/item/abductor_baton
- name = "advanced baton"
- desc = "A quad-mode baton used for incapacitation and restraining of specimens."
- var/mode = BATON_STUN
- icon = 'icons/obj/abductor.dmi'
- lefthand_file = 'icons/mob/inhands/weapons_lefthand.dmi'
- righthand_file = 'icons/mob/inhands/weapons_righthand.dmi'
- icon_state = "wonderprodStun"
- item_state = "wonderprod"
- slot_flags = ITEM_SLOT_BELT
- origin_tech = "materials=4;combat=4;biotech=7;abductor=4"
- w_class = WEIGHT_CLASS_NORMAL
- actions_types = list(/datum/action/item_action/toggle_mode)
-
-/obj/item/abductor_baton/proc/toggle(mob/living/user = usr)
- mode = (mode+1)%BATON_MODES
- var/txt
- switch(mode)
- if(BATON_STUN)
- txt = "stunning"
- if(BATON_SLEEP)
- txt = "sleep inducement"
- if(BATON_CUFF)
- txt = "restraining"
- if(BATON_PROBE)
- txt = "probing"
-
- to_chat(usr, "You switch the baton to [txt] mode.")
- update_icon(UPDATE_ICON_STATE)
- for(var/X in actions)
- var/datum/action/A = X
- A.UpdateButtons()
-
-/obj/item/abductor_baton/update_icon_state()
- switch(mode)
- if(BATON_STUN)
- icon_state = "wonderprodStun"
- item_state = "wonderprodStun"
- if(BATON_SLEEP)
- icon_state = "wonderprodSleep"
- item_state = "wonderprodSleep"
- if(BATON_CUFF)
- icon_state = "wonderprodCuff"
- item_state = "wonderprodCuff"
- if(BATON_PROBE)
- icon_state = "wonderprodProbe"
- item_state = "wonderprodProbe"
-
-/obj/item/abductor_baton/attack__legacy__attackchain(mob/target, mob/living/user)
- if(!isabductor(user))
- return
-
-
- if(!isliving(target))
- return
-
- var/mob/living/L = target
-
- user.do_attack_animation(L)
-
- if(isrobot(L))
- L.apply_damage(80, STAMINA) //Force a reboot on two hits for consistency.
- return
-
- if(ishuman(L))
- var/mob/living/carbon/human/H = L
- if(H.check_shields(src, 0, "[user]'s [name]", MELEE_ATTACK))
- playsound(L, 'sound/weapons/genhit.ogg', 50, 1)
- return 0
-
- switch(mode)
- if(BATON_STUN)
- StunAttack(L,user)
- if(BATON_SLEEP)
- SleepAttack(L,user)
- if(BATON_CUFF)
- CuffAttack(L,user)
- if(BATON_PROBE)
- ProbeAttack(L,user)
-
-/obj/item/abductor_baton/attack_self__legacy__attackchain(mob/living/user)
- toggle(user)
- if(ishuman(user))
- var/mob/living/carbon/human/H = user
- H.update_inv_l_hand()
- H.update_inv_r_hand()
-
-/obj/item/abductor_baton/proc/StunAttack(mob/living/L,mob/living/user)
- L.lastattacker = user.real_name
- L.lastattackerckey = user.ckey
-
- L.KnockDown(7 SECONDS)
- L.apply_damage(80, STAMINA)
- L.Stuttering(14 SECONDS)
-
- L.visible_message("[user] has stunned [L] with [src]!", \
- "[user] has stunned you with [src]!")
- playsound(loc, 'sound/weapons/egloves.ogg', 50, TRUE, -1)
-
- add_attack_logs(user, L, "Stunned with [src]")
-
-/obj/item/abductor_baton/proc/SleepAttack(mob/living/L, mob/living/user)
- var/mob/living/carbon/C = L
- if(!iscarbon(L))
- return
- if((C.getStaminaLoss() < 100) && !C.IsSleeping())
- C.AdjustDrowsy(2 SECONDS)
- to_chat(user, "Sleep inducement works fully only on stunned or asleep specimens!")
- C.visible_message("[user] tried to induce sleep in [L] with [src]!", \
- "You suddenly feel drowsy!")
- return
- if(do_mob(user, C, 2.5 SECONDS))
- C.visible_message("[user] has induced sleep in [L] with [src]!", \
- "You suddenly feel very drowsy!")
- playsound(loc, 'sound/weapons/egloves.ogg', 50, TRUE, -1)
- C.Sleeping(120 SECONDS)
- add_attack_logs(user, C, "Put to sleep with [src]")
-
-/obj/item/abductor_baton/proc/CuffAttack(mob/living/L,mob/living/user)
- if(!iscarbon(L))
- return
- var/mob/living/carbon/C = L
- if(!C.handcuffed)
- playsound(loc, 'sound/weapons/cablecuff.ogg', 30, TRUE, -2)
- C.visible_message("[user] begins restraining [C] with [src]!", \
- "[user] begins shaping an energy field around your hands!")
- if(do_mob(user, C, 3 SECONDS))
- if(!C.handcuffed)
- C.handcuffed = new /obj/item/restraints/handcuffs/energy(C)
- C.update_handcuffed()
- to_chat(user, "You handcuff [C].")
- add_attack_logs(user, C, "Handcuffed ([src])")
- else
- to_chat(user, "You fail to handcuff [C].")
-
-/obj/item/abductor_baton/proc/ProbeAttack(mob/living/L,mob/living/user)
- L.visible_message("[user] probes [L] with [src]!", \
- "[user] probes you!")
-
- var/species = "Unknown species"
- var/helptext = "Species unsuitable for experiments."
-
- if(ishuman(L))
- var/mob/living/carbon/human/H = L
- species = "[H.dna.species.name]"
- if(IS_CHANGELING(L))
- species = "Changeling lifeform"
- var/obj/item/organ/internal/heart/gland/temp = locate() in H.internal_organs
- if(temp)
- helptext = "Experimental gland detected!"
- else
- helptext = "Subject suitable for experiments."
-
- to_chat(user,"Probing result: [species]")
- to_chat(user, "[helptext]")
-
-/obj/item/restraints/handcuffs/energy
- name = "hard-light energy field"
- desc = "A hard-light field restraining the hands."
- icon_state = "cablecuff" // Needs sprite
- breakouttime = 450
- origin_tech = "materials=4;magnets=5;abductor=2"
- flags = DROPDEL
-
-/obj/item/restraints/handcuffs/energy/finish_resist_restraints(mob/living/carbon/user, break_cuffs, silent)
- user.visible_message("[src] restraining [user] breaks in a discharge of energy!", "[src] restraining [user] breaks in a discharge of energy!")
- break_cuffs = TRUE
- silent = TRUE
- do_sparks(4, 0, user.loc)
- . = ..()
-
-/obj/item/abductor_baton/examine(mob/user)
- . = ..()
- switch(mode)
- if(BATON_STUN)
- . += "The baton is in stun mode."
- if(BATON_SLEEP)
- . += "The baton is in sleep inducement mode."
- if(BATON_CUFF)
- . += "The baton is in restraining mode."
- if(BATON_PROBE)
- . += "The baton is in probing mode."
-
-/obj/item/radio/headset/abductor
- name = "alien headset"
- desc = "An advanced alien headset designed to monitor communications of human space stations. Why does it have a microphone? No one knows."
- flags = EARBANGPROTECT
- origin_tech = "magnets=2;abductor=3"
- icon = 'icons/obj/abductor.dmi'
- icon_state = "abductor_headset"
- item_state = "abductor_headset"
- ks2type = /obj/item/encryptionkey/heads/captain
-
-/obj/item/radio/headset/abductor/Initialize(mapload)
- . = ..()
- make_syndie() // Why the hell is this a proc why cant it just be a subtype
-
-/obj/item/radio/headset/abductor/screwdriver_act()
- return // Stops humans from disassembling abductor headsets.
-
-/////////////////////////////////////////
-///////////// SCIENTIST GEAR ////////////
-/////////////////////////////////////////
-/obj/item/abductor/gizmo
- name = "science tool"
- desc = "A dual-mode tool for retrieving specimens and scanning appearances. Scanning can be done through cameras."
- icon_state = "gizmo_scan"
- item_state = "gizmo"
- origin_tech = "engineering=7;magnets=4;bluespace=4;abductor=3"
- var/mode = GIZMO_SCAN
- var/mob/living/marked = null
- var/obj/machinery/abductor/console/console
-
-/obj/item/abductor/gizmo/attack_self__legacy__attackchain(mob/user)
- if(!ScientistCheck(user))
- return
- if(!console)
- to_chat(user, "The device is not linked to a console!")
- return
-
- if(mode == GIZMO_SCAN)
- mode = GIZMO_MARK
- icon_state = "gizmo_mark"
- else
- mode = GIZMO_SCAN
- icon_state = "gizmo_scan"
- to_chat(user, "You switch the device to [mode==GIZMO_SCAN? "SCAN": "MARK"] MODE")
-
-/obj/item/abductor/gizmo/attack__legacy__attackchain(mob/living/M, mob/user)
- if(!ScientistCheck(user))
- return
- if(!console)
- to_chat(user, "The device is not linked to console!")
- return
-
- switch(mode)
- if(GIZMO_SCAN)
- scan(M, user)
- if(GIZMO_MARK)
- mark(M, user)
-
-/obj/item/abductor/gizmo/afterattack__legacy__attackchain(atom/target, mob/living/user, flag, params)
- if(flag)
- return
- if(!ScientistCheck(user))
- return
- if(!console)
- to_chat(user, "The device is not linked to console!")
- return
-
- switch(mode)
- if(GIZMO_SCAN)
- scan(target, user)
- if(GIZMO_MARK)
- mark(target, user)
-
-/obj/item/abductor/gizmo/proc/scan(atom/target, mob/living/user)
- if(ishuman(target))
- console.AddSnapshot(target)
- to_chat(user, "You scan [target] and add [target.p_them()] to the database.")
-
-/obj/item/abductor/gizmo/proc/mark(atom/target, mob/living/user)
- if(marked == target)
- to_chat(user, "This specimen is already marked!")
- return
- if(ishuman(target))
- if(isabductor(target))
- marked = target
- to_chat(user, "You mark [target] for future retrieval.")
- else
- prepare(target,user)
- else
- prepare(target,user)
-
-/obj/item/abductor/gizmo/proc/prepare(atom/target, mob/living/user)
- if(get_dist(target,user)>1)
- to_chat(user, "You need to be next to the specimen to prepare it for transport!")
- return
- to_chat(user, "You begin preparing [target] for transport...")
- if(do_after(user, 100, target = target))
- marked = target
- to_chat(user, "You finish preparing [target] for transport.")
-
-/obj/item/abductor/gizmo/Destroy()
- if(console)
- console.gizmo = null
- return ..()
-
-/obj/item/abductor/mind_device
- name = "mental interface device"
- desc = "A dual-mode tool for directly communicating with sentient brains. It can be used to send a direct message to a target, or to send a command to a test subject with a charged gland."
- icon_state = "mind_device_message"
- item_state = "silencer"
- var/mode = MIND_DEVICE_MESSAGE
-
-/obj/item/abductor/mind_device/attack_self__legacy__attackchain(mob/user)
- if(!ScientistCheck(user))
- return
-
- if(mode == MIND_DEVICE_MESSAGE)
- mode = MIND_DEVICE_CONTROL
- icon_state = "mind_device_control"
- else
- mode = MIND_DEVICE_MESSAGE
- icon_state = "mind_device_message"
- to_chat(user, "You switch the device to [mode == MIND_DEVICE_MESSAGE ? "TRANSMISSION" : "COMMAND"] MODE")
-
-/obj/item/abductor/mind_device/afterattack__legacy__attackchain(atom/target, mob/living/user, flag, params)
- if(!ScientistCheck(user))
- return
-
- switch(mode)
- if(MIND_DEVICE_CONTROL)
- mind_control(target, user)
- if(MIND_DEVICE_MESSAGE)
- mind_message(target, user)
-
-/obj/item/abductor/mind_device/proc/mind_control(atom/target, mob/living/user)
- if(iscarbon(target))
- var/mob/living/carbon/C = target
- var/obj/item/organ/internal/heart/gland/G = C.get_organ_slot("heart")
- if(!istype(G))
- to_chat(user, "Your target does not have an experimental gland!")
- return
- if(!G.mind_control_uses)
- to_chat(user, "Your target's gland is spent!")
- return
- if(G.active_mind_control)
- to_chat(user, "Your target is already under a mind-controlling influence!")
- return
-
- var/command = tgui_input_text(user, "Enter the command for your target to follow. Uses Left: [G.mind_control_uses], Duration: [DisplayTimeText(G.mind_control_duration)]", "Enter command")
- if(!command)
- return
- if(QDELETED(user) || user.get_active_hand() != src || loc != user)
- return
- if(QDELETED(G))
- return
- G.mind_control(command, user)
- to_chat(user, "You send the command to your target.")
-
-/obj/item/abductor/mind_device/proc/mind_message(atom/target, mob/living/user)
- if(isliving(target))
- var/mob/living/L = target
- if(L.stat == DEAD)
- to_chat(user, "Your target is dead!")
- return
- var/message = tgui_input_text(user, "Write a message to send to your target's brain.", "Enter message")
- if(!message)
- return
- if(QDELETED(L) || L.stat == DEAD)
- return
-
- to_chat(L, "You hear a voice in your head saying: [message]")
- to_chat(user, "You send the message to your target.")
- log_say("[key_name(user)] sent an abductor mind message to [key_name(L)]: '[message]'", user)
-
-/obj/item/paper/abductor
- name = "Dissection Guide"
- icon_state = "alienpaper_words"
- info = {"Dissection for Dummies
-
- 1.Acquire fresh specimen.
- 2.Put the specimen on operating table.
- 3.Apply scalpel to the chest, preparing for experimental dissection.
- 4.Apply scalpel to specimen's torso.
- 5.Clamp bleeders on specimen's torso with a hemostat.
- 6.Retract skin of specimen's torso with a retractor.
- 7.Saw through the specimen's torso with a saw.
- 8.Apply retractor again to specimen's torso.
- 9.Search through the specimen's torso with your hands to remove any superfluous organs.
- 10.Insert replacement gland (Retrieve one from gland storage).
- 11.Cauterize the patient's torso with a cautery.
- 12.Consider dressing the specimen back to not disturb the habitat.
- 13.Put the specimen in the experiment machinery.
- 14.Choose one of the machine options. The target will be analyzed and teleported to the selected drop-off point.
- 15.You will receive one supply credit, and the subject will be counted towards your quota.
-
-Congratulations! You are now trained for invasive xenobiology research!"}
-
-/obj/item/paper/abductor/update_icon_state()
- return
-
-/obj/item/paper/abductor/AltClick()
- return
-
-/////////////////////////////////////////
-/////////// ENGINEERING TOOLS ///////////
-/////////////////////////////////////////
-/obj/item/screwdriver/abductor
- name = "alien screwdriver"
- desc = "An ultrasonic screwdriver."
- icon = 'icons/obj/abductor.dmi'
- icon_state = "screwdriver"
- belt_icon = null
- usesound = 'sound/items/pshoom.ogg'
- toolspeed = 0.1
- random_color = FALSE
-
-/obj/item/wrench/abductor
- name = "alien wrench"
- desc = "A polarized wrench. It causes anything placed between the jaws to turn."
- icon = 'icons/obj/abductor.dmi'
- icon_state = "wrench"
- belt_icon = null
- usesound = 'sound/effects/empulse.ogg'
- toolspeed = 0.1
- origin_tech = "materials=5;engineering=5;abductor=3"
-
-/obj/item/weldingtool/abductor
- name = "alien welding tool"
- desc = "An alien welding tool. Whatever fuel it uses, it never runs out."
- icon = 'icons/obj/abductor.dmi'
- icon_state = "welder"
- belt_icon = null
- toolspeed = 0.1
- w_class = WEIGHT_CLASS_SMALL
- light_intensity = 0
- origin_tech = "plasmatech=5;engineering=5;abductor=3"
- requires_fuel = FALSE
- refills_over_time = TRUE
- low_fuel_changes_icon = FALSE
-
-/obj/item/crowbar/abductor
- name = "alien crowbar"
- desc = "A hard-light crowbar. It appears to pry by itself, without any effort required."
- icon = 'icons/obj/abductor.dmi'
- icon_state = "crowbar"
- belt_icon = null
- usesound = 'sound/weapons/sonic_jackhammer.ogg'
- toolspeed = 0.1
- w_class = WEIGHT_CLASS_SMALL
- origin_tech = "combat=4;engineering=4;abductor=3"
-
-/obj/item/wirecutters/abductor
- name = "alien wirecutters"
- desc = "Extremely sharp wirecutters, made out of a silvery-green metal."
- icon = 'icons/obj/abductor.dmi'
- icon_state = "cutters"
- belt_icon = null
- toolspeed = 0.1
- origin_tech = "materials=5;engineering=4;abductor=3"
- random_color = FALSE
-
-/obj/item/wirecutters/abductor/Initialize(mapload)
- . = ..()
- ADD_TRAIT(src, TRAIT_SHOW_WIRE_INFO, ROUNDSTART_TRAIT)
-
-/obj/item/multitool/abductor
- name = "alien multitool"
- desc = "An omni-technological interface."
- icon = 'icons/obj/abductor.dmi'
- icon_state = "multitool"
- belt_icon = null
- toolspeed = 0.1
- w_class = WEIGHT_CLASS_SMALL
- origin_tech = "magnets=5;engineering=5;abductor=3"
-
-/obj/item/multitool/abductor/Initialize(mapload)
- . = ..()
- ADD_TRAIT(src, TRAIT_SHOW_WIRE_INFO, ROUNDSTART_TRAIT)
-
-/obj/item/storage/belt/military/abductor
- name = "agent belt"
- desc = "A belt used by abductor agents."
- icon = 'icons/obj/abductor.dmi'
- icon_state = "belt"
- item_state = "security"
-
-/obj/item/storage/belt/military/abductor/full/populate_contents()
- new /obj/item/screwdriver/abductor(src)
- new /obj/item/wrench/abductor(src)
- new /obj/item/weldingtool/abductor(src)
- new /obj/item/crowbar/abductor(src)
- new /obj/item/wirecutters/abductor(src)
- new /obj/item/multitool/abductor(src)
- new /obj/item/stack/cable_coil(src, 30, COLOR_WHITE)
-
-/////////////////////////////////////////
-/////////// MEDICAL TOOLS ///////////////
-/////////////////////////////////////////
-/obj/item/scalpel/alien
- name = "alien scalpel"
- desc = "It's a gleaming sharp knife made out of silvery-green metal."
- icon = 'icons/obj/abductor.dmi'
- origin_tech = "materials=2;biotech=2;abductor=2"
- toolspeed = 0.25
-
-/obj/item/hemostat/alien
- name = "alien hemostat"
- desc = "You've never seen this before."
- icon = 'icons/obj/abductor.dmi'
- origin_tech = "materials=2;biotech=2;abductor=2"
- toolspeed = 0.25
-
-/obj/item/retractor/alien
- name = "alien retractor"
- desc = "You're not sure if you want the veil pulled back."
- icon = 'icons/obj/abductor.dmi'
- origin_tech = "materials=2;biotech=2;abductor=2"
- toolspeed = 0.25
-
-/obj/item/circular_saw/alien
- name = "alien saw"
- desc = "Do the aliens also lose this, and need to find an alien hatchet?"
- icon = 'icons/obj/abductor.dmi'
- origin_tech = "materials=2;biotech=2;abductor=2"
- toolspeed = 0.25
-
-/obj/item/surgicaldrill/alien
- name = "alien drill"
- desc = "Maybe alien surgeons have finally found a use for the drill."
- icon = 'icons/obj/abductor.dmi'
- origin_tech = "materials=2;biotech=2;abductor=2"
- toolspeed = 0.25
-
-/obj/item/cautery/alien
- name = "alien cautery"
- desc = "Why would bloodless aliens have a tool to stop bleeding? Unless..."
- icon = 'icons/obj/abductor.dmi'
- origin_tech = "materials=2;biotech=2;abductor=2"
- toolspeed = 0.25
-
-/obj/item/bonegel/alien
- name = "alien bone gel"
- desc = "It smells like duct tape."
- icon = 'icons/obj/abductor.dmi'
- origin_tech = "materials=2;biotech=2;abductor=2"
- toolspeed = 0.25
-
-/obj/item/fix_o_vein/alien
- name = "alien FixOVein"
- desc = "Bloodless aliens would totally know how to stop internal bleeding... Right?"
- icon = 'icons/obj/abductor.dmi'
- origin_tech = "materials=2;biotech=2;abductor=2"
- toolspeed = 0.25
-
-/obj/item/bonesetter/alien
- name = "alien bone setter"
- desc = "You're not sure you want to know whether or not aliens have bones."
- icon = 'icons/obj/abductor.dmi'
- origin_tech = "materials=2;biotech=2;abductor=2"
- toolspeed = 0.25
-
-/////////////////////////////////////////
-//////////// JANITORIAL TOOLS ///////////
-/////////////////////////////////////////
-/obj/item/mop/advanced/abductor
- name = "alien mop"
- desc = "A collapsible mop clearly used by aliens to clean up any evidence of a close encounter. The head produces a constant supply of water when run over a surface, seemingly out of nowhere."
- icon = 'icons/obj/abductor.dmi'
- icon_state = "mop_abductor"
- mopcap = 100
- origin_tech = "materials=3;engineering=3;abductor=2"
- refill_rate = 50
- refill_reagent = "water"
- mopspeed = 10
-
-/obj/item/soap/syndie/abductor
- name = "alien soap"
- desc = "Even bloodless aliens need to wash the grime off. Smells like gunpowder."
- icon = 'icons/obj/abductor.dmi'
- icon_state = "soap_abductor"
-
-/obj/item/lightreplacer/bluespace/abductor
- name = "alien light replacer"
- desc = "It's important to keep all the mysterious lights on a UFO functional when flying over backwater country."
- icon = 'icons/obj/abductor.dmi'
- icon_state = "lightreplacer_abductor"
- origin_tech = "magnets=3;engineering=4;abductor=2"
- max_uses = 40
- uses = 20
-
-/obj/item/melee/flyswatter/abductor
- name = "alien flyswatter"
- desc = "For killing alien insects, obviously."
- icon = 'icons/obj/abductor.dmi'
- icon_state = "flyswatter_abductor"
- item_state = "flyswatter_abductor"
- origin_tech = "abductor=1"
- force = 2 // Twice as powerful thanks to alien technology!
- throwforce = 2
-
-/obj/item/reagent_containers/spray/cleaner/safety/abductor // Essentially an Advanced Space Cleaner, but abductor-themed. For the implant.
- name = "alien space cleaner"
- desc = "An alien spray bottle contaning alien-brand non-foaming space cleaner! It only accepts space cleaner."
- icon = 'icons/obj/abductor.dmi'
- icon_state = "cleaner_abductor"
- item_state = "cleaner_abductor"
- volume = 500
- spray_maxrange = 3
- spray_currentrange = 3
- list_reagents = list("cleaner" = 500)
-
-/obj/item/storage/belt/janitor/abductor
- name = "alien janibelt"
- desc = "A belt used to hold out-of-this-world cleaning supplies! Used by abductors to keep their ships clean."
- icon = 'icons/obj/abductor.dmi'
- icon_state = "janibelt_abductor"
- item_state = "security"
- storage_slots = 7
- can_hold = list(
- /obj/item/grenade/chem_grenade/cleaner,
- /obj/item/lightreplacer,
- /obj/item/flashlight,
- /obj/item/reagent_containers/spray,
- /obj/item/soap,
- /obj/item/holosign_creator/janitor,
- /obj/item/melee/flyswatter,
- /obj/item/storage/bag/trash,
- /obj/item/push_broom,
- /obj/item/door_remote/janikeyring,
- /obj/item/mop/advanced/abductor
- )
-
-/obj/item/storage/belt/janitor/abductor/full/populate_contents()
- new /obj/item/mop/advanced/abductor(src)
- new /obj/item/soap/syndie/abductor(src)
- new /obj/item/lightreplacer/bluespace/abductor(src)
- new /obj/item/storage/bag/trash/bluespace(src)
- new /obj/item/melee/flyswatter/abductor(src)
- new /obj/item/reagent_containers/spray/cleaner/safety/abductor(src)
- new /obj/item/holosign_creator/janitor(src)
-
-/////////////////////////////////////////
-/////////////// STRUCTURES //////////////
-/////////////////////////////////////////
-/obj/structure/bed/abductor
- name = "resting contraption"
- desc = "This looks similar to contraptions from earth. Could aliens be stealing our technology?"
- icon = 'icons/obj/abductor.dmi'
- buildstacktype = /obj/item/stack/sheet/mineral/abductor
- icon_state = "bed"
-
-/obj/structure/table_frame/abductor
- name = "alien table frame"
- desc = "A sturdy table frame made from alien alloy."
- icon_state = "alien_frame"
- framestack = /obj/item/stack/sheet/mineral/abductor
- framestackamount = 1
- density = TRUE
- anchored = TRUE
- resistance_flags = FIRE_PROOF | ACID_PROOF
- restrict_table_types = list(/obj/item/stack/sheet/mineral/silver = /obj/machinery/optable/abductor, /obj/item/stack/sheet/mineral/abductor = /obj/item/stack/sheet/mineral/abductor::table_type)
-
-/obj/structure/table/abductor
- name = "alien table"
- desc = "Advanced flat surface technology at work!"
- icon = 'icons/obj/smooth_structures/tables/alien_table.dmi'
- icon_state = "alien_table-0"
- base_icon_state = "alien_table"
- buildstack = /obj/item/stack/sheet/mineral/abductor
- framestack = /obj/item/stack/sheet/mineral/abductor
- buildstackamount = 1
- framestackamount = 1
- smoothing_groups = list(SMOOTH_GROUP_ABDUCTOR_TABLES)
- canSmoothWith = list(SMOOTH_GROUP_ABDUCTOR_TABLES)
- frame = /obj/structure/table_frame/abductor
-
-/obj/machinery/optable/abductor
- icon = 'icons/obj/abductor.dmi'
- icon_state = "bed"
- no_icon_updates = 1 //no icon updates for this; it's static.
- injected_reagents = list("corazone","spaceacillin")
- reagent_target_amount = 31 //the patient needs at least 30u of spaceacillin to prevent necrotization.
- inject_amount = 10
-
-/obj/structure/closet/abductor
- name = "alien locker"
- desc = "Contains secrets of the universe."
- icon_state = "abductor"
- door_anim_time = 0
- material_drop = /obj/item/stack/sheet/mineral/abductor
-
-/obj/structure/door_assembly/door_assembly_abductor
- name = "alien airlock assembly"
- icon = 'icons/obj/doors/airlocks/abductor/abductor_airlock.dmi'
- base_name = "alien airlock"
- overlays_file = 'icons/obj/doors/airlocks/abductor/overlays.dmi'
- airlock_type = /obj/machinery/door/airlock/abductor
- material_type = /obj/item/stack/sheet/mineral/abductor
- noglass = TRUE
-
-#undef GIZMO_SCAN
-#undef GIZMO_MARK
-#undef MIND_DEVICE_MESSAGE
-#undef MIND_DEVICE_CONTROL
-#undef BATON_STUN
-#undef BATON_SLEEP
-#undef BATON_CUFF
-#undef BATON_PROBE
-#undef BATON_MODES
diff --git a/code/game/gamemodes/miniantags/demons/demon.dm b/code/game/gamemodes/miniantags/demons/demon.dm
deleted file mode 100644
index 91aa036212b5c..0000000000000
--- a/code/game/gamemodes/miniantags/demons/demon.dm
+++ /dev/null
@@ -1,39 +0,0 @@
-/mob/living/simple_animal/demon
- name = "a generic demon"
- desc = "you shouldnt be reading this, file a github report."
- speak_emote = list("gurgles")
- emote_hear = list("wails","screeches")
- response_help = "thinks better of touching"
- response_disarm = "flails at"
- response_harm = "punches"
- speed = 1
- a_intent = INTENT_HARM
- mob_biotypes = MOB_ORGANIC | MOB_HUMANOID
- stop_automated_movement = TRUE
- status_flags = CANPUSH
- attack_sound = 'sound/misc/demon_attack1.ogg'
- death_sound = 'sound/misc/demon_dies.ogg'
- atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0)
- minbodytemp = 0
- maxbodytemp = INFINITY
- faction = list("demon")
- attacktext = "wildly tears into"
- maxHealth = 200
- health = 200
- environment_smash = ENVIRONMENT_SMASH_STRUCTURES
- obj_damage = 50
- melee_damage_lower = 30
- melee_damage_upper = 30
- see_in_dark = 8
- lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE
- del_on_death = TRUE
- var/datum/action/innate/demon_whisper/whisper_action
-
-/mob/living/simple_animal/demon/Initialize(mapload)
- . = ..()
- whisper_action = new()
- whisper_action.Grant(src)
-
-/mob/living/simple_animal/demon/Destroy()
- QDEL_NULL(whisper_action)
- return ..()
diff --git a/code/game/gamemodes/miniantags/demons/slaughter_demon/slaughter.dm b/code/game/gamemodes/miniantags/demons/slaughter_demon/slaughter.dm
deleted file mode 100644
index dedc9c679eeec..0000000000000
--- a/code/game/gamemodes/miniantags/demons/slaughter_demon/slaughter.dm
+++ /dev/null
@@ -1,359 +0,0 @@
-//////////////////The Monster
-
-/mob/living/simple_animal/demon/slaughter
- name = "slaughter demon"
- real_name = "slaughter demon"
- desc = "A large, menacing creature covered in armored black scales. You should run."
- maxHealth = 240
- health = 240
- speak = list("ire", "ego", "nahlizet", "certum", "veri", "jatkaa", "balaq", "mgar", "karazet", "geeri", "orkan", "allaq")
- icon = 'icons/mob/mob.dmi'
- icon_state = "daemon"
- icon_living = "daemon"
- var/boost = 0
- var/feast_sound = 'sound/misc/demon_consume.ogg'
- var/devoured = 0
- var/list/consumed_mobs = list()
-
- var/list/nearby_mortals = list()
- var/cooldown = 0
- var/gorecooldown = 0
- var/vialspawned = FALSE
- loot = list(/obj/effect/decal/cleanable/blood/innards, /obj/effect/decal/cleanable/blood, /obj/effect/gibspawner/generic, /obj/effect/gibspawner/generic, /obj/item/organ/internal/heart/demon/slaughter)
- var/playstyle_string = "You are the Slaughter Demon, a terrible creature from another existence. You have a single desire: to kill. \
- You may use the blood crawl icon when on blood pools to travel through them, appearing and dissapearing from the station at will. \
- Pulling a dead or critical mob while you enter a pool will pull them in with you, allowing you to feast. \
- You move quickly upon leaving a pool of blood, but the material world will soon sap your strength and leave you sluggish. "
- del_on_death = TRUE
- deathmessage = "screams in anger as it collapses into a puddle of viscera!"
-
-
-/mob/living/simple_animal/demon/slaughter/New()
- ..()
- remove_from_all_data_huds()
- ADD_TRAIT(src, TRAIT_BLOODCRAWL_EAT, "bloodcrawl_eat")
- var/datum/spell/bloodcrawl/bloodspell = new
- AddSpell(bloodspell)
- if(istype(loc, /obj/effect/dummy/slaughter))
- bloodspell.phased = TRUE
- addtimer(CALLBACK(src, PROC_REF(attempt_objectives)), 5 SECONDS)
-
-
-/mob/living/simple_animal/demon/slaughter/Life(seconds, times_fired)
- ..()
- if(boost < world.time)
- speed = 1
- else
- speed = 0
-
-/mob/living/simple_animal/demon/slaughter/proc/attempt_objectives()
- if(mind)
- var/list/messages = list()
- messages.Add(playstyle_string)
- messages.Add("You are not currently in the same plane of existence as the station. Use the blood crawl action at a blood pool to manifest.")
- SEND_SOUND(src, sound('sound/misc/demon_dies.ogg'))
- if(!vialspawned)
- SSticker.mode.traitors |= mind
- mind.add_mind_objective(/datum/objective/slaughter)
- mind.add_mind_objective(/datum/objective/demon_fluff)
- messages.Add(mind.prepare_announce_objectives(FALSE))
-
- messages.Add("For more information, check the wiki page: ([GLOB.configuration.url.wiki_url]/index.php/Slaughter_Demon)")
- to_chat(src, chat_box_red(messages.Join(" ")))
-
-
-/obj/effect/decal/cleanable/blood/innards
- icon = 'icons/obj/surgery.dmi'
- icon_state = "innards"
- name = "pile of viscera"
- desc = "A repulsive pile of guts and gore."
-
-/mob/living/simple_animal/demon/slaughter/Destroy()
- // Only execute the below if we successfully died
- for(var/mob/living/M in consumed_mobs)
- REMOVE_TRAIT(M, TRAIT_UNREVIVABLE, "demon")
- release_consumed(M)
- . = ..()
-
-/mob/living/simple_animal/demon/slaughter/proc/release_consumed(mob/living/M)
- M.forceMove(get_turf(src))
-
-
-// Midround slaughter demon, less tanky
-
-/mob/living/simple_animal/demon/slaughter/lesser
- maxHealth = 170
- health = 170
-
-// Cult slaughter demon
-/// Summoned as part of the cult objective "Bring the Slaughter"
-/mob/living/simple_animal/demon/slaughter/cult
- name = "harbinger of the slaughter"
- real_name = "harbinger of the Slaughter"
- desc = "An awful creature from beyond the realms of madness."
- maxHealth = 540
- health = 540
- melee_damage_upper = 60
- melee_damage_lower = 60
- environment_smash = ENVIRONMENT_SMASH_RWALLS //Smashes through EVERYTHING - r-walls included
- faction = list("cult")
- playstyle_string = "You are a Harbinger of the Slaughter. Brought forth by the servants of Nar'Sie, you have a single purpose: slaughter the heretics \
- who do not worship your master. You may use the ability 'Blood Crawl' near a pool of blood to enter it and become incorporeal. Using the ability again near a blood pool will allow you \
- to emerge from it. You are fast, powerful, and almost invincible. By dragging a dead or unconscious body into a blood pool with you, you will consume it after a time and fully regain \
- your health. You may use the ability 'Sense Victims' in your Cultist tab to locate a random, living heretic."
-
-/datum/spell/sense_victims
- name = "Sense Victims"
- desc = "Sense the location of heretics."
- base_cooldown = 0
- clothes_req = FALSE
- cooldown_min = 0
- overlay = null
- action_icon_state = "bloodcrawl"
- action_background_icon_state = "bg_cult"
-
-/datum/spell/sense_victims/create_new_targeting()
- return new /datum/spell_targeting/alive_mob_list
-
-/datum/spell/sense_victims/valid_target(mob/living/target, user)
- return target.stat == CONSCIOUS && target.key && !IS_CULTIST(target) // Only conscious, non cultist players
-
-/datum/spell/sense_victims/cast(list/targets, mob/user)
- var/mob/living/victim = targets[1]
- to_chat(victim, "You feel an awful sense of being watched...")
- victim.Stun(6 SECONDS) //HUE
- var/area/A = get_area(victim)
- if(!A)
- to_chat(user, "You could not locate any sapient heretics for the Slaughter.")
- return 0
- to_chat(user, "You sense a terrified soul at [A]. Show [A.p_them()] the error of [A.p_their()] ways.")
-
-/mob/living/simple_animal/demon/slaughter/cult/New()
- ..()
- spawn(5)
- var/list/demon_candidates = SSghost_spawns.poll_candidates("Do you want to play as a slaughter demon?", ROLE_DEMON, TRUE, 10 SECONDS, source = /mob/living/simple_animal/demon/slaughter/cult)
- if(!length(demon_candidates))
- visible_message("[src] disappears in a flash of red light!")
- qdel(src)
- return
- if(QDELETED(src)) // Just in case
- return
- var/mob/M = pick(demon_candidates)
- var/mob/living/simple_animal/demon/slaughter/cult/S = src
- if(!M || !M.client)
- visible_message("[src] disappears in a flash of red light!")
- qdel(src)
- return
- var/client/C = M.client
-
- S.key = C.key
- dust_if_respawnable(M)
- S.mind.assigned_role = "Harbinger of the Slaughter"
- S.mind.special_role = "Harbinger of the Slaughter"
- to_chat(S, playstyle_string)
- S.mind.add_antag_datum(/datum/antagonist/cultist)
- var/datum/spell/sense_victims/SV = new
- AddSpell(SV)
-
- S.mind.add_mind_objective(/datum/objective/cult_slaughter)
- var/list/messages = S.mind.prepare_announce_objectives(FALSE)
- to_chat(S, chat_box_red(messages.Join(" ")))
-
-////////////////////The Powers
-
-//Paradise Port: I added this because..SPOOPY DEMON IN YOUR BRAIN
-
-
-/datum/action/innate/demon_whisper
- name = "Demonic Whisper"
- button_overlay_icon_state = "demon_comms"
- button_background_icon_state = "bg_demon"
-
-/datum/action/innate/demon_whisper/IsAvailable()
- return ..()
-
-/datum/action/innate/demon_whisper/proc/choose_targets(mob/user = usr)//yes i am copying from telepathy..hush...
- var/list/validtargets = list()
- for(var/mob/living/M in view(user.client.maxview(), get_turf(user)))
- if(M && M.mind && M.stat != DEAD)
- if(M == user)
- continue
-
- validtargets += M
-
- if(!length(validtargets))
- to_chat(usr, "There are no valid targets!")
- return
-
- var/mob/living/target = tgui_input_list(user, "Choose the target to talk to", "Targeting", validtargets)
- return target
-
-/datum/action/innate/demon_whisper/Activate()
- var/mob/living/choice = choose_targets()
- if(!choice)
- return
-
- var/msg = tgui_input_text(usr, "What do you wish to tell [choice]?", null, "")
- if(!msg)
- return
- log_say("(SLAUGHTER to [key_name(choice)]) [msg]", usr)
- to_chat(usr, "You whisper to [choice]: [msg]")
- to_chat(choice, "Suddenly a strange, demonic voice resonates in your head... [msg]")
- for(var/mob/dead/observer/G in GLOB.player_list)
- G.show_message("Demonic message from [usr] ([ghost_follow_link(usr, ghost=G)]) to [choice] ([ghost_follow_link(choice, ghost=G)]): [msg]")
-
-
-//////////The Loot
-
-// Demon heart base type
-/obj/item/organ/internal/heart/demon
- name = "demon heart"
- desc = "Still it beats furiously, emanating an aura of utter hate."
- icon = 'icons/obj/surgery.dmi'
- icon_state = "demon_heart"
- origin_tech = "combat=5;biotech=7"
- organ_datums = list(/datum/organ/heart/always_beating, /datum/organ/battery)
-
-/obj/item/organ/internal/heart/demon/update_icon_state()
- return //always beating visually
-
-/obj/item/organ/internal/heart/demon/prepare_eat()
- return // Just so people don't accidentally waste it
-
-/obj/item/organ/internal/heart/demon/attack_self__legacy__attackchain(mob/living/user)
- user.visible_message("[user] raises [src] to [user.p_their()] mouth and tears into it with [user.p_their()] teeth!", \
- "An unnatural hunger consumes you. You raise [src] to your mouth and devour it!")
- playsound(user, 'sound/misc/demon_consume.ogg', 50, 1)
-
-//////////The Loot
-
-//The loot from killing a slaughter demon - can be consumed to allow the user to blood crawl
-/// SLAUGHTER DEMON HEART
-
-/obj/item/organ/internal/heart/demon/slaughter/attack_self__legacy__attackchain(mob/living/user)
- ..()
-
- // Eating the heart for the first time. Gives basic bloodcrawling. This is the only time we need to insert the heart.
- if(!HAS_TRAIT(user, TRAIT_BLOODCRAWL))
- user.visible_message("[user]'s eyes flare a deep crimson!", \
- "You feel a strange power seep into your body... you have absorbed the demon's blood-travelling powers!")
- ADD_TRAIT(user, TRAIT_BLOODCRAWL, "bloodcrawl")
- user.drop_item()
- insert(user) //Consuming the heart literally replaces your heart with a demon heart. H A R D C O R E.
- return TRUE
-
- // Eating a 2nd heart. Gives the ability to drag people into blood and eat them.
- if(HAS_TRAIT(user, TRAIT_BLOODCRAWL))
- to_chat(user, "You feel differ- CONSUME THEM!")
- ADD_TRAIT(user, TRAIT_BLOODCRAWL_EAT, "bloodcrawl_eat")
- qdel(src) // Replacing their demon heart with another demon heart is pointless, just delete this one and return.
- return TRUE
-
- // Eating any more than 2 demon hearts does nothing.
- to_chat(user, "...and you don't feel any different.")
- qdel(src)
-
-/obj/item/organ/internal/heart/demon/slaughter/insert(mob/living/carbon/M, special = 0)
- . = ..()
- if(M.mind)
- M.mind.AddSpell(new /datum/spell/bloodcrawl(null))
-
-/obj/item/organ/internal/heart/demon/slaughter/remove(mob/living/carbon/M, special = 0)
- . = ..()
- if(M.mind)
- REMOVE_TRAIT(M, TRAIT_BLOODCRAWL, "bloodcrawl")
- REMOVE_TRAIT(M, TRAIT_BLOODCRAWL_EAT, "bloodcrawl_eat")
- M.mind.RemoveSpell(/datum/spell/bloodcrawl)
-
-/mob/living/simple_animal/demon/slaughter/laughter
- // The laughter demon! It's everyone's best friend! It just wants to hug
- // them so much, it wants to hug everyone at once!
- name = "laughter demon"
- real_name = "laughter demon"
- desc = "A large, adorable creature covered in armor with pink bows."
- speak_emote = list("giggles", "titters", "chuckles")
- emote_hear = list("gaffaws", "laughs")
- response_help = "hugs"
- attacktext = "wildly tickles"
- maxHealth = 215
- health = 215
- melee_damage_lower = 25
- melee_damage_upper = 25
- playstyle_string = "You are the Laughter Demon, an adorable creature from another existence. You have a single desire: to hug and tickle. \
- You may use the blood crawl icon when on blood pools to travel through them, appearing and dissapearing from the station at will. \
- Pulling a dead or critical mob while you enter a pool will pull them in with you, allowing you to hug them. \
- You move quickly upon leaving a pool of blood, but the material world will soon sap your strength and leave you sluggish. \
- (You should be attacking people on harm intent, and not nuzzling them.)"
-
- attack_sound = 'sound/items/bikehorn.ogg'
- feast_sound = 'sound/spookoween/scary_horn2.ogg'
- death_sound = 'sound/misc/sadtrombone.ogg'
-
- icon_state = "bowmon"
- icon_living = "bowmon"
- deathmessage = "fades out, as all of its friends are released from its prison of hugs."
- loot = list(/mob/living/simple_animal/pet/cat/kitten{name = "Laughter"})
-
-/mob/living/simple_animal/demon/slaughter/laughter/release_consumed(mob/living/M)
- if(M.revive())
- M.grab_ghost(force = TRUE)
- playsound(get_turf(src), feast_sound, 50, TRUE, -1)
- to_chat(M, "You leave [src]'s warm embrace, and feel ready to take on the world.")
- ..(M)
-
-
-//Objectives and helpers.
-
-//Objective info, Based on Reverent mini Atang
-/datum/objective/slaughter
- needs_target = FALSE
- var/targetKill = 10
-
-/datum/objective/slaughter/New()
- targetKill = rand(10,20)
- explanation_text = "Devour [targetKill] mortals."
- ..()
-
-/datum/objective/slaughter/check_completion()
- var/kill_count = 0
- for(var/datum/mind/M in get_owners())
- if(!isslaughterdemon(M.current) || QDELETED(M.current))
- continue
- var/mob/living/simple_animal/demon/slaughter/R = M.current
- kill_count += R.devoured
- if(kill_count >= targetKill)
- return TRUE
- return FALSE
-
-/datum/objective/demon_fluff
- name = "Spread blood"
- needs_target = FALSE
-
-/datum/objective/demon_fluff/New()
- find_target()
- var/targetname = "someone"
- if(target && target.current)
- targetname = target.current.real_name
- var/list/explanation_texts = list(
- "Spread blood all over the bridge.",
- "Spread blood all over the brig.",
- "Spread blood all over the chapel.",
- "Kill or Destroy all Janitors or Sanitation bots.",
- "Spare a few after striking them... make them bleed before the harvest.",
- "Hunt those that try to hunt you first.",
- "Hunt those that run away from you in fear",
- "Show [targetname] the power of blood.",
- "Drive [targetname] insane with demonic whispering."
- )
- // As this is a fluff objective, we don't need a target, so we want to null it out.
- // We don't want the demon getting a "Time for Plan B" message if the target cryos.
- target = null
- explanation_text = pick(explanation_texts)
- ..()
-
-/datum/objective/demon_fluff/check_completion()
- return TRUE
-
-/datum/objective/cult_slaughter
- explanation_text = "Bring forth the Slaughter to the nonbelievers."
- needs_target = FALSE
diff --git a/code/game/gamemodes/miniantags/guardian/host_actions.dm b/code/game/gamemodes/miniantags/guardian/host_actions.dm
deleted file mode 100644
index e21f25094156c..0000000000000
--- a/code/game/gamemodes/miniantags/guardian/host_actions.dm
+++ /dev/null
@@ -1,210 +0,0 @@
-/**
- * # Base guardian host action
- *
- * These are used by guardian hosts to interact with their guardians. These are not buttons that guardians themselves use.
- */
-/datum/action/guardian
- name = "Generic guardian host action"
- button_overlay_icon = 'icons/mob/guardian.dmi'
- button_overlay_icon_state = "base"
- var/mob/living/simple_animal/hostile/guardian/guardian
-
-/datum/action/guardian/Grant(mob/M, mob/living/simple_animal/hostile/guardian/G)
- if(!G || !istype(G))
- stack_trace("/datum/action/guardian created with no guardian to link to.")
- qdel(src)
- guardian = G
- return ..()
-
-/**
- * # Communicate action
- *
- * Allows the guardian host to communicate with their guardian.
- */
-/datum/action/guardian/communicate
- name = "Communicate"
- desc = "Communicate telepathically with your guardian."
- button_overlay_icon_state = "communicate"
-
-/datum/action/guardian/communicate/Trigger(left_click)
- var/input = tgui_input_text(owner, "Enter a message to tell your guardian:", "Message")
- if(!input || !guardian)
- return
-
- // Show the message to our guardian and to host.
- to_chat(guardian, "[owner]: [input]")
- to_chat(owner, "[owner]: [input]")
- log_say("(HOST to [key_name(guardian)]): [input]", owner)
- owner.create_log(SAY_LOG, "HOST to GUARDIAN: [input]", guardian)
-
- // Show the message to any ghosts/dead players.
- for(var/mob/M in GLOB.dead_mob_list)
- if(M && M.client && M.stat == DEAD && !isnewplayer(M))
- to_chat(M, "Guardian Communication from [owner] ([ghost_follow_link(owner, ghost=M)]): [input]")
-
-/**
- * # Recall guardian action
- *
- * Allows the guardian host to recall their guardian.
- */
-/datum/action/guardian/recall
- name = "Recall Guardian"
- desc = "Forcibly recall your guardian."
- button_overlay_icon_state = "recall"
-
-/datum/action/guardian/recall/Trigger(left_click)
- guardian.Recall()
-
-/**
- * # Reset guardian action
- *
- * Allows the guardian host to exchange their guardian's player for another.
- */
-/datum/action/guardian/reset_guardian
- name = "Replace Guardian Player"
- desc = "Replace your guardian's player with a ghost. This can only be done once."
- button_overlay_icon_state = "reset"
- var/cooldown_timer
-
-/datum/action/guardian/reset_guardian/IsAvailable()
- if(cooldown_timer)
- return FALSE
- return TRUE
-
-/datum/action/guardian/reset_guardian/Trigger(left_click)
- if(cooldown_timer)
- to_chat(owner, "This ability is still recharging.")
- return
-
- var/confirm = tgui_alert(owner, "Are you sure you want replace your guardian's player?", "Confirm", list("Yes", "No"))
- if(confirm != "Yes")
- return
-
- // Do this immediately, so the user can't spam a bunch of polls.
- cooldown_timer = addtimer(CALLBACK(src, PROC_REF(reset_cooldown)), 5 MINUTES)
- UpdateButtons()
-
- to_chat(owner, "Searching for a replacement ghost...")
- var/list/mob/dead/observer/candidates = SSghost_spawns.poll_candidates("Do you want to play as [guardian.real_name]?", ROLE_GUARDIAN, FALSE, 15 SECONDS, source = guardian)
-
- if(!length(candidates))
- to_chat(owner, "There were no ghosts willing to take control of your guardian. You can try again in 5 minutes.")
- return
- if(QDELETED(guardian)) // Just in case
- return
-
- var/mob/dead/observer/new_stand = pick(candidates)
- to_chat(guardian, "Your user reset you, and your body was taken over by a ghost. Looks like they weren't happy with your performance.")
- to_chat(owner, "Your guardian has been successfully reset.")
- message_admins("[key_name_admin(new_stand)] has taken control of ([key_name_admin(guardian)])")
- guardian.ghostize()
- guardian.key = new_stand.key
- dust_if_respawnable(new_stand)
- qdel(src)
-
-/datum/spell/summon_guardian_beacon
- name = "Place Teleportation Beacon"
- desc = "Mark a floor as your beacon point, allowing you to warp targets to it. Your beacon requires an anchor, will not work on space tiles."
- clothes_req = FALSE
- antimagic_flags = NONE
- base_cooldown = 300 SECONDS
- action_icon_state = "no_state"
- action_background_icon = 'icons/mob/guardian.dmi'
- action_background_icon_state = "reset"
-
-/datum/spell/summon_guardian_beacon/create_new_targeting()
- return new /datum/spell_targeting/self
-
-/datum/spell/summon_guardian_beacon/cast(list/targets, mob/living/user = usr)
- var/target = targets[1]
- var/mob/living/simple_animal/hostile/guardian/healer/guardian_user = user
- var/turf/beacon_loc = get_turf(target)
- if(isfloorturf(beacon_loc) && !islava(beacon_loc) && !ischasm(beacon_loc))
- QDEL_NULL(guardian_user.beacon)
- guardian_user.beacon = new(beacon_loc)
- to_chat(guardian_user, "Beacon placed! You may now warp targets to it, including your user, via Alt Click.")
-
- return TRUE
-
-/datum/spell/surveillance_snare
- name = "Set Surveillance Snare"
- desc = "Places an invisible Surveillance Snare on the ground, if someone walks over it you'll be alerted. Max of 6 snares active at a time"
- clothes_req = FALSE
- antimagic_flags = NONE
- base_cooldown = 3 SECONDS
- action_icon_state = "no_state"
- action_background_icon = 'icons/mob/guardian.dmi'
- action_background_icon_state = "reset"
-
-/datum/spell/surveillance_snare/create_new_targeting()
- return new /datum/spell_targeting/self
-
-/datum/spell/surveillance_snare/cast(list/targets, mob/living/user = usr)
- var/target = targets[1]
- var/mob/living/simple_animal/hostile/guardian/ranged/guardian_user = user
- if(length(guardian_user.snares) < 6)
- var/turf/snare_loc = get_turf(target)
- var/obj/effect/snare/S = new /obj/effect/snare(snare_loc)
- S.spawner = guardian_user
- S.name = "[get_area(snare_loc)] trap ([snare_loc.x],[snare_loc.y],[snare_loc.z])"
- guardian_user.snares |= S
- to_chat(guardian_user, "Surveillance trap deployed!")
- return TRUE
- else
- var/picked_snare = tgui_input_list(guardian_user, "You have too many snares deployed! Delete one to place another.", "Disarm Snare", guardian_user.snares)
- if(picked_snare)
- guardian_user.snares -= picked_snare
- qdel(picked_snare)
- to_chat(user, "Snare disarmed.")
- revert_cast()
-
-/datum/spell/choose_battlecry
- name = "Change battlecry"
- desc = "Changes your battlecry."
- clothes_req = FALSE
- antimagic_flags = NONE
- base_cooldown = 1 SECONDS
- action_icon_state = "no_state"
- action_background_icon = 'icons/mob/guardian.dmi'
- action_background_icon_state = "communicate"
-
-/datum/spell/choose_battlecry/create_new_targeting()
- return new /datum/spell_targeting/self
-
-/datum/spell/choose_battlecry/cast(list/targets, mob/living/user = usr)
- var/mob/living/simple_animal/hostile/guardian/punch/guardian_user = user
- var/input = tgui_input_text(guardian_user, "What do you want your battlecry to be? Max length of 6 characters.", "Change Battlecry", guardian_user.battlecry, 6)
- if(!input)
- revert_cast()
- return
- guardian_user.battlecry = input
-
-/**
- * Takes the action button off cooldown and makes it available again.
- */
-/datum/action/guardian/reset_guardian/proc/reset_cooldown()
- cooldown_timer = null
- UpdateButtons()
-
-/**
- * Grants all existing `/datum/action/guardian` type actions to the src mob.
- *
- * Called whenever the host gains their gauardian.
- */
-/mob/living/proc/grant_guardian_actions(mob/living/simple_animal/hostile/guardian/G)
- if(!G || !istype(G))
- return
- for(var/action in subtypesof(/datum/action/guardian))
- var/datum/action/guardian/A = new action
- A.Grant(src, G)
-
-/**
- * Removes all `/datum/action/guardian` type actions from the src mob.
- *
- * Called whenever the host loses their guardian.
- */
-/mob/living/proc/remove_guardian_actions()
- for(var/action in actions)
- var/datum/action/A = action
- if(istype(A, /datum/action/guardian))
- A.Remove(src)
diff --git a/code/game/gamemodes/miniantags/guardian/types/explosive_guardian.dm b/code/game/gamemodes/miniantags/guardian/types/explosive_guardian.dm
deleted file mode 100644
index 8ed033b7f9cbb..0000000000000
--- a/code/game/gamemodes/miniantags/guardian/types/explosive_guardian.dm
+++ /dev/null
@@ -1,117 +0,0 @@
-/mob/living/simple_animal/hostile/guardian/bomb
- melee_damage_lower = 15
- melee_damage_upper = 15
- damage_transfer = 0.6
- range = 13
- playstyle_string = "As an Explosive type, you have only moderate close combat abilities, but are capable of converting any adjacent item into a disguised bomb via alt click even when not manifested."
- magic_fluff_string = "..And draw the Scientist, master of explosive death."
- tech_fluff_string = "Boot sequence complete. Explosive modules active. Holoparasite swarm online."
- bio_fluff_string = "Your scarab swarm finishes mutating and stirs to life, capable of stealthily booby trapping items."
- var/bomb_cooldown = 0
- var/default_bomb_cooldown = 20 SECONDS
-
-/mob/living/simple_animal/hostile/guardian/bomb/get_status_tab_items()
- var/list/status_tab_data = ..()
- . = status_tab_data
- if(bomb_cooldown >= world.time)
- status_tab_data[++status_tab_data.len] = list("Bomb Cooldown Remaining:", "[max(round((bomb_cooldown - world.time) * 0.1, 0.1), 0)] seconds")
-
-/mob/living/simple_animal/hostile/guardian/bomb/AltClickOn(atom/movable/A)
- if(!istype(A))
- return
- if(get_dist(get_turf(src), get_turf(A)) > 1)
- to_chat(src, "You're too far from [A] to disguise it as a bomb.")
- return
- if(isobj(A) && can_plant(A))
- if(bomb_cooldown <= world.time && stat == CONSCIOUS)
- var/obj/item/guardian_bomb/B = new /obj/item/guardian_bomb(get_turf(A))
- add_attack_logs(src, A, "booby trapped (summoner: [summoner])")
- to_chat(src, "Success! Bomb on [A] armed!")
- if(summoner)
- to_chat(summoner, "Your guardian has primed [A] to explode!")
- bomb_cooldown = world.time + default_bomb_cooldown
- B.spawner = src
- B.disguise (A)
- else
- to_chat(src, "Your power is on cooldown! You must wait another [max(round((bomb_cooldown - world.time)*0.1, 0.1), 0)] seconds before you can place next bomb.")
-
-/mob/living/simple_animal/hostile/guardian/bomb/proc/can_plant(atom/movable/A)
- if(ismecha(A))
- var/obj/mecha/target = A
- if(target.occupant)
- to_chat(src, "You can't disguise piloted mechs as a bomb!")
- return FALSE
- if(istype(A, /obj/machinery/disposal)) // Have no idea why they just destroy themselves
- to_chat(src, "You can't disguise disposal units as a bomb!")
- return FALSE
- return TRUE
-
-/obj/item/guardian_bomb
- name = "bomb"
- desc = "You shouldn't be seeing this!"
- var/obj/stored_obj
- var/mob/living/spawner
-
-/obj/item/guardian_bomb/proc/disguise(obj/A)
- A.forceMove(src)
- stored_obj = A
- opacity = A.opacity
- anchored = A.anchored
- density = A.density
- appearance = A.appearance
- dir = A.dir
- move_resist = A.move_resist
- addtimer(CALLBACK(src, PROC_REF(disable)), 600)
-
-/obj/item/guardian_bomb/proc/disable()
- add_attack_logs(null, stored_obj, "booby trap expired")
- stored_obj.forceMove(get_turf(src))
- if(spawner)
- to_chat(spawner, "Failure! Your trap on [stored_obj] didn't catch anyone this time.")
- qdel(src)
-
-/obj/item/guardian_bomb/proc/detonate(mob/living/user)
- if(!istype(user))
- return
- to_chat(user, "[src] was boobytrapped!")
- if(isguardian(spawner))
- var/mob/living/simple_animal/hostile/guardian/G = spawner
- if(user == G.summoner)
- add_attack_logs(user, stored_obj, "booby trap defused")
- to_chat(user, "You knew this because of your link with your guardian, so you smartly defuse the bomb.")
- stored_obj.forceMove(get_turf(loc))
- qdel(src)
- return
- add_attack_logs(user, stored_obj, "booby trap TRIGGERED (spawner: [spawner])")
- to_chat(spawner, "Success! Your trap on [src] caught [user]!")
- stored_obj.forceMove(get_turf(loc))
- playsound(get_turf(src),'sound/effects/explosion2.ogg', 200, 1)
- user.ex_act(EXPLODE_HEAVY)
- user.Stun(3 SECONDS)//A bomb went off in your hands. Actually lets people follow up with it if they bait someone, right now it is unreliable.
- qdel(src)
-
-/obj/item/guardian_bomb/attackby__legacy__attackchain(obj/item/W, mob/living/user)
- detonate(user)
-
-/obj/item/guardian_bomb/attack_hand(mob/user)
- detonate(user)
-
-/obj/item/guardian_bomb/MouseDrop_T(obj/item/I, mob/living/user)
- detonate(user)
-
-/obj/item/guardian_bomb/AltClick(mob/living/user)
- detonate(user)
-
-/obj/item/guardian_bomb/MouseDrop(mob/living/user)
- detonate(user)
-
-/obj/item/guardian_bomb/Bumped(mob/living/user)
- detonate(user)
-
-/obj/item/guardian_bomb/can_be_pulled(mob/living/user)
- detonate(user)
-
-/obj/item/guardian_bomb/examine(mob/user)
- . = stored_obj.examine(user)
- if(get_dist(user, src) <= 2)
- . += "Looks odd!"
diff --git a/code/game/gamemodes/miniantags/guardian/types/protector.dm b/code/game/gamemodes/miniantags/guardian/types/protector.dm
deleted file mode 100644
index 756b4489bdead..0000000000000
--- a/code/game/gamemodes/miniantags/guardian/types/protector.dm
+++ /dev/null
@@ -1,151 +0,0 @@
-#define LEFT_SHIELD FALSE
-#define RIGHT_SHEILD TRUE
-
-/mob/living/simple_animal/hostile/guardian/protector
- melee_damage_lower = 15
- melee_damage_upper = 15
- range = 15 //worse for it due to how it leashes
- damage_transfer = 0.4
- playstyle_string = "As a Protector type you cause your summoner to leash to you instead of you leashing to them and have two modes; Combat Mode, where you do and take medium damage, and Protection Mode, where you do and take almost no damage, but move slightly slower, as well as have a protective shield. Nobody can walk through your shield, but you can still move your shield through them."
- magic_fluff_string = "..And draw the Guardian, a stalwart protector that never leaves the side of its charge."
- tech_fluff_string = "Boot sequence complete. Protector modules loaded. Holoparasite swarm online."
- bio_fluff_string = "Your scarab swarm finishes mutating and stirs to life, ready to defend you."
- var/toggle = FALSE
- /// The shields the guardian has, and brings with it as it moves
- var/list/connected_shields = list()
-
-/mob/living/simple_animal/hostile/guardian/protector/ex_act(severity)
- if(severity == EXPLODE_DEVASTATE)
- adjustBruteLoss(200) //if in protector mode, will do 20 damage and not actually necessarily kill the summoner
- else
- ..()
- if(toggle)
- visible_message("The explosion glances off [src]'s energy shielding!")
-
-
-/mob/living/simple_animal/hostile/guardian/protector/Manifest()
- . = ..()
- if(toggle && cooldown < world.time)
- var/dir_left = turn(dir, -90)
- var/dir_right = turn(dir, 90)
- connected_shields += new /obj/effect/guardianshield(get_step(src, dir_left), src, FALSE)
- connected_shields += new /obj/effect/guardianshield(get_step(src, dir_right), src, TRUE)
-
-/mob/living/simple_animal/hostile/guardian/protector/Recall(forced)
- . = ..()
- if(cooldown > world.time && !forced)
- QDEL_LIST_CONTENTS(connected_shields)
-
-/mob/living/simple_animal/hostile/guardian/protector/Move()
- . = ..()
- for(var/obj/effect/guardianshield/G in connected_shields)
- var/dir_chosen
- if(G.shield_orientation)
- dir_chosen = turn(dir, 90)
- else
- dir_chosen = turn(dir, -90)
- G.forceMove(get_step(src, dir_chosen))
-
-/mob/living/simple_animal/hostile/guardian/protector/ToggleMode()
- if(cooldown > world.time)
- return 0
- cooldown = world.time + 10
- if(toggle)
- overlays.Cut()
- melee_damage_lower = initial(melee_damage_lower)
- melee_damage_upper = initial(melee_damage_upper)
- obj_damage = initial(obj_damage)
- move_resist = initial(move_resist)
- speed = initial(speed)
- damage_transfer = 0.4
- to_chat(src, "You switch to combat mode.")
- toggle = FALSE
- QDEL_LIST_CONTENTS(connected_shields)
- else
- if(!isturf(loc))
- return
- if(get_turf(summoner) == get_turf(src))
- to_chat(src, "You cannot deploy your shield while on your host!")
- return
- var/icon/shield_overlay = icon('icons/effects/effects.dmi', "shield-grey")
- shield_overlay *= name_color
- overlays.Add(shield_overlay)
- melee_damage_lower = 2
- melee_damage_upper = 2
- obj_damage = 6 //40/7.5 rounded up, we don't want a protector guardian 2 shotting blob tiles while taking 5% damage, thats just silly.
- move_resist = MOVE_FORCE_STRONG
- speed = 1
- damage_transfer = 0.1 //damage? what's damage?
- to_chat(src, "You switch to protection mode.")
- toggle = TRUE
- var/dir_left = turn(dir, -90)
- var/dir_right = turn(dir, 90)
- connected_shields += new /obj/effect/guardianshield(get_step(src, dir_left), src, LEFT_SHIELD)
- connected_shields += new /obj/effect/guardianshield(get_step(src, dir_right), src, RIGHT_SHEILD)
-
-/mob/living/simple_animal/hostile/guardian/protector/snapback() //snap to what? snap to the guardian!
- // If the summoner dies instantly, the summoner's ghost may be drawn into null space as the protector is deleted. This check should prevent that.
- if(summoner && loc && summoner.loc)
- if(get_dist(get_turf(summoner),get_turf(src)) <= range)
- return
- else
- if(iseffect(summoner.loc))
- to_chat(src, "You moved out of range, and were pulled back! You can only move [range] meters from [summoner.real_name]!")
- visible_message("[src] jumps back to its user.")
- Recall(TRUE)
- else
- to_chat(summoner, "You moved out of range, and were pulled back! You can only move [range] meters from [src]!")
- summoner.visible_message("[summoner] jumps back to [summoner.p_their()] protector.")
- new /obj/effect/temp_visual/guardian/phase/out(get_turf(summoner))
- summoner.forceMove(get_turf(src))
- new /obj/effect/temp_visual/guardian/phase(get_turf(summoner))//Protector
-
-/mob/living/simple_animal/hostile/guardian/protector/CanPass(atom/movable/mover, border_dir)
- . = ..()
- if(toggle && isliving(mover)) //No crawling under a protector
- return FALSE
-
-/mob/living/simple_animal/hostile/guardian/protector/Destroy()
- QDEL_LIST_CONTENTS(connected_shields)
- return ..()
-
-/obj/effect/guardianshield
- name = "guardian's shield"
- desc = "A guardian's defensive wall."
- icon = 'icons/effects/effects.dmi'
- icon_state = "shield-grey"
- can_be_hit = TRUE
- var/mob/living/simple_animal/hostile/guardian/protector/linked_guardian
- ///Is the guardians shield the left or right shield?
- var/shield_orientation = LEFT_SHIELD
-
-/obj/effect/guardianshield/Initialize(mapload, mob/living/simple_animal/hostile/guardian/protector/creator, left_or_right)
- . = ..()
- linked_guardian = creator
- color = linked_guardian.name_color
- shield_orientation = left_or_right
-
-/obj/effect/guardianshield/CanPass(atom/movable/mover, border_dir)
- if(mover == linked_guardian)
- return TRUE
- return FALSE
-
-/obj/effect/guardianshield/bullet_act(obj/item/projectile/P)
- if(!P)
- return
- linked_guardian.apply_damage(P.damage, P.damage_type)
- P.on_hit(src, 0)
- return FALSE
-
-/obj/effect/guardianshield/attacked_by__legacy__attackchain(obj/item/I, mob/living/user)
- if(I.force)
- user.visible_message("[user] has hit [src] with [I]!", "You hit [src] with [I]!")
- linked_guardian.apply_damage(I.force, I.damtype)
-
-/obj/effect/guardianshield/Destroy()
- linked_guardian = null
- return ..()
-
-
-#undef LEFT_SHIELD
-#undef RIGHT_SHEILD
diff --git a/code/game/gamemodes/miniantags/guardian/types/ranged.dm b/code/game/gamemodes/miniantags/guardian/types/ranged.dm
deleted file mode 100644
index 862fe56ff1115..0000000000000
--- a/code/game/gamemodes/miniantags/guardian/types/ranged.dm
+++ /dev/null
@@ -1,112 +0,0 @@
-/obj/item/projectile/guardian
- name = "crystal spray"
- icon_state = "guardian"
- damage = 20
- damage_type = BRUTE
- armour_penetration_percentage = 100
-
-/mob/living/simple_animal/hostile/guardian/ranged
- friendly = "quietly assesses"
- melee_damage_lower = 10
- melee_damage_upper = 10
- damage_transfer = 1
- projectiletype = /obj/item/projectile/guardian
- ranged_cooldown_time = 5 //fast!
- projectilesound = 'sound/effects/hit_on_shattered_glass.ogg'
- ranged = TRUE
- range = 13
- lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE
- see_in_dark = 8
- playstyle_string = "As a Ranged type, you have only light damage resistance, but are capable of spraying shards of crystal at incredibly high speed. You can also deploy surveillance snares to monitor enemy movement. Finally, you can switch to scout mode, in which you can't attack, but can move without limit."
- magic_fluff_string = "..And draw the Sentinel, an alien master of ranged combat."
- tech_fluff_string = "Boot sequence complete. Ranged combat modules active. Holoparasite swarm online."
- bio_fluff_string = "Your scarab swarm finishes mutating and stirs to life, capable of spraying shards of crystal."
- var/list/snares = list()
- var/toggle = FALSE
-
-/mob/living/simple_animal/hostile/guardian/ranged/Initialize(mapload, mob/living/host)
- . = ..()
- AddSpell(new /datum/spell/surveillance_snare(null))
-
-/mob/living/simple_animal/hostile/guardian/ranged/ToggleMode()
- if(loc == summoner)
- if(toggle)
- ranged = TRUE
- melee_damage_lower = 10
- melee_damage_upper = 10
- obj_damage = initial(obj_damage)
- environment_smash = initial(environment_smash)
- alpha = 255
- range = 13
- incorporeal_move = NO_INCORPOREAL_MOVE
- ADD_TRAIT(src, TRAIT_CAN_STRIP, TRAIT_GENERIC)
- to_chat(src, "You switch to combat mode.")
- toggle = FALSE
- else
- ranged = FALSE
- melee_damage_lower = 0
- melee_damage_upper = 0
- obj_damage = 0
- environment_smash = ENVIRONMENT_SMASH_NONE
- alpha = 60
- range = 255
- incorporeal_move = INCORPOREAL_MOVE_NORMAL
- REMOVE_TRAIT(src, TRAIT_CAN_STRIP, TRAIT_GENERIC) //spiritual pickpocketting is forbidden
- to_chat(src, "You switch to scout mode.")
- toggle = TRUE
- else
- to_chat(src, "You have to be recalled to toggle modes!")
-
-/mob/living/simple_animal/hostile/guardian/ranged/ToggleLight()
- var/msg
- switch(lighting_alpha)
- if(LIGHTING_PLANE_ALPHA_VISIBLE)
- lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE
- msg = "You activate your night vision."
- if(LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE)
- lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE
- msg = "You increase your night vision."
- if(LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE)
- lighting_alpha = LIGHTING_PLANE_ALPHA_INVISIBLE
- msg = "You maximize your night vision."
- else
- lighting_alpha = LIGHTING_PLANE_ALPHA_VISIBLE
- msg = "You deactivate your night vision."
-
- update_sight()
-
- to_chat(src, "[msg]")
-
-/mob/living/simple_animal/hostile/guardian/ranged/blob_act(obj/structure/blob/B)
- if(toggle)
- return // we don't want blob tiles to hurt us when we fly over them and trigger /Crossed(), this prevents ranged scouts from being insta killed
- return ..() // otherwise do normal damage!
-
-/obj/effect/snare
- name = "snare"
- desc = "You shouldn't be seeing this!"
- var/mob/living/spawner
- invisibility = 101
-
-/obj/effect/snare/Initialize(mapload)
- . = ..()
- var/static/list/loc_connections = list(
- COMSIG_ATOM_ENTERED = PROC_REF(on_atom_entered),
- )
- AddElement(/datum/element/connect_loc, loc_connections)
-
-/obj/effect/snare/singularity_act()
- return
-
-/obj/effect/snare/singularity_pull()
- return
-
-/obj/effect/snare/proc/on_atom_entered(datum/source, atom/movable/entered)
- if(isliving(entered))
- var/turf/snare_loc = get_turf(loc)
- if(spawner)
- to_chat(spawner, "[entered] has crossed your surveillance trap at [get_area(snare_loc)].")
- if(isguardian(spawner))
- var/mob/living/simple_animal/hostile/guardian/G = spawner
- if(G.summoner)
- to_chat(G.summoner, "[entered] has crossed your surveillance trap at [get_area(snare_loc)].")
diff --git a/code/game/gamemodes/miniantags/morph/spells/pass_airlock.dm b/code/game/gamemodes/miniantags/morph/spells/pass_airlock.dm
deleted file mode 100644
index 847ee5cce0044..0000000000000
--- a/code/game/gamemodes/miniantags/morph/spells/pass_airlock.dm
+++ /dev/null
@@ -1,54 +0,0 @@
-// TODO refactor when spell code is component based instead of OO based
-/datum/spell/morph_spell/pass_airlock
- name = "Pass Airlock"
- desc = "Reform yourself so you can fit through a non bolted airlock. Takes a while to do and can only be used in a non disguised form."
- action_background_icon_state = "bg_morph"
- action_icon_state = "morph_airlock"
- clothes_req = FALSE
- antimagic_flags = NONE
- base_cooldown = 10 SECONDS
- selection_activated_message = "Click on an airlock to try pass it."
-
-/datum/spell/morph_spell/pass_airlock/create_new_targeting()
- var/datum/spell_targeting/click/T = new
- T.range = 1
- T.allowed_type = /obj/machinery/door/airlock
- T.click_radius = -1
- return T
-
-
-/datum/spell/morph_spell/pass_airlock/can_cast(mob/living/simple_animal/hostile/morph/user, charge_check, show_message)
- . = ..()
- if(!.)
- return
-
- if(user.morphed)
- if(show_message)
- to_chat(user, "You can only pass through airlocks in your true form!")
- return FALSE
-
-/datum/spell/morph_spell/pass_airlock/cast(list/targets, mob/living/simple_animal/hostile/morph/user)
- var/obj/machinery/door/airlock/A = targets[1]
- if(A.locked)
- to_chat(user, "[A] is bolted shut! You're unable to create a crack to pass through!")
- revert_cast(user)
- return
- user.visible_message("[user] starts pushing itself against [A]!", "You try to pry [A] open enough to get through.")
- if(!do_after(user, 6 SECONDS, FALSE, user, TRUE, list(CALLBACK(src, PROC_REF(pass_check), user, A)), FALSE))
- if(user.morphed)
- to_chat(user, "You need to stay in your true form to pass through [A]!")
- else if(A.locked)
- to_chat(user, "[A] is bolted shut! You're unable to create a crack to pass through!")
- else
- to_chat(user, "You need to stay still to pass through [A]!")
- revert_cast(user)
- return
- if(QDELETED(A))
- return
-
- user.visible_message("[user] briefly opens [A] slightly and passes through!", "You slide through the open crack in [A].")
- user.forceMove(A.loc) // Move into the turf of the airlock
-
-
-/datum/spell/morph_spell/pass_airlock/proc/pass_check(mob/living/simple_animal/hostile/morph/user, obj/machinery/door/airlock/A)
- return user.morphed || A.locked
diff --git a/code/game/gamemodes/miniantags/pulsedemon/cross_shock_component.dm b/code/game/gamemodes/miniantags/pulsedemon/cross_shock_component.dm
deleted file mode 100644
index cf7da6be862b0..0000000000000
--- a/code/game/gamemodes/miniantags/pulsedemon/cross_shock_component.dm
+++ /dev/null
@@ -1,65 +0,0 @@
-/datum/component/cross_shock
- var/shock_damage
- var/energy_cost
- var/delay_between_shocks
- var/requires_cable
-
- COOLDOWN_DECLARE(last_shock)
-
-/datum/component/cross_shock/Initialize(_shock_damage, _energy_cost, _delay_between_shocks, _requires_cable = TRUE)
- if(ismovable(parent))
- var/static/list/crossed_connections = list(
- COMSIG_ATOM_ENTERED = PROC_REF(do_shock),
- )
- AddComponent(/datum/component/connect_loc_behalf, parent, crossed_connections)
- RegisterSignal(parent, COMSIG_MOVABLE_MOVED, PROC_REF(on_movable_moved))
- if(ismob(parent))
- RegisterSignal(parent, COMSIG_CARBON_LOSE_ORGAN, PROC_REF(on_organ_removal))
- else if(isarea(parent))
- RegisterSignal(parent, COMSIG_ATOM_EXITED, PROC_REF(do_shock))
- else if(isturf(parent))
- RegisterSignal(parent, COMSIG_ATOM_ENTERED, PROC_REF(do_shock))
- else
- return COMPONENT_INCOMPATIBLE
-
- shock_damage = _shock_damage
- energy_cost = _energy_cost
- delay_between_shocks = _delay_between_shocks
- requires_cable = _requires_cable
-
-/datum/component/cross_shock/proc/on_movable_moved(atom/source, old_location, direction, forced)
- SIGNAL_HANDLER // COMSIG_MOVABLE_MOVED
- if(isturf(source.loc))
- for(var/mob/living/mob in source.loc)
- do_shock(src, mob)
-
-/datum/component/cross_shock/proc/do_shock(atom/source, atom/movable/to_shock)
- SIGNAL_HANDLER // COMSIG_ATOM_ENTERED
- if(!COOLDOWN_FINISHED(src, last_shock))
- return
- var/mob/living/living_to_shock = to_shock
- if(!istype(living_to_shock))
- return
- if(isliving(parent))
- var/mob/living/M = parent
- if(M.stat == DEAD || !IS_HORIZONTAL(M))
- return
- if(requires_cable)
- var/turf/our_turf = get_turf(parent)
- if(our_turf.transparent_floor || our_turf.intact || HAS_TRAIT(our_turf, TRAIT_TURF_COVERED))
- return
- var/obj/structure/cable/our_cable = locate(/obj/structure/cable) in our_turf
- if(!our_cable || !our_cable.powernet || !our_cable.powernet.available_power)
- return
- var/area/to_deduct_from = get_area(our_cable)
- living_to_shock.electrocute_act(shock_damage, source)
- to_deduct_from.powernet.use_active_power(energy_cost)
- playsound(get_turf(parent), 'sound/effects/eleczap.ogg', 30, TRUE)
- else
- living_to_shock.electrocute_act(shock_damage, source)
- playsound(get_turf(parent), 'sound/effects/eleczap.ogg', 30, TRUE)
- COOLDOWN_START(src, last_shock, delay_between_shocks)
-
-/datum/component/cross_shock/proc/on_organ_removal(datum/source)
- SIGNAL_HANDLER
- qdel(src)
diff --git a/code/game/gamemodes/miniantags/pulsedemon/pulsedemon.dm b/code/game/gamemodes/miniantags/pulsedemon/pulsedemon.dm
deleted file mode 100644
index 83e7c32d8b69f..0000000000000
--- a/code/game/gamemodes/miniantags/pulsedemon/pulsedemon.dm
+++ /dev/null
@@ -1,895 +0,0 @@
-// original implementation: https://ss13.moe/wiki/index.php/Pulse_Demon
-
-#define PULSEDEMON_PLATING_SPARK_CHANCE 20
-#define PULSEDEMON_APC_CHARGE_MULTIPLIER 2
-#define PULSEDEMON_SMES_DRAIN_MULTIPLIER 10
-#define ALERT_CATEGORY_NOPOWER "pulse_nopower"
-#define ALERT_CATEGORY_NOREGEN "pulse_noregen"
-/// Conversion ratio from Watt ticks to joules.
-
-/mob/living/simple_animal/demon/pulse_demon
- name = "pulse demon"
- real_name = "pulse demon"
- desc = "A strange electrical apparition that lives in wires."
- gender = NEUTER
- speak_chance = 20
-
- damage_coeff = list(BRUTE = 0, BURN = 0.5, TOX = 0, CLONE = 0, STAMINA = 0, OXY = 0) // Pulse demons take damage from nothing except some from lasers
-
- emote_hear = list("vibrates", "sizzles")
- speak_emote = list("modulates")
-
- icon = 'icons/mob/animal.dmi'
- icon_state = "pulsedem"
- icon_living = "pulsedem"
- icon_dead = "pulsedem"
- response_help = "reaches their hand into"
- response_disarm = "pushes their hand through"
- response_harm = "punches their fist through"
- deathmessage = "fizzles out into faint sparks, leaving only a slight trail of smoke..."
- level = 1
- plane = FLOOR_PLANE
- layer = ABOVE_PLATING_LAYER
-
- maxHealth = 50
- health = 50
- speed = -0.5
- mob_size = MOB_SIZE_TINY
- density = FALSE
- del_on_death = TRUE
-
- attacktext = "electrocutes"
- attack_sound = "sparks"
- a_intent = INTENT_HARM
- harm_intent_damage = 0
- melee_damage_lower = 0
- melee_damage_upper = 0
- pass_flags = PASSDOOR
- stop_automated_movement = TRUE
- has_unlimited_silicon_privilege = TRUE
- // this makes the demon able to speak through holopads, due to the overriden say, PD cannot speak normally regardless
- universal_speak = TRUE
- loot = list(/obj/item/organ/internal/heart/demon/pulse)
- initial_traits = list(TRAIT_FLYING)
-
- /// List of sounds that is picked from when the demon speaks.
- var/list/speech_sounds = list("sound/voice/pdvoice1.ogg", "sound/voice/pdvoice2.ogg", "sound/voice/pdvoice3.ogg")
- /// List of sounds that is picked from when the demon dies or is EMP'd.
- var/list/hurt_sounds = list("sound/voice/pdwail1.ogg", "sound/voice/pdwail2.ogg", "sound/voice/pdwail3.ogg")
-
- /// Current quantity of energy the demon currently holds (Joules), spent while purchasing, upgrading or using spells or upgrades. Use adjust_charge to modify this.
- var/charge = 1000
- /// Maximum quantity of energy the demon can hold at once (Joules).
- var/maxcharge = 1000
- /// Book keeping for objective win conditions (Joules).
- var/charge_drained = 0
- /// Controls whether the demon will drain power from sources. Toggled by a spell.
- var/do_drain = TRUE
- /// Amount of power (Watts) to drain from power sources every Life tick.
- var/power_drain_rate = 1000
- /// Maximum value for power_drain_rate based on upgrades. (Watts)
- var/max_drain_rate = 1000
-
- /// Amount of power (Watts) required to regenerate health.
- var/power_per_regen = 1000
- /// Amount of health lost per Life tick when the power requirement was not met.
- var/health_loss_rate = 5
- /// Amount of health regenerated per Life tick when the power requirement was met.
- var/health_regen_rate = 3
- /// Lock health regeneration while this is not 0, decreases by 1 every Life tick.
- var/regen_lock = 0
- /// Tracking to prevent multiple EMPs in the same tick from instakilling a demon.
- var/emp_debounce = FALSE
-
- /// Controls whether the demon can move outside of cables. Toggled by a spell.
- var/can_exit_cable = FALSE
- /// Speed used while moving inside cables.
- var/inside_cable_speed = -0.5
- /// Speed used while moving outside cables. Can be upgraded.
- var/outside_cable_speed = 5
-
- /// The time it takes to hijack APCs and cyborgs.
- var/hijack_time = 30 SECONDS
-
- /// The color of light the demon emits. The range of the light is proportional to energy stored.
- var/glow_color = "#bbbb00"
-
- /// Area being controlled, should be maintained as long as the demon does not move outside a container (APC, object, robot, bot).
- var/area/controlling_area
- /// Inhabited cable, only maintained while on top of the cable.
- var/obj/structure/cable/current_cable
- /// Inhabited power source, maintained while inside, or while inside its area if it is an APC.
- var/obj/machinery/power/current_power
- /// Inhabited item, only items which can be used in rechargers can be hijacked. Only maintained while inside the item.
- var/obj/item/current_weapon
- /// Inhabited cyborg, only maintained while inside the cyborg.
- var/mob/living/silicon/robot/current_robot
- /// Inhabited bot, only maintained while inside the bot.
- var/mob/living/simple_animal/bot/current_bot
-
- /// Delay tracker for movement inside bots.
- var/bot_movedelay = 0
- /// A cyborg that has already been hijacked can be re-entered instantly.
- var/list/hijacked_robots = list()
-
- /// Images of cables currently being shown on the client.
- var/list/cable_images = list()
- /// Images of APCs currently being shown on the client.
- var/list/apc_images = list()
- /// List of all previously hijacked APCs.
- var/list/hijacked_apcs = list()
- /// Reference to the APC currently being hijacked.
- var/obj/machinery/power/apc/apc_being_hijacked
-
-/mob/living/simple_animal/demon/pulse_demon/Initialize(mapload)
- . = ..()
- if(!mapload)
- name += " ([rand(100, 999)])"
- real_name = name
-
- remove_from_all_data_huds()
- ADD_TRAIT(src, TRAIT_AI_UNTRACKABLE, PULSEDEMON_TRAIT)
- flags_2 |= RAD_NO_CONTAMINATE_2
-
- // For when someone steps on us
- var/static/list/loc_connections = list(
- COMSIG_ATOM_ENTERED = PROC_REF(on_atom_entered)
- )
- AddElement(/datum/element/connect_loc, loc_connections)
- // For when we move somewhere else
- RegisterSignal(src, COMSIG_MOVABLE_MOVED, PROC_REF(on_movable_moved))
-
- // drop demon onto ground if its loc is a non-turf and gets deleted
- RegisterSignal(src, COMSIG_PARENT_PREQDELETED, PROC_REF(deleted_handler))
-
- RegisterSignal(SSdcs, COMSIG_GLOB_CABLE_UPDATED, PROC_REF(cable_updated_handler))
-
- RegisterSignal(src, COMSIG_BODY_TRANSFER_TO, PROC_REF(make_pulse_antagonist))
- RegisterSignal(src, COMSIG_ATOM_EMP_ACT, PROC_REF(handle_emp))
-
- current_power = locate(/obj/machinery/power) in loc
- // in the case that both current_power and current_cable are null, the pulsedemon will die the next tick
- if(!current_power)
- current_cable = locate(/obj/structure/cable) in loc
- else
- forceMove(current_power)
- update_glow()
- playsound(get_turf(src), 'sound/effects/eleczap.ogg', 30, TRUE)
- give_spells()
- whisper_action.button_overlay_icon_state = "pulse_whisper"
- whisper_action.button_background_icon_state = "bg_pulsedemon"
-
-/mob/living/simple_animal/demon/pulse_demon/proc/deleted_handler(our_demon, force)
- SIGNAL_HANDLER
- // assume normal deletion if we're on a turf, otherwise deletion could be inherited from loc
- if(force || isnull(loc) || isturf(loc))
- return FALSE
- // if we did actually die, simple_animal/death will set del_on_death to FALSE before calling qdel
- if(!del_on_death)
- return FALSE
- exit_to_turf()
- return TRUE
-
-/mob/living/simple_animal/demon/pulse_demon/proc/cable_updated_handler(SSdcs, turf/T)
- SIGNAL_HANDLER
- if(cable_images[T])
- var/list/turf_images = cable_images[T]
- for(var/image/current_image in turf_images)
- client?.images -= current_image
- turf_images.Cut()
- else
- cable_images[T] = list()
-
- for(var/obj/structure/cable/C in T)
- var/image/cable_image = image(C, C, layer = ABOVE_LIGHTING_LAYER, dir = C.dir)
- cable_image.plane = ABOVE_LIGHTING_PLANE
- cable_images[T] += cable_image
- client?.images += cable_image
-
-/mob/living/simple_animal/demon/pulse_demon/proc/apc_deleted_handler(obj/machinery/power/apc/A, force)
- SIGNAL_HANDLER
- hijacked_apcs -= A
-
-/mob/living/simple_animal/demon/pulse_demon/Destroy()
- cable_images.Cut()
- apc_images.Cut()
-
- controlling_area = null
- current_bot = null
- current_cable = null
- current_power = null
- current_robot = null
- current_weapon = null
- apc_being_hijacked = null
- hijacked_apcs = null
- hijacked_robots = null
-
- return ..()
-
-/mob/living/simple_animal/demon/pulse_demon/Login()
- . = ..()
- update_cableview()
-
-/mob/living/simple_animal/demon/pulse_demon/proc/make_pulse_antagonist(demon)
- SIGNAL_HANDLER
- mind.assigned_role = SPECIAL_ROLE_DEMON
- mind.special_role = SPECIAL_ROLE_DEMON
- give_objectives()
-
-/mob/living/simple_animal/demon/pulse_demon/vv_edit_var(var_name, var_value)
- switch(var_name)
- if("glow_color")
- update_glow()
- if("charge")
- // automatically adjusts maxcharge to allow the new value
- adjust_charge(var_value - charge, TRUE)
- return TRUE
- return ..()
-
-/mob/living/simple_animal/demon/pulse_demon/forceMove(atom/destination)
- var/old_location = loc
- . = ..()
- current_weapon = null
- current_robot = null
- if(current_bot)
- current_bot.hijacked = FALSE
- current_bot = null
- if(istype(old_location, /obj/item/stock_parts/cell))
- var/obj/item/stock_parts/cell/C = old_location
- // only set rigged if there are no remaining demons in the cell
- C.rigged = !(locate(/mob/living/simple_animal/demon/pulse_demon) in old_location)
- if(istype(loc, /obj/item/stock_parts/cell))
- var/obj/item/stock_parts/cell/C = loc
- C.rigged = FALSE
-
-/mob/living/simple_animal/demon/pulse_demon/proc/give_objectives()
- if(!mind)
- return
- mind.wipe_memory()
- var/list/greeting = list()
- greeting.Add("You are a pulse demon.")
- greeting.Add("A being made of pure electrical energy, you travel through the station's wires and infest machinery.")
- greeting.Add("Navigate the station's power cables to find power sources to steal from, and hijack APCs to interact with their connected machines.")
- greeting.Add("If the wire or power source you're connected to runs out of power you'll start losing health and eventually die, but you are otherwise immune to damage.")
- greeting.Add("For more information, check the wiki page: ([GLOB.configuration.url.wiki_url]/index.php/Pulse_Demon)")
- for(var/datum/objective/new_obj in list(/datum/objective/pulse_demon/infest, /datum/objective/pulse_demon/drain, /datum/objective/pulse_demon/tamper))
- mind.add_mind_objective(new_obj)
- greeting.Add(mind.prepare_announce_objectives(FALSE))
- to_chat(src, chat_box_red(greeting.Join(" ")))
- SSticker.mode.traitors |= mind
-
-/mob/living/simple_animal/demon/pulse_demon/proc/give_spells()
- AddSpell(new /datum/spell/pulse_demon/cycle_camera)
- AddSpell(new /datum/spell/pulse_demon/toggle/do_drain(do_drain))
- AddSpell(new /datum/spell/pulse_demon/toggle/can_exit_cable(can_exit_cable))
- AddSpell(new /datum/spell/pulse_demon/cablehop)
- AddSpell(new /datum/spell/pulse_demon/emagtamper)
- AddSpell(new /datum/spell/pulse_demon/emp)
- AddSpell(new /datum/spell/pulse_demon/overload)
- AddSpell(new /datum/spell/pulse_demon/remotehijack)
- AddSpell(new /datum/spell/pulse_demon/remotedrain)
- AddSpell(new /datum/spell/pulse_demon/open_upgrades)
-
-/mob/living/simple_animal/demon/pulse_demon/get_status_tab_items()
- var/list/status_tab_data = ..()
- . = status_tab_data
- status_tab_data[++status_tab_data.len] = list("Energy:", "[format_si_suffix(charge)]J")
- status_tab_data[++status_tab_data.len] = list("Maximum Energy:", "[format_si_suffix(maxcharge)]J")
- status_tab_data[++status_tab_data.len] = list("Drained Energy:", "[format_si_suffix(charge_drained)]J")
- status_tab_data[++status_tab_data.len] = list("Hijacked APCs:", "[length(hijacked_apcs)]")
- status_tab_data[++status_tab_data.len] = list("Drain Rate:", "[format_si_suffix(power_drain_rate)]W")
- status_tab_data[++status_tab_data.len] = list("Hijack Time:", "[hijack_time / 10] seconds")
-
-/mob/living/simple_animal/demon/pulse_demon/dust()
- return death()
-
-/mob/living/simple_animal/demon/pulse_demon/gib()
- return death()
-
-/mob/living/simple_animal/demon/pulse_demon/death()
- var/turf/T = get_turf(src)
- do_sparks(rand(2, 4), FALSE, src)
- . = ..()
-
- var/heavy_radius = min(charge / 50000, 20)
- var/light_radius = min(charge / 25000, 25)
- empulse(T, heavy_radius, light_radius)
- playsound(T, pick(hurt_sounds), 30, TRUE)
-
-/mob/living/simple_animal/demon/pulse_demon/proc/exit_to_turf()
- var/turf/T = get_turf(src)
- current_power = null
- update_controlling_area()
- current_cable = null
- forceMove(T)
- Move(T)
- if(!current_cable && !current_power)
- var/datum/spell/pulse_demon/toggle/can_exit_cable/S = locate() in mob_spell_list
- if(!S.locked && !can_exit_cable)
- can_exit_cable = TRUE
- S.do_toggle(can_exit_cable)
- to_chat(src, "Your self-sustaining ability has automatically enabled itself to prevent death from having no connection!")
-
-/mob/living/simple_animal/demon/pulse_demon/proc/update_controlling_area(reset = FALSE)
- var/area/prev = controlling_area
- if(reset || current_power == null)
- controlling_area = null
- else if(isapc(current_power))
- var/obj/machinery/power/apc/A = current_power
- if(A in hijacked_apcs)
- controlling_area = A.apc_area
- else
- controlling_area = null
-
- if((!prev && !controlling_area) || (prev && controlling_area))
- return // only update icons when we get or no longer have ANY area
- for(var/datum/spell/pulse_demon/S in mob_spell_list)
- if(!S.action || S.locked)
- continue
- if(S.requires_area)
- S.action.UpdateButtons()
-
-// can enter an apc at all?
-/mob/living/simple_animal/demon/pulse_demon/proc/is_valid_apc(obj/machinery/power/apc/A)
- return istype(A) && !(A.stat & BROKEN) && !A.shorted
-
-/mob/living/simple_animal/demon/pulse_demon/Move(newloc)
- var/obj/machinery/power/new_power = locate(/obj/machinery/power) in newloc
- var/obj/structure/cable/new_cable = locate(/obj/structure/cable) in newloc
-
- if(QDELETED(new_power))
- new_power = null
- if(QDELETED(new_cable))
- new_cable = null
-
- if(istype(new_power, /obj/machinery/power/terminal))
- // entering a terminal is kinda useless and any working terminal will have a cable under it
- new_power = null
-
- if(isapc(new_power))
- var/obj/machinery/power/apc/A = new_power
- if(!is_valid_apc(new_power) || !A.terminal)
- new_power = null // don't enter an APC without a terminal or a broken APC, etc.
-
- // there's no electricity in space
- if(!new_cable && !new_power && (!can_exit_cable || isspaceturf(newloc)))
- return
-
- var/moved = ..()
-
- if(!new_cable && !new_power)
- if(can_exit_cable && moved)
- speed = outside_cable_speed
- else
- speed = inside_cable_speed
-
- if(moved)
- if(!is_under_tile() && prob(PULSEDEMON_PLATING_SPARK_CHANCE))
- do_sparks(rand(2, 4), FALSE, src)
-
- current_weapon = null
- current_robot = null
- if(current_bot)
- current_bot.hijacked = FALSE
- current_bot = null
-
- /*
- A few notes about this terrible proc, If you're wondering, I didn't write it but man I do NOT want to touch it
- 1. A lot of this 100% shouldn't be on move, that's just waiting for something bad to happen
- 2. Never, EVER directly call a do_after here, it will cause move to sleep which is awful
- */
- if(new_power)
- current_power = new_power
- current_cable = null
- forceMove(current_power) // we go inside the machine
- RegisterSignal(current_power, COMSIG_ATOM_EMP_ACT, PROC_REF(handle_emp), TRUE)
- playsound(src, 'sound/effects/eleczap.ogg', 15, TRUE)
- do_sparks(rand(2, 4), FALSE, src)
- if(isapc(current_power))
- if(current_power in hijacked_apcs)
- update_controlling_area()
- else
- INVOKE_ASYNC(src, PROC_REF(try_hijack_apc), current_power)
- else if(new_cable)
- current_cable = new_cable
- if(current_power)
- UnregisterSignal(current_power, COMSIG_ATOM_EMP_ACT)
- current_power = null
- update_controlling_area()
- if(!isturf(loc))
- loc = get_turf(newloc)
- if(!moved)
- forceMove(newloc)
- else if(moved)
- current_cable = null
- current_power = null
- update_controlling_area()
-
-// signal to replace relaymove where or when? // Never, actually just manage your code instead
-/obj/machinery/power/relaymove(mob/user, dir)
- if(!ispulsedemon(user))
- return ..()
-
- var/mob/living/simple_animal/demon/pulse_demon/demon = user
- var/turf/T = get_turf(src)
- var/turf/T2 = get_step(T, dir)
- if(demon.can_exit_cable || locate(/obj/structure/cable) in T2)
- playsound(src, 'sound/effects/eleczap.ogg', 15, TRUE)
- do_sparks(rand(2, 4), FALSE, src)
- user.forceMove(T)
- if(isapc(src))
- demon.update_controlling_area(TRUE)
-
-/mob/living/simple_animal/demon/pulse_demon/proc/adjust_charge(amount, adjust_max = FALSE)
- if(!amount)
- return FALSE
- if(adjust_max)
- maxcharge = max(maxcharge, charge + amount)
- var/orig = charge
- charge = min(maxcharge, charge + amount)
- var/realdelta = charge - orig
- if(!realdelta)
- return FALSE
- if(realdelta > 0)
- charge_drained += realdelta
-
- update_glow()
- for(var/datum/spell/pulse_demon/S in mob_spell_list)
- if(!S.action || S.locked || !S.cast_cost)
- continue
- var/dist = S.cast_cost - orig
- // only update icon if the amount is actually enough to change a spell's availability
- if(dist == 0 || (dist > 0 && realdelta >= dist) || (dist < 0 && realdelta <= dist))
- S.action.UpdateButtons()
- return realdelta
-
-// logarithmic scale for glow strength, see table:
- // 1.5 <= 25k
- // 2 at 50k
- // 2.5 at 100k
- // 3 at 200k
- // 3.5 at 400k, etc
-/mob/living/simple_animal/demon/pulse_demon/proc/update_glow()
- var/range = 2 + (log(2, charge + 1) - log(2, 50000)) / 2
- range = max(range, 1.5)
- set_light(range, 2, glow_color)
-
-/mob/living/simple_animal/demon/pulse_demon/proc/drain_APC(obj/machinery/power/apc/A, multiplier = 1)
- if(A.being_hijacked)
- return PULSEDEMON_SOURCE_DRAIN_INVALID
- //CELLRATE is the conversion ratio between a watt tick and powercell energy storage units
- var/amount_to_drain = clamp(A.cell.charge / GLOB.CELLRATE, 0, power_drain_rate * WATT_TICK_TO_JOULE * multiplier)
- A.cell.use(min(amount_to_drain * GLOB.CELLRATE, maxcharge - charge)) // calculated seperately because the apc charge multiplier shouldn't affect the actual consumption
- return adjust_charge(amount_to_drain * PULSEDEMON_APC_CHARGE_MULTIPLIER)
-
-/mob/living/simple_animal/demon/pulse_demon/proc/drain_SMES(obj/machinery/power/smes/S, multiplier = 1)
- //CELLRATE is the conversion ratio between a watt tick and powercell energy storage units.
- var/amount_to_drain = clamp(S.charge / GLOB.CELLRATE, 0, power_drain_rate * WATT_TICK_TO_JOULE * multiplier * PULSEDEMON_SMES_DRAIN_MULTIPLIER)
- var/drained = adjust_charge(amount_to_drain)
- S.charge -= drained * GLOB.CELLRATE
- return drained
-
-/mob/living/simple_animal/demon/pulse_demon/Life(seconds, times_fired)
- . = ..()
-
- var/got_power = FALSE
- if(current_cable)
- if(current_cable.get_available_power() >= power_per_regen)
- current_cable.add_power_demand(power_per_regen)
- got_power = TRUE
-
- var/excess = initial(power_per_regen) - power_per_regen
- if(excess > 0 && current_cable.get_available_power() >= excess && do_drain)
- adjust_charge(excess)
- current_cable.add_power_demand(excess)
- else if(current_power)
- if(isapc(current_power) && loc == current_power && do_drain)
- if(drain_APC(current_power) > power_per_regen)
- got_power = TRUE
- else if(istype(current_power, /obj/machinery/power/smes) && do_drain)
- if(drain_SMES(current_power) > power_per_regen)
- got_power = TRUE
- // try to take power from the powernet if the APC or SMES is empty (or we're not /really/ in the APC)
- if(!got_power && current_power.get_available_power() >= power_per_regen)
- current_power.consume_direct_power(power_per_regen)
- got_power = TRUE
- else if(!can_exit_cable)
- death()
- return
-
- if(got_power)
- if(regen_lock <= 0)
- adjustHealth(-health_regen_rate)
- clear_alert(ALERT_CATEGORY_NOPOWER)
- else
- var/rate = health_loss_rate
- if(!current_cable && !current_power && can_exit_cable)
- // 2 * initial_rate - upgrade_level
- rate += initial(health_loss_rate)
- adjustHealth(rate)
- throw_alert(ALERT_CATEGORY_NOPOWER, /atom/movable/screen/alert/pulse_nopower)
-
- if(regen_lock > 0)
- if(--regen_lock == 0)
- clear_alert(ALERT_CATEGORY_NOREGEN)
-
-/mob/living/simple_animal/demon/pulse_demon/proc/gen_speech_name()
- . = ""
- for(var/i = 1 to 10)
- . += pick("!", "@", "#", "$", "%", "^", "&", "*")
-
-/mob/living/simple_animal/demon/pulse_demon/say(message, verb, sanitize = TRUE, ignore_speech_problems = FALSE, ignore_atmospherics = FALSE, ignore_languages = FALSE)
- if(client && check_mute(client.ckey, MUTE_IC))
- to_chat(src, "You cannot speak in IC (Muted).")
- return FALSE
-
- if(sanitize)
- message = sanitize_for_ic(trim(message))
-
- if(stat)
- if(stat == DEAD)
- return say_dead(message)
- return FALSE
-
- if(current_robot)
- var/turf/T = get_turf(src)
- log_say("[key_name_admin(src)] (@[T.x], [T.y], [T.z]) made [current_robot]([key_name_admin(current_robot)]) say: [message]")
- log_admin("[key_name_admin(src)] made [key_name_admin(current_robot)] say: [message]")
- message_admins("[key_name_admin(src)] made [key_name_admin(current_robot)] say: [message]")
- // don't sanitize again
- current_robot.say(message, null, FALSE, ignore_speech_problems, ignore_atmospherics, ignore_languages)
- return TRUE
-
- var/message_mode = parse_message_mode(message, "headset")
-
- if(message_mode)
- if(message_mode == "headset")
- message = copytext(message, 2)
- else
- message = copytext(message, 3)
-
- message = trim_left(message)
-
- var/list/message_pieces = list()
- if(ignore_languages)
- message_pieces = message_to_multilingual(message)
- else
- message_pieces = parse_languages(message)
-
- // hivemind languages
- if(istype(message_pieces, /datum/multilingual_say_piece))
- var/datum/multilingual_say_piece/S = message_pieces
- S.speaking.broadcast(src, S.message)
- return TRUE
-
- if(!LAZYLEN(message_pieces))
- . = FALSE
- CRASH("Message failed to generate pieces. [message] - [json_encode(message_pieces)]")
-
- create_log(SAY_LOG, "[message_mode ? "([message_mode])" : ""] '[message]'")
-
- playsound(get_turf(src), pick(speech_sounds), 30, TRUE)
- if(istype(loc, /obj/item/radio))
- var/obj/item/radio/R = loc
- name = gen_speech_name()
- R.talk_into(src, message_pieces, message_mode, verbage = verb)
- name = real_name
- return TRUE
- else if(istype(loc, /obj/machinery/hologram/holopad))
- var/obj/machinery/hologram/holopad/H = loc
- name = "[H]"
- for(var/mob/M as anything in get_mobs_in_view(7, H))
- M.hear_say(message_pieces, verb, FALSE, src)
- name = real_name
- return TRUE
-
- emote("me", message = "[pick(emote_hear)]")
- return TRUE
-
-/mob/living/simple_animal/demon/pulse_demon/update_runechat_msg_location()
- if(istype(loc, /obj/machinery/hologram/holopad))
- runechat_msg_location = loc.UID()
- else
- return ..()
-
-/mob/living/simple_animal/demon/pulse_demon/visible_message(message, self_message, blind_message, chat_message_type)
- // overriden because pulse demon is quite often in non-turf locs, and /mob/visible_message acts differently there
- for(var/mob/M as anything in get_mobs_in_view(7, src))
- if(M.see_invisible < invisibility)
- continue //can't view the invisible
- var/msg = message
- if(self_message && M == src)
- msg = self_message
- M.show_message(msg, EMOTE_VISIBLE, blind_message, EMOTE_AUDIBLE, chat_message_type = MESSAGE_TYPE_LOCALCHAT)
-
-/mob/living/simple_animal/demon/pulse_demon/has_internal_radio_channel_access(mob/user, list/req_one_accesses)
- return has_access(list(), req_one_accesses, get_all_accesses())
-
-/mob/living/simple_animal/demon/pulse_demon/proc/try_hijack_apc(obj/machinery/power/apc/A, remote = FALSE)
- // one APC per pulse demon, one pulse demon per APC, no duplicate APCs
- if(!is_valid_apc(A) || (A in hijacked_apcs) || apc_being_hijacked || A.being_hijacked)
- return FALSE
-
- to_chat(src, "You are now attempting to hijack [A], this will take approximately [hijack_time / 10] seconds.")
- apc_being_hijacked = A
- A.being_hijacked = TRUE
- A.update_icon()
- if(do_after(src, hijack_time, target = A))
- if(is_valid_apc(A))
- finish_hijack_apc(A, remote)
- else
- to_chat(src, "Failed to hijack [src].")
- apc_being_hijacked = null
- A.being_hijacked = FALSE
- A.update_icon()
-
-// Basically this proc gives you more max charge per apc you have hijacked
-// Looks weird but it gets the job done
-/mob/living/simple_animal/demon/pulse_demon/proc/calc_maxcharge(hijacked_apcs)
- if(!hijacked_apcs) // No APCs hijacked? No extra charge
- return 1000
- return 20000 * clamp(hijacked_apcs, 0, 20) + 500000 * clamp(hijacked_apcs - 20, 0, 30) + 1000000 * clamp(hijacked_apcs - 50, 0, 50) + 500000000 * max(0, hijacked_apcs - 100)
-
-/mob/living/simple_animal/demon/pulse_demon/proc/finish_hijack_apc(obj/machinery/power/apc/A, remote = FALSE)
- var/image/apc_image = image('icons/obj/power.dmi', A, "apcemag", ABOVE_LIGHTING_LAYER, A.dir)
- apc_image.plane = ABOVE_LIGHTING_PLANE
- LAZYADD(apc_images[get_turf(A)], apc_image)
- client.images += apc_image
-
- hijacked_apcs += A
- RegisterSignal(A, COMSIG_PARENT_QDELETING, PROC_REF(apc_deleted_handler))
- if(!remote)
- update_controlling_area()
- maxcharge = calc_maxcharge(length(hijacked_apcs)) + (maxcharge - calc_maxcharge(length(hijacked_apcs) - 1))
- to_chat(src, "Hijacking complete! You now control [length(hijacked_apcs)] APCs.")
-
-/mob/living/simple_animal/demon/pulse_demon/proc/on_atom_entered(datum/source, atom/movable/entered)
- SIGNAL_HANDLER // COMSIG_ATOM_ENTERED
- try_cross_shock(entered)
-
-/mob/living/simple_animal/demon/pulse_demon/proc/on_movable_moved(datum/source, old_location, direction, forced)
- SIGNAL_HANDLER // COMSIG_MOVABLE_MOVED
- if(is_under_tile())
- return
- for(var/mob/living/mob in loc)
- try_shock_mob(mob)
-
-/mob/living/simple_animal/demon/pulse_demon/proc/try_cross_shock(atom/movable/A)
- if(!isliving(A) || is_under_tile())
- return
- var/mob/living/L = A
- try_shock_mob(L)
-
-/mob/living/simple_animal/demon/pulse_demon/proc/try_shock_mob(mob/living/L, siemens_coeff = 1)
- var/dealt = 0
- if(current_cable && current_cable.powernet && current_cable.powernet.available_power)
- // returns used energy, not damage dealt, but ez conversion with /20
- dealt = electrocute_mob(L, current_cable.powernet, src, siemens_coeff) / 20
- else if(charge >= 1000)
- dealt = L.electrocute_act(30, src, siemens_coeff)
- adjust_charge(-1000)
- if(dealt > 0)
- do_sparks(rand(2, 4), FALSE, src)
- add_attack_logs(src, L, "shocked ([dealt] damage)")
-
-/mob/living/simple_animal/demon/pulse_demon/proc/is_under_tile()
- var/turf/T = get_turf(src)
- return T.intact || HAS_TRAIT(T, TRAIT_TURF_COVERED)
-
-// cable (and hijacked APC) view helper
-/mob/living/simple_animal/demon/pulse_demon/proc/update_cableview()
- if(!client)
- return
-
- // clear out old images
- for(var/image/current_image in cable_images + apc_images)
- client.images -= current_image
-
- var/turf/T = get_turf(src)
-
- // regenerate for all cables on our (or our holder's) z-level
- cable_images.Cut()
- for(var/datum/regional_powernet/P in SSmachines.powernets)
- for(var/obj/structure/cable/C in P.cables)
- var/turf/cable_turf = get_turf(C)
- if(T.z != cable_turf.z)
- break // skip entire powernet if it's off z-level
-
- var/image/cable_image = image(C, C, layer = ABOVE_LIGHTING_LAYER, dir = C.dir)
- // good visibility here
- cable_image.plane = ABOVE_LIGHTING_PLANE
- LAZYADD(cable_images[cable_turf], cable_image)
- client.images += cable_image
-
- // same for hijacked APCs
- apc_images.Cut()
- for(var/obj/machinery/power/apc/A in hijacked_apcs)
- var/turf/apc_turf = get_turf(A)
- if(T.z != apc_turf.z)
- continue
- // parent of image is the APC, not the turf because of how clicking on images works
- var/image/apc_image = image('icons/obj/power.dmi', A, "apcemag", ABOVE_LIGHTING_LAYER, A.dir)
- apc_image.plane = ABOVE_LIGHTING_PLANE
- LAZYADD(apc_images[apc_turf], apc_image)
- client.images += apc_image
-
-/mob/living/simple_animal/demon/pulse_demon/proc/handle_emp(datum/source, severity)
- SIGNAL_HANDLER
- if(emp_debounce)
- return
- visible_message("[src] [pick("fizzles", "wails", "flails")] in anguish!")
- playsound(get_turf(src), pick(hurt_sounds), 30, TRUE)
- throw_alert(ALERT_CATEGORY_NOREGEN, /atom/movable/screen/alert/pulse_noregen)
- switch(severity)
- if(EMP_LIGHT)
- adjustHealth(round(max(initial(health) / 4, round(maxHealth / 8))))
- regen_lock = 3
- if(EMP_HEAVY)
- adjustHealth(round(max(initial(health) / 3, round(maxHealth / 6))))
- regen_lock = 5
- emp_debounce = TRUE
- addtimer(VARSET_CALLBACK(src, emp_debounce, FALSE), 0.1 SECONDS, TIMER_UNIQUE | TIMER_OVERRIDE)
-
-/mob/living/simple_animal/demon/pulse_demon/proc/try_attack_mob(mob/living/L)
- if(!is_under_tile() && L != src)
- do_attack_animation(L)
- try_shock_mob(L)
-
-/mob/living/simple_animal/demon/pulse_demon/UnarmedAttack(atom/A)
- if(isliving(A))
- try_attack_mob(A)
- else if(isitem(A) && !is_under_tile())
- var/obj/item/O = A
- var/obj/item/stock_parts/cell/C = O.get_cell()
- if(C?.charge)
- C.use(min(C.charge, power_drain_rate))
- adjust_charge(min(C.charge, power_drain_rate))
- visible_message("[src] touches [O] and drains its power!", "You touch [O] and drain it's power!")
-
-/mob/living/simple_animal/demon/pulse_demon/attack_hand(mob/living/carbon/human/M)
- if(is_under_tile())
- to_chat(M, "You can't interact with something that's under the floor!")
- return
- switch(M.intent)
- if(INTENT_HELP)
- visible_message("[M] [response_help] [src].")
- if(INTENT_DISARM, INTENT_GRAB)
- visible_message("[M] [response_disarm] [src].")
- if(INTENT_HELP)
- visible_message("[M] [response_harm] [src].")
- try_attack_mob(M)
-
-/mob/living/simple_animal/demon/pulse_demon/attackby__legacy__attackchain(obj/item/O, mob/living/user)
- if(is_under_tile())
- to_chat(user, "You can't interact with something that's under the floor!")
- return
- var/obj/item/stock_parts/cell/C = O.get_cell()
- if(C && C.charge)
- C.use(min(C.charge, power_drain_rate))
- adjust_charge(min(C.charge, power_drain_rate))
- to_chat(user, "You touch [src] with [O] and [src] drains it!")
- to_chat(src, "[user] touches you with [O] and you drain its power!")
- visible_message("[O] goes right through [src].")
- try_shock_mob(user, O.siemens_coefficient)
-
-/mob/living/simple_animal/demon/pulse_demon/ex_act()
- return
-
-/mob/living/simple_animal/demon/pulse_demon/CanPass(atom/movable/mover, border_dir)
- . = ..()
- if(istype(mover, /obj/item/projectile/ion))
- return FALSE
-
-/mob/living/simple_animal/demon/pulse_demon/bullet_act(obj/item/projectile/proj)
- if(proj.damage_type == BURN)
- regen_lock = max(regen_lock, 1)
- return ..()
- else
- visible_message("[proj] goes right through [src]!")
-
-/mob/living/simple_animal/demon/pulse_demon/electrocute_act(shock_damage, source, siemens_coeff, flags)
- return
-
-/mob/living/simple_animal/demon/pulse_demon/blob_act(obj/structure/blob/B)
- return // will likely end up dying if the blob cuts its wires anyway
-
-/mob/living/simple_animal/demon/pulse_demon/narsie_act()
- return // you can't turn electricity into a harvester
-
-/mob/living/simple_animal/demon/pulse_demon/get_access()
- return get_all_accesses()
-
-/mob/living/simple_animal/demon/pulse_demon/IsAdvancedToolUser()
- return TRUE // interacting with machines
-
-/mob/living/simple_animal/demon/pulse_demon/can_be_pulled()
- return FALSE
-
-/mob/living/simple_animal/demon/pulse_demon/can_buckle()
- return FALSE
-
-/mob/living/simple_animal/demon/pulse_demon/hitby(atom/movable/AM, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum)
- return
-
-/mob/living/simple_animal/demon/pulse_demon/experience_pressure_difference(flow_x, flow_y)
- return // Immune to gas flow.
-
-/mob/living/simple_animal/demon/pulse_demon/singularity_pull()
- return
-
-/mob/living/simple_animal/demon/pulse_demon/mob_negates_gravity()
- return TRUE
-
-/mob/living/simple_animal/demon/pulse_demon/mob_has_gravity()
- return TRUE
-
-/mob/living/simple_animal/demon/pulse_demon/can_remote_apc_interface(obj/machinery/power/apc/ourapc)
- if(ourapc.hacked_by_ruin_AI || ourapc.malfai || ourapc.malfhack)
- return FALSE
- return TRUE
-
-/mob/living/simple_animal/demon/pulse_demon/adjustHealth(amount, updating_health)
- if(amount > 0) // This damages the pulse demon
- return ..()
-
- if(!ismachinery(loc))
- if(health >= (maxHealth / 2))
- amount = 0
- else
- amount = clamp(amount, -((maxHealth / 2) - health), 0)
- amount = round(amount, 1)
- return ..()
-
-/obj/item/organ/internal/heart/demon/pulse
- name = "perpetual pacemaker"
- desc = "It still beats furiously, thousands of bright lights shine within it."
- color = COLOR_YELLOW
-
-/obj/item/organ/internal/heart/demon/pulse/Initialize(mapload)
- . = ..()
- set_light(13, 2, "#bbbb00")
-
-/obj/item/organ/internal/heart/demon/pulse/attack_self__legacy__attackchain(mob/living/user)
- . = ..()
- user.drop_item()
- insert(user)
-
-/obj/item/organ/internal/heart/demon/pulse/insert(mob/living/carbon/M, special, dont_remove_slot)
- . = ..()
- M.AddComponent(/datum/component/cross_shock, 30, 500, 2 SECONDS)
- ADD_TRAIT(M, TRAIT_SHOCKIMMUNE, UNIQUE_TRAIT_SOURCE(src))
- M.set_light(3, 2, "#bbbb00")
-
-/obj/item/organ/internal/heart/demon/pulse/remove(mob/living/carbon/M, special)
- . = ..()
- REMOVE_TRAIT(M, TRAIT_SHOCKIMMUNE, UNIQUE_TRAIT_SOURCE(src))
- M.remove_light()
-
-/obj/item/organ/internal/heart/demon/pulse/on_life()
- if(!owner)
- return
- for(var/obj/item/stock_parts/cell/cell_to_charge in owner.GetAllContents())
- var/newcharge = min(0.05 * cell_to_charge.maxcharge + cell_to_charge.charge, cell_to_charge.maxcharge)
- if(cell_to_charge.charge < newcharge)
- cell_to_charge.charge = newcharge
- if(isobj(cell_to_charge.loc))
- var/obj/cell_location = cell_to_charge.loc
- cell_location.update_icon() //update power meters and such
- cell_to_charge.update_icon()
-
-/atom/movable/screen/alert/pulse_nopower
- name = "No Power"
- desc = "You are not connected to a cable or machine and are losing health!"
- icon_state = "pd_nopower"
-
-/atom/movable/screen/alert/pulse_noregen
- name = "Regeneration Stalled"
- desc = "You've been EMP'd and cannot regenerate health!"
- icon_state = "pd_noregen"
-
-#undef ALERT_CATEGORY_NOPOWER
-#undef ALERT_CATEGORY_NOREGEN
-
-#undef PULSEDEMON_PLATING_SPARK_CHANCE
-#undef PULSEDEMON_APC_CHARGE_MULTIPLIER
-#undef PULSEDEMON_SMES_DRAIN_MULTIPLIER
diff --git a/code/game/gamemodes/miniantags/pulsedemon/pulsedemon_abilities.dm b/code/game/gamemodes/miniantags/pulsedemon/pulsedemon_abilities.dm
deleted file mode 100644
index 97c86faf060d4..0000000000000
--- a/code/game/gamemodes/miniantags/pulsedemon/pulsedemon_abilities.dm
+++ /dev/null
@@ -1,496 +0,0 @@
-#define PULSEDEMON_REMOTE_DRAIN_MULTIPLIER 5
-
-#define PD_UPGRADE_HIJACK_SPEED "Speed"
-#define PD_UPGRADE_DRAIN_SPEED "Absorption"
-#define PD_UPGRADE_HEALTH_LOSS "Endurance"
-#define PD_UPGRADE_HEALTH_REGEN "Recovery"
-#define PD_UPGRADE_MAX_HEALTH "Strength"
-#define PD_UPGRADE_HEALTH_COST "Efficiency"
-#define PD_UPGRADE_MAX_CHARGE "Capacity"
-
-/datum/spell/pulse_demon
- clothes_req = FALSE
- antimagic_flags = NONE
- action_background_icon_state = "bg_pulsedemon"
- var/locked = TRUE
- var/unlock_cost = 1 KJ
- var/cast_cost = 1 KJ
- var/upgrade_cost = 1 KJ
- var/requires_area = FALSE
- base_cooldown = 20 SECONDS
- level_max = 4
-
-/datum/spell/pulse_demon/New()
- . = ..()
- update_info()
-
-/datum/spell/pulse_demon/proc/update_info()
- if(locked)
- name = "[initial(name)] (Locked) ([format_si_suffix(unlock_cost)]W)"
- desc = "[initial(desc)] It costs [format_si_suffix(unlock_cost)]W to unlock. Alt-Click this spell to unlock it."
- else
- name = "[initial(name)][cast_cost == 0 ? "" : " ([format_si_suffix(cast_cost)]W)"]"
- desc = "[initial(desc)][spell_level == level_max ? "" : " It costs [format_si_suffix(upgrade_cost)]W to upgrade. Alt-Click this spell to upgrade it."]"
- action.name = name
- action.desc = desc
- action.UpdateButtons()
-
-/datum/spell/pulse_demon/can_cast(mob/living/simple_animal/demon/pulse_demon/user, charge_check, show_message)
- if(!..())
- return FALSE
- if(!istype(user))
- return FALSE
- if(locked)
- if(show_message)
- to_chat(user, "This ability is locked! Alt-click the button to purchase this ability.")
- to_chat(user, "It costs [format_si_suffix(unlock_cost)]W to unlock.")
- return FALSE
- if(user.charge < cast_cost)
- if(show_message)
- to_chat(user, "You do not have enough charge to use this ability!")
- to_chat(user, "It costs [format_si_suffix(cast_cost)]W to use.")
- return FALSE
- if(requires_area && !user.controlling_area)
- if(show_message)
- to_chat(user, "You need to be controlling an area to use this ability!")
- return FALSE
- return TRUE
-
-/datum/spell/pulse_demon/cast(list/targets, mob/living/simple_animal/demon/pulse_demon/user)
- if(!istype(user) || locked || user.charge < cast_cost || !length(targets))
- return FALSE
- if(requires_area && !user.controlling_area)
- return FALSE
- if(requires_area && user.controlling_area != get_area(targets[1]))
- to_chat(user, "You can only use this ability in your controlled area!")
- return FALSE
- if(try_cast_action(user, targets[1]))
- user.adjust_charge(-cast_cost)
- return TRUE
- else
- revert_cast(user)
- return FALSE
-
-/datum/spell/pulse_demon/create_new_targeting()
- return new /datum/spell_targeting/clicked_atom
-
-/datum/spell/pulse_demon/proc/try_cast_action(mob/living/simple_animal/demon/pulse_demon/user, atom/target)
- return FALSE
-
-// handles purchasing and upgrading abilities
-/datum/spell/pulse_demon/AltClick(mob/living/simple_animal/demon/pulse_demon/user)
- if(!istype(user))
- return
-
- if(locked)
- if(user.charge >= unlock_cost)
- user.adjust_charge(-unlock_cost)
- locked = FALSE
- to_chat(user, "You have unlocked [initial(name)]!")
-
- if(cast_cost > 0)
- to_chat(user, "It costs [format_si_suffix(cast_cost)]W to use once.")
- if(level_max > 0 && spell_level < level_max)
- to_chat(user, "It will cost [format_si_suffix(upgrade_cost)]W to upgrade.")
-
- update_info()
- else
- to_chat(user, "You cannot afford this ability! It costs [format_si_suffix(unlock_cost)]W to unlock.")
- else
- if(spell_level >= level_max)
- to_chat(user, "You have already fully upgraded this ability!")
- else if(user.charge >= upgrade_cost)
- user.adjust_charge(-upgrade_cost)
- spell_level = min(spell_level + 1, level_max)
- upgrade_cost = round(initial(upgrade_cost) * (1.5 ** spell_level))
- do_upgrade(user)
-
- if(spell_level == level_max)
- to_chat(user, "You have fully upgraded [initial(name)]!")
- else
- to_chat(user, "The next upgrade will cost [format_si_suffix(upgrade_cost)]W to unlock.")
-
- update_info()
- else
- to_chat(user, "You cannot afford to upgrade this ability! It costs [format_si_suffix(upgrade_cost)]W to upgrade.")
-
-/datum/spell/pulse_demon/proc/do_upgrade(mob/living/simple_animal/demon/pulse_demon/user)
- cooldown_handler.recharge_duration = round(base_cooldown / (1.5 ** spell_level))
- to_chat(user, "You have upgraded [initial(name)] to level [spell_level + 1], it now takes [cooldown_handler.recharge_duration / 10] seconds to recharge.")
-
-/datum/spell/pulse_demon/cablehop
- name = "Cable Hop"
- desc = "Jump to another cable in view."
- action_icon_state = "pd_cablehop"
- unlock_cost = 15 KJ
- cast_cost = 5 KJ
- upgrade_cost = 75 KJ
-
-/datum/spell/pulse_demon/cablehop/try_cast_action(mob/living/simple_animal/demon/pulse_demon/user, atom/target)
- var/turf/O = get_turf(user)
- var/turf/T = get_turf(target)
- var/obj/structure/cable/C = locate(/obj/structure/cable) in T
- if(!istype(C))
- to_chat(user, "No cable found!")
- return FALSE
- if(get_dist(O, T) > 15) //Some extra range to account for them possessing machines away from their APC, but blocking demons from using a camera console to zap across the station.
- to_chat(user, "That cable is too far away!")
- return FALSE
- playsound(T, 'sound/magic/lightningshock.ogg', 50, TRUE)
- O.Beam(target, icon_state = "lightning[rand(1, 12)]", icon = 'icons/effects/effects.dmi', time = 1 SECONDS)
- for(var/turf/working in get_line(O, T))
- for(var/mob/living/L in working)
- if(!electrocute_mob(L, C.powernet, user)) // give a little bit of non-lethal counterplay against insuls
- L.Jitter(5 SECONDS)
- L.apply_status_effect(STATUS_EFFECT_DELAYED, 1 SECONDS, CALLBACK(L, TYPE_PROC_REF(/mob/living, KnockDown), 5 SECONDS), COMSIG_LIVING_CLEAR_STUNS)
- user.forceMove(T)
- user.Move(T)
- return TRUE
-
-/datum/spell/pulse_demon/emagtamper
- name = "Electromagnetic Tamper"
- desc = "Unlocks hidden programming in machines. Must be inside a hijacked APC to use."
- action_icon_state = "pd_emag"
- unlock_cost = 50 KJ
- cast_cost = 20 KJ
- upgrade_cost = 200 KJ
- requires_area = TRUE
-
-/datum/spell/pulse_demon/emagtamper/try_cast_action(mob/living/simple_animal/demon/pulse_demon/user, atom/target)
- to_chat(user, "You attempt to tamper with [target]!")
- target.emag_act(user)
- return TRUE
-
-/datum/spell/pulse_demon/emp
- name = "Electromagnetic Pulse"
- desc = "Creates an EMP where you click. Be careful not to use it on yourself!"
- action_icon_state = "pd_emp"
- unlock_cost = 50 KJ
- cast_cost = 10 KJ
- upgrade_cost = 200 KJ
- requires_area = TRUE
-
-/datum/spell/pulse_demon/emp/try_cast_action(mob/living/simple_animal/demon/pulse_demon/user, atom/target)
- to_chat(user, "You attempt to EMP [target]!")
- empulse(get_turf(target), 1, 1)
- return TRUE
-
-/datum/spell/pulse_demon/overload
- name = "Overload Machine"
- desc = "Overloads a machine, causing it to explode."
- action_icon_state = "pd_overload"
- unlock_cost = 300 KJ
- cast_cost = 50 KJ
- upgrade_cost = 500 KJ
- requires_area = TRUE
-
-/datum/spell/pulse_demon/overload/try_cast_action(mob/living/simple_animal/demon/pulse_demon/user, atom/target)
- var/obj/machinery/M = target
- if(!istype(M))
- to_chat(user, "That is not a machine.")
- return FALSE
- if(target.flags_2 & NO_MALF_EFFECT_2)
- to_chat(user, "That machine cannot be overloaded.")
- return FALSE
- target.audible_message("You hear a loud electrical buzzing sound coming from [target]!")
- addtimer(CALLBACK(src, PROC_REF(detonate), M), 5 SECONDS)
- return TRUE
-
-/datum/spell/pulse_demon/overload/proc/detonate(obj/machinery/target)
- if(!QDELETED(target))
- explosion(get_turf(target), 0, 1, 1, 0)
- if(!QDELETED(target))
- qdel(target)
-
-/datum/spell/pulse_demon/remotehijack
- name = "Remote Hijack"
- desc = "Remotely hijacks an APC."
- action_icon_state = "pd_remotehack"
- unlock_cost = 15 KJ
- cast_cost = 10 KJ
- level_max = 0
- base_cooldown = 3 SECONDS // you have to wait for the regular hijack time anyway
-
-/datum/spell/pulse_demon/remotehijack/try_cast_action(mob/living/simple_animal/demon/pulse_demon/user, atom/target)
- var/obj/machinery/power/apc/A = target
- if(!istype(A))
- to_chat(user, "That is not an APC.")
- return FALSE
- if(!user.try_hijack_apc(A, TRUE))
- to_chat(user, "You cannot hijack that APC right now!")
- return TRUE
-
-/datum/spell/pulse_demon/remotedrain
- name = "Remote Drain"
- desc = "Remotely drains a power source."
- action_icon_state = "pd_remotedrain"
- unlock_cost = 5 KJ
- cast_cost = 100
- upgrade_cost = 100 KJ
-
-/datum/spell/pulse_demon/remotedrain/try_cast_action(mob/living/simple_animal/demon/pulse_demon/user, atom/target)
- if(isapc(target))
- var/drained = user.drain_APC(target, PULSEDEMON_REMOTE_DRAIN_MULTIPLIER)
- if(drained == PULSEDEMON_SOURCE_DRAIN_INVALID)
- to_chat(user, "This APC is being hijacked, you cannot drain from it right now.")
- else
- to_chat(user, "You drain [format_si_suffix(drained)]W from [target].")
- else if(istype(target, /obj/machinery/power/smes))
- var/drained = user.drain_SMES(target, PULSEDEMON_REMOTE_DRAIN_MULTIPLIER)
- to_chat(user, "You drain [format_si_suffix(drained)]W from [target].")
- else
- to_chat(user, "That is not a valid source.")
- return FALSE
- return TRUE
-
-/datum/spell/pulse_demon/toggle
- base_cooldown = 0
- cast_cost = 0
- create_attack_logs = FALSE
- var/base_message = "see messages you shouldn't!"
-
-/datum/spell/pulse_demon/toggle/New(initstate = FALSE)
- . = ..()
- do_toggle(initstate, null)
-
-/datum/spell/pulse_demon/toggle/create_new_targeting()
- return new /datum/spell_targeting/self
-
-/datum/spell/pulse_demon/toggle/proc/do_toggle(varstate, mob/user)
- if(action)
- action.button_background_icon_state = varstate ? action_background_icon_state : "[action_background_icon_state]_disabled"
- action.UpdateButtons()
- if(user)
- to_chat(user, "You will [varstate ? "now" : "no longer"] [base_message]")
- return varstate
-
-/datum/spell/pulse_demon/toggle/do_drain
- name = "Toggle Draining"
- desc = "Toggle whether you drain charge from power sources."
- base_message = "drain charge from power sources."
- action_icon_state = "pd_toggle_steal"
- locked = FALSE
- level_max = 0
-
-/datum/spell/pulse_demon/toggle/do_drain/try_cast_action(mob/living/simple_animal/demon/pulse_demon/user, atom/target)
- user.do_drain = do_toggle(!user.do_drain, user)
- return TRUE
-
-/datum/spell/pulse_demon/toggle/do_drain/AltClick(mob/living/simple_animal/demon/pulse_demon/user)
- if(!istype(user))
- return
-
- var/amount = text2num(input(user, "Input a value between 1 and [user.max_drain_rate]. 0 will reset it to the maximum.", "Drain Speed Setting"))
- if(amount == null || amount < 0)
- to_chat(user, "Invalid input. Drain speed has not been modified.")
- return
-
- if(amount == 0)
- amount = user.max_drain_rate
- user.power_drain_rate = amount
- to_chat(user, "Drain speed has been set to [format_si_suffix(user.power_drain_rate)]W per second.")
-
-/datum/spell/pulse_demon/toggle/can_exit_cable
- name = "Toggle Self-Sustaining"
- desc = "Toggle whether you can move outside of cables or power sources."
- base_message = "move outside of cables."
- action_icon_state = "pd_toggle_exit"
- unlock_cost = 100 KJ
- upgrade_cost = 300 KJ
- level_max = 3
-
-/datum/spell/pulse_demon/toggle/can_exit_cable/try_cast_action(mob/living/simple_animal/demon/pulse_demon/user, atom/target)
- if(user.can_exit_cable && !(user.current_cable || user.current_power))
- to_chat(user, "Enter a cable or power source first!")
- return FALSE
- user.can_exit_cable = do_toggle(!user.can_exit_cable, user)
- return TRUE
-
-/datum/spell/pulse_demon/toggle/can_exit_cable/do_upgrade(mob/living/simple_animal/demon/pulse_demon/user)
- user.outside_cable_speed = max(initial(user.outside_cable_speed) - spell_level, 1)
- to_chat(user, "You have upgraded [initial(name)] to level [spell_level + 1], you will now move faster outside of cables.")
-
-/datum/spell/pulse_demon/cycle_camera
- name = "Cycle Camera View"
- desc = "Jump between the cameras in your APC's area. Alt-click to return to the APC."
- action_icon_state = "pd_camera_view"
- create_attack_logs = FALSE
- locked = FALSE
- cast_cost = 0
- level_max = 0
- base_cooldown = 0
- requires_area = TRUE
- var/current_camera = 0
-
-/datum/spell/pulse_demon/cycle_camera/create_new_targeting()
- return new /datum/spell_targeting/self
-
-/datum/spell/pulse_demon/cycle_camera/AltClick(mob/living/simple_animal/demon/pulse_demon/user)
- if(!istype(user))
- return
- current_camera = 0
-
- if(!isapc(user.current_power))
- return
- if(get_area(user.loc) != user.controlling_area)
- return
- user.forceMove(user.current_power)
-
-/datum/spell/pulse_demon/cycle_camera/try_cast_action(mob/living/simple_animal/demon/pulse_demon/user, atom/target)
- if(!length(user.controlling_area.cameras))
- return FALSE
-
- if(isapc(user.loc))
- current_camera = 0
- else if(istype(user.loc, /obj/machinery/camera))
- current_camera = (current_camera + 1) % length(user.controlling_area.cameras)
- if(current_camera == 0)
- user.forceMove(user.current_power)
- return TRUE
-
- if(length(user.controlling_area.cameras) < current_camera)
- current_camera = 0
-
- user.forceMove(locateUID(user.controlling_area.cameras[current_camera + 1]))
- return TRUE
-
-/datum/spell/pulse_demon/open_upgrades
- name = "Open Upgrade Menu"
- desc = "Open the upgrades menu. Alt-click for descriptions and costs."
- action_icon_state = "pd_upgrade"
- create_attack_logs = FALSE
- locked = FALSE
- cast_cost = 0
- level_max = 0
- base_cooldown = 0
- var/static/list/upgrade_icons = list(
- PD_UPGRADE_HIJACK_SPEED = image(icon = 'icons/obj/power.dmi', icon_state = "apcemag"),
- PD_UPGRADE_DRAIN_SPEED = image(icon = 'icons/obj/power.dmi', icon_state = "ccharger"),
- PD_UPGRADE_MAX_HEALTH = image(icon = 'icons/obj/stock_parts.dmi', icon_state = "bluespace_matter_bin"),
- PD_UPGRADE_HEALTH_REGEN = image(icon = 'icons/obj/stock_parts.dmi', icon_state = "femto_mani"),
- PD_UPGRADE_HEALTH_LOSS = image(icon = 'icons/obj/stock_parts.dmi', icon_state = "triphasic_scan_module"),
- PD_UPGRADE_HEALTH_COST = image(icon = 'icons/obj/stock_parts.dmi', icon_state = "quadultra_micro_laser"),
- PD_UPGRADE_MAX_CHARGE = image(icon = 'icons/obj/stock_parts.dmi', icon_state = "quadratic_capacitor")
- )
- var/static/list/upgrade_descs = list(
- PD_UPGRADE_HIJACK_SPEED = "Decrease the amount of time required to hijack an APC.",
- PD_UPGRADE_DRAIN_SPEED = "Increase the amount of charge drained from a power source per cycle.",
- PD_UPGRADE_MAX_HEALTH = "Increase the total amount of health you can have at once.",
- PD_UPGRADE_HEALTH_REGEN = "Increase the amount of health regenerated when powered per cycle.",
- PD_UPGRADE_HEALTH_LOSS = "Decrease the amount of health lost when unpowered per cycle.",
- PD_UPGRADE_HEALTH_COST = "Decrease the amount of power required to regenerate per cycle.",
- PD_UPGRADE_MAX_CHARGE = "Increase the total amount of charge you can have at once."
- )
-
-/datum/spell/pulse_demon/open_upgrades/create_new_targeting()
- return new /datum/spell_targeting/self
-
-/datum/spell/pulse_demon/open_upgrades/proc/calc_cost(mob/living/simple_animal/demon/pulse_demon/user, upgrade)
- var/cost
- switch(upgrade)
- if(PD_UPGRADE_HIJACK_SPEED)
- if(user.hijack_time <= 1 SECONDS)
- return -1
- cost = (100 / (user.hijack_time / (1 SECONDS))) * 20 KJ
- if(PD_UPGRADE_DRAIN_SPEED)
- if(user.max_drain_rate >= 500 KJ)
- return -1
- cost = user.max_drain_rate * 15
- if(PD_UPGRADE_MAX_HEALTH)
- if(user.maxHealth >= 200)
- return -1
- cost = user.maxHealth * 5 KJ
- if(PD_UPGRADE_HEALTH_REGEN)
- if(user.health_regen_rate >= 100)
- return -1
- cost = user.health_regen_rate * 50 KJ
- if(PD_UPGRADE_HEALTH_LOSS)
- if(user.health_loss_rate <= 1)
- return -1
- cost = (100 / user.health_loss_rate) * 20 KJ
- if(PD_UPGRADE_HEALTH_COST)
- if(user.power_per_regen <= 1)
- return -1
- cost = (100 / user.power_per_regen) * 50 KJ
- if(PD_UPGRADE_MAX_CHARGE)
- cost = user.maxcharge
- else
- return -1
- return round(cost)
-
-/datum/spell/pulse_demon/open_upgrades/proc/get_upgrades(mob/living/simple_animal/demon/pulse_demon/user)
- var/upgrades = list()
- for(var/upgrade in upgrade_icons)
- var/cost = calc_cost(user, upgrade)
- if(cost == -1)
- continue
- upgrades["[upgrade] ([format_si_suffix(cost)]W)"] = upgrade_icons[upgrade]
- return upgrades
-
-/datum/spell/pulse_demon/open_upgrades/AltClick(mob/living/simple_animal/demon/pulse_demon/user)
- if(!istype(user))
- return
-
- to_chat(user, "Pulse Demon upgrades:")
- for(var/upgrade in upgrade_descs)
- var/cost = calc_cost(user, upgrade)
- to_chat(user, "[upgrade] ([cost == -1 ? "Fully Upgraded" : "[format_si_suffix(cost)]J"]) - [upgrade_descs[upgrade]]")
-
-/datum/spell/pulse_demon/open_upgrades/try_cast_action(mob/living/simple_animal/demon/pulse_demon/user, atom/target)
- var/upgrades = get_upgrades(user)
- if(!length(upgrades))
- to_chat(user, "You have already fully upgraded everything available!")
- return FALSE
-
- var/raw_choice = show_radial_menu(user, user, upgrades, radius = 48)
- if(!raw_choice)
- return
- var/choice = splittext(raw_choice, " ")[1]
-
- var/cost = calc_cost(user, choice)
- if(cost == -1)
- return FALSE
- if(user.charge < cost)
- to_chat(user, "You do not have enough charge to purchase this upgrade!")
- return FALSE
-
- user.adjust_charge(-cost)
- switch(choice)
- if(PD_UPGRADE_HIJACK_SPEED)
- user.hijack_time = max(round(user.hijack_time / 1.5), 1 SECONDS)
- to_chat(user, "You have upgraded your [choice], it now takes [user.hijack_time / (1 SECONDS)] second\s to hijack APCs.")
- if(PD_UPGRADE_DRAIN_SPEED)
- var/old = user.max_drain_rate
- user.max_drain_rate = min(round(user.max_drain_rate * 1.5), 500 KJ)
- if(user.power_drain_rate == old)
- user.power_drain_rate = user.max_drain_rate
- to_chat(user, "You have upgraded your [choice], you can now drain [format_si_suffix(user.max_drain_rate)]W.")
- if(PD_UPGRADE_MAX_HEALTH)
- user.maxHealth = min(round(user.maxHealth * 1.5), 200)
- to_chat(user, "You have upgraded your [choice], your max health is now [user.maxHealth].")
- if(PD_UPGRADE_HEALTH_REGEN)
- user.health_regen_rate = min(round(user.health_regen_rate * 1.5), 100)
- to_chat(user, "You have upgraded your [choice], you will now regenerate [user.health_regen_rate] health per cycle when powered.")
- if(PD_UPGRADE_HEALTH_LOSS)
- user.health_loss_rate = max(round(user.health_loss_rate / 1.5), 1)
- to_chat(user, "You have upgraded your [choice], you will now lose [user.health_loss_rate] health per cycle when unpowered.")
- if(PD_UPGRADE_HEALTH_COST)
- user.power_per_regen = max(round(user.power_per_regen / 1.5), 1)
- to_chat(user, "You have upgraded your [choice], it now takes [format_si_suffix(user.power_per_regen)]W of power to regenerate health.")
- to_chat(user, "Additionally, if you enable draining while on a cable, any excess power that would've been used regenerating will be added to your charge.")
- if(PD_UPGRADE_MAX_CHARGE)
- user.maxcharge = round(user.maxcharge * 2)
- to_chat(user, "You have upgraded your [choice], you can now store [format_si_suffix(user.maxcharge)]J of energy.")
- else
- return FALSE
- return TRUE
-
-
-#undef PULSEDEMON_REMOTE_DRAIN_MULTIPLIER
-#undef PD_UPGRADE_HIJACK_SPEED
-#undef PD_UPGRADE_DRAIN_SPEED
-#undef PD_UPGRADE_HEALTH_LOSS
-#undef PD_UPGRADE_HEALTH_REGEN
-#undef PD_UPGRADE_MAX_HEALTH
-#undef PD_UPGRADE_HEALTH_COST
-#undef PD_UPGRADE_MAX_CHARGE
diff --git a/code/game/gamemodes/miniantags/revenant/revenant.dm b/code/game/gamemodes/miniantags/revenant/revenant.dm
deleted file mode 100644
index 3719b54a9881f..0000000000000
--- a/code/game/gamemodes/miniantags/revenant/revenant.dm
+++ /dev/null
@@ -1,368 +0,0 @@
-//Revenants: based off of wraiths from Goon
-//"Ghosts" that are invisible and move like ghosts, cannot take damage while invsible
-//Wreck havoc with haunting themed abilities
-//Admin-spawn or random event
-
-#define INVISIBILITY_REVENANT 45
-#define REVENANT_NAME_FILE "revenant_names.json"
-
-/mob/living/simple_animal/revenant
- name = "revenant" //The name shown on examine
- real_name = "revenant" //The name shown in dchat
- desc = "A malevolent spirit."
- icon = 'icons/mob/mob.dmi'
- icon_state = "revenant_idle"
-
- mob_biotypes = MOB_SPIRIT
- incorporeal_move = INCORPOREAL_MOVE_HOLY_BLOCK
- see_invisible = INVISIBILITY_REVENANT
- invisibility = INVISIBILITY_REVENANT
- health = INFINITY //Revenants don't use health, they use essence instead
- maxHealth = INFINITY
- see_in_dark = 8
- lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE
- response_help = "passes through"
- response_disarm = "swings at"
- response_harm = "punches"
- unsuitable_atmos_damage = 0
- minbodytemp = 0
- maxbodytemp = INFINITY
- harm_intent_damage = 0
- friendly = "touches"
- status_flags = 0
- wander = FALSE
- density = FALSE
- move_resist = INFINITY
- mob_size = MOB_SIZE_TINY
- pass_flags = PASSTABLE | PASSGRILLE | PASSMOB
- atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0)
- initial_traits = list(TRAIT_FLYING)
-
- /// The revenant's idle icon
- var/icon_idle = "revenant_idle"
- /// The revenant's revealed icon
- var/icon_reveal = "revenant_revealed"
- /// The revenant's stunned icon
- var/icon_stun = "revenant_stun"
- /// The revant's icon while draining mobs
- var/icon_drain = "revenant_draining"
-
- ///The resource of revenants. Max health is equal to three times this amount
- var/essence = 75
- ///The regeneration cap of essence (go figure); regenerates every Life() tick up to this amount.
- var/essence_regen_cap = 75
- ///If the revenant regenerates essence or not; 1 for yes, 0 for no
- var/essence_regenerating = TRUE
- ///How much essence regenerates
- var/essence_regen_amount = 5
- ///How much essence the revenant has stolen
- var/essence_accumulated = 0
- ///If the revenant can take damage from normal sources.
- var/revealed = FALSE
- ///How long the revenant is revealed for, is about 2 seconds times this var.
- var/unreveal_time = 0
- ///How long the revenant is stunned for, is about 2 seconds times this var.
- var/unstun_time = 0
- ///If the revenant's abilities are blocked by a chaplain's power.
- var/inhibited = FALSE
- ///How much essence the revenant has drained.
- var/essence_drained = 0
- ///If the revenant is draining someone.
- var/draining = FALSE
- /// contains a list of UIDs of mobs who have been drained. cannot drain the same mob twice.
- var/list/drained_mobs = list()
- ///How many perfect, regen-cap increasing souls the revenant has.
- var/perfectsouls = 0
-
-/mob/living/simple_animal/revenant/Life(seconds, times_fired)
- ..()
- if(revealed && essence <= 0)
- death()
- if(essence_regenerating && !inhibited && essence < essence_regen_cap) //While inhibited, essence will not regenerate
- essence = min(essence_regen_cap, essence+essence_regen_amount)
- if(unreveal_time && world.time >= unreveal_time)
- unreveal_time = 0
- revealed = FALSE
- incorporeal_move = INCORPOREAL_MOVE_HOLY_BLOCK
- invisibility = INVISIBILITY_REVENANT
- to_chat(src, "You are once more concealed.")
- if(unstun_time && world.time >= unstun_time)
- unstun_time = 0
- notransform = FALSE
- to_chat(src, "You can move again!")
- update_spooky_icon()
-
-/mob/living/simple_animal/revenant/ex_act(severity)
- return TRUE //Immune to the effects of explosions.
-
-/mob/living/simple_animal/revenant/blob_act(obj/structure/blob/B)
- return //blah blah blobs aren't in tune with the spirit world, or something.
-
-/mob/living/simple_animal/revenant/singularity_act()
- return //don't walk into the singularity expecting to find corpses, okay?
-
-/mob/living/simple_animal/revenant/narsie_act()
- return //most humans will now be either bones or harvesters, but we're still un-alive.
-
-/mob/living/simple_animal/revenant/electrocute_act(shock_damage, source, siemens_coeff = 1, flags = NONE)
- return FALSE //You are a ghost, atmos and grill makes sparks, and you make your own shocks with lights.
-
-/mob/living/simple_animal/revenant/adjustHealth(amount, updating_health = TRUE)
- if(!revealed)
- return
- essence = max(0, essence-amount)
- if(!essence)
- to_chat(src, "You feel your essence fraying!")
-
-/mob/living/simple_animal/revenant/say(message)
- if(!message)
- return
- log_say(message, src)
- if(copytext(message, 1, 2) == "*")
- return emote(copytext(message, 2), intentional = TRUE)
-
- say_dead(message)
-
-/mob/living/simple_animal/revenant/get_status_tab_items()
- var/list/status_tab_data = ..()
- . = status_tab_data
- status_tab_data[++status_tab_data.len] = list("Current essence:", "[essence]/[essence_regen_cap]E")
- status_tab_data[++status_tab_data.len] = list("Stolen essence:", "[essence_accumulated]E")
- status_tab_data[++status_tab_data.len] = list("Stolen perfect souls:", "[perfectsouls]")
-
-/mob/living/simple_animal/revenant/New()
- ..()
- flags_2 |= RAD_NO_CONTAMINATE_2
- remove_from_all_data_huds()
- random_revenant_name()
-
- addtimer(CALLBACK(src, PROC_REF(firstSetupAttempt)), 15 SECONDS) // Give admin 15 seconds to put in a ghost (Or wait 15 seconds before giving it objectives)
-
-/mob/living/simple_animal/revenant/proc/random_revenant_name()
- var/built_name = ""
- built_name += pick(strings(REVENANT_NAME_FILE, "spirit_type"))
- built_name += " of "
- built_name += pick(strings(REVENANT_NAME_FILE, "adjective"))
- built_name += pick(strings(REVENANT_NAME_FILE, "theme"))
- name = built_name
- real_name = built_name
-
-/mob/living/simple_animal/revenant/proc/firstSetupAttempt()
- if(mind)
- giveObjectivesandGoals()
- giveSpells()
- else
- message_admins("Revenant was created but has no mind. Put a ghost inside, or a poll will be made in one minute.")
- addtimer(CALLBACK(src, PROC_REF(setupOrDelete)), 1 MINUTES)
-
-/mob/living/simple_animal/revenant/proc/setupOrDelete()
- if(mind)
- giveObjectivesandGoals()
- giveSpells()
- else
- var/list/mob/dead/observer/candidates = SSghost_spawns.poll_candidates("Do you want to play as a revenant?", poll_time = 15 SECONDS, source = /mob/living/simple_animal/revenant)
- var/mob/dead/observer/theghost = null
- if(length(candidates))
- theghost = pick(candidates)
- message_admins("[key_name_admin(theghost)] has taken control of a revenant created without a mind")
- key = theghost.key
- giveObjectivesandGoals()
- giveSpells()
- dust_if_respawnable(theghost)
- else
- message_admins("No ghost was willing to take control of a mindless revenant. Deleting...")
- qdel(src)
-
-/mob/living/simple_animal/revenant/proc/giveObjectivesandGoals()
- mind.wipe_memory() // someone kill this and give revenants their own minds please
- SEND_SOUND(src, sound('sound/effects/ghost.ogg'))
- var/list/messages = list()
- messages.Add("You are a revenant.")
- messages.Add("Your formerly mundane spirit has been infused with alien energies and empowered into a revenant.")
- messages.Add("You are not dead, not alive, but somewhere in between. You are capable of limited interaction with both worlds.")
- messages.Add("You are invincible and invisible to everyone but other ghosts. Most abilities will reveal you, rendering you vulnerable.")
- messages.Add("To function, you are to drain the life essence from humans. This essence is a resource, as well as your health, and will power all of your abilities.")
- messages.Add("You do not remember anything of your past lives, nor will you remember anything about this one after your death.")
- messages.Add("For more information, check the wiki page: ([GLOB.configuration.url.wiki_url]/index.php/Revenant)")
-
- SSticker.mode.traitors |= mind //Necessary for announcing
- mind.add_mind_objective(/datum/objective/revenant)
- mind.add_mind_objective(/datum/objective/revenant_fluff)
- messages.Add(mind.prepare_announce_objectives(FALSE))
- to_chat(src, chat_box_red(messages.Join(" ")))
-
-/mob/living/simple_animal/revenant/proc/giveSpells()
- mind.AddSpell(new /datum/spell/night_vision/revenant(null))
- mind.AddSpell(new /datum/spell/revenant_transmit(null))
- mind.AddSpell(new /datum/spell/aoe/revenant/defile(null))
- mind.AddSpell(new /datum/spell/aoe/revenant/malfunction(null))
- mind.AddSpell(new /datum/spell/aoe/revenant/overload(null))
- mind.AddSpell(new /datum/spell/aoe/revenant/haunt_object(null))
- mind.AddSpell(new /datum/spell/aoe/revenant/hallucinations(null))
- return TRUE
-
-/mob/living/simple_animal/revenant/dust()
- return death()
-
-/mob/living/simple_animal/revenant/gib()
- return death()
-
-/mob/living/simple_animal/revenant/death()
- if(!revealed)
- return FALSE
- // Only execute the below if we successfully died
- . = ..()
- if(!.)
- return FALSE
-
- to_chat(src, "NO! No... it's too late, you can feel your essence breaking apart...")
- notransform = TRUE
- revealed = TRUE
- invisibility = 0
- playsound(src, 'sound/effects/screech.ogg', 100, TRUE)
- visible_message("[src] lets out a waning screech as violet mist swirls around its dissolving body!")
- icon_state = "revenant_draining"
- animate(src, alpha = 0, time = 3 SECONDS)
- visible_message("[src]'s body breaks apart into a fine pile of blue dust.")
- new /obj/item/ectoplasm(get_turf(src))
- ghostize()
- qdel(src)
-
-/mob/living/simple_animal/revenant/attackby__legacy__attackchain(obj/item/W, mob/living/user, params)
- if(istype(W, /obj/item/nullrod))
- visible_message("[src] violently flinches!", \
- "As \the [W] passes through you, you feel your essence draining away!")
- adjustBruteLoss(25) //hella effective
- inhibited = TRUE
- spawn(30)
- inhibited = FALSE
-
- ..()
-
-/mob/living/simple_animal/revenant/proc/castcheck(essence_cost)
- if(holy_check(src))
- return
- var/turf/T = get_turf(src)
- if(iswallturf(T))
- to_chat(src, "You cannot use abilities from inside of a wall.")
- return FALSE
- if(inhibited)
- to_chat(src, "Your powers have been suppressed by nulling energy!")
- return FALSE
- if(!change_essence_amount(essence_cost, 1))
- to_chat(src, "You lack the essence to use that ability.")
- return FALSE
- return TRUE
-
-/mob/living/simple_animal/revenant/proc/change_essence_amount(essence_amt, silent = FALSE, source)
- if(essence + essence_amt <= 0)
- return
- essence = max(0, essence + essence_amt)
- if(essence_amt > 0)
- essence_accumulated = max(0, essence_accumulated + essence_amt)
- if(!silent)
- if(essence_amt > 0)
- to_chat(src, "Gained [essence_amt]E from [source].")
- else
- to_chat(src, "Lost [essence_amt]E from [source].")
- return TRUE
-
-/mob/living/simple_animal/revenant/proc/reveal(time)
- if(time <= 0)
- return
- revealed = TRUE
- invisibility = 0
- incorporeal_move = NO_INCORPOREAL_MOVE
- if(!unreveal_time)
- to_chat(src, "You have been revealed!")
- unreveal_time = world.time + time
- else
- to_chat(src, "You have been revealed!")
- unreveal_time = unreveal_time + time
- update_spooky_icon()
-
-/mob/living/simple_animal/revenant/proc/stun(time)
- if(time <= 0)
- return
- notransform = TRUE
- if(!unstun_time)
- to_chat(src, "You cannot move!")
- unstun_time = world.time + time
- else
- to_chat(src, "You cannot move!")
- unstun_time = unstun_time + time
- update_spooky_icon()
-
-/mob/living/simple_animal/revenant/proc/update_spooky_icon()
- if(!revealed)
- icon_state = icon_idle
- return
-
- if(!notransform)
- icon_state = icon_reveal
- return
-
- if(draining)
- icon_state = icon_drain
- return
- // No other state is happening, therefore we are stunned
- icon_state = icon_stun
-
-
-/datum/objective/revenant
- needs_target = FALSE
- var/targetAmount = 100
-
-/datum/objective/revenant/New()
- targetAmount = rand(350, 600)
- explanation_text = "Absorb [targetAmount] points of essence from humans."
- ..()
-
-/datum/objective/revenant/check_completion()
- var/total_essence = 0
- for(var/datum/mind/M in get_owners())
- if(!istype(M.current, /mob/living/simple_animal/revenant) || QDELETED(M.current))
- continue
- var/mob/living/simple_animal/revenant/R = M.current
- total_essence += R.essence_accumulated
- if(total_essence < targetAmount)
- return FALSE
- return TRUE
-
-/datum/objective/revenant_fluff
- needs_target = FALSE
-
-/datum/objective/revenant_fluff/New()
- var/list/explanationTexts = list("Assist and exacerbate existing threats at critical moments.", \
- "Cause as much chaos and anger as you can without being killed.", \
- "Damage and render as much of the station rusted and unusable as possible.", \
- "Disable and cause malfunctions in as many machines as possible.", \
- "Ensure that any holy weapons are rendered unusable.", \
- "Hinder the crew while attempting to avoid being noticed.", \
- "Make the crew as miserable as possible.", \
- "Make the clown as miserable as possible.", \
- "Make the captain as miserable as possible.", \
- "Make the AI as miserable as possible.", \
- "Annoy the ones that insult you the most.", \
- "Whisper ghost jokes into peoples heads.", \
- "Help the crew in critical situations, but take your payments in souls.", \
- "Prevent the use of energy weapons where possible.")
- explanation_text = pick(explanationTexts)
- ..()
-
-/datum/objective/revenant_fluff/check_completion()
- return TRUE
-
-/obj/item/ectoplasm
- name = "glimmering residue"
- desc = "A pile of fine blue dust. Small tendrils of violet mist swirl around it."
- icon = 'icons/effects/effects.dmi'
- icon_state = "revenantEctoplasm"
- w_class = WEIGHT_CLASS_SMALL
-
-/obj/item/ectoplasm/examine(mob/user)
- . = ..()
- . += "Lifeless ectoplasm, still faintly glimmering in the light. From what was once a spirit seeking revenge on the station."
-
-#undef INVISIBILITY_REVENANT
-#undef REVENANT_NAME_FILE
diff --git a/code/game/gamemodes/miniantags/revenant/revenant_abilities.dm b/code/game/gamemodes/miniantags/revenant/revenant_abilities.dm
deleted file mode 100644
index 4fae962fa5136..0000000000000
--- a/code/game/gamemodes/miniantags/revenant/revenant_abilities.dm
+++ /dev/null
@@ -1,548 +0,0 @@
-///Harvest
-/mob/living/simple_animal/revenant/ClickOn(atom/A, params) //Copypaste from ghost code - revenants can't interact with the world directly.
-
- if(client.click_intercept)
- client.click_intercept.InterceptClickOn(src, params, A)
- return
-
- var/list/modifiers = params2list(params)
- if(modifiers["middle"])
- MiddleClickOn(A)
- return
- if(modifiers["middle"] && modifiers["shift"] && modifiers["ctrl"])
- MiddleShiftControlClickOn(A)
- return
- if(modifiers["middle"] && modifiers["shift"])
- MiddleShiftClickOn(A)
- return
- if(modifiers["shift"] && modifiers["ctrl"])
- CtrlShiftClickOn(A)
- return
- if(modifiers["shift"] && modifiers["alt"])
- AltShiftClickOn(A)
- return
- if(modifiers["shift"])
- ShiftClickOn(A)
- return
- if(modifiers["alt"])
- AltClickOn(A)
- return
- if(modifiers["ctrl"])
- CtrlClickOn(A)
- return
-
- if(world.time <= next_move)
- return
- A.attack_ghost(src)
- if(ishuman(A) && in_range(src, A))
- if(isLivingSSD(A) && client.send_ssd_warning(A)) //Do NOT Harvest SSD people unless you accept the warning
- return
- Harvest(A)
-
-/mob/living/simple_animal/revenant/proc/Harvest(mob/living/carbon/human/target)
- if(!castcheck(0))
- return
- if(draining)
- to_chat(src, "You are already siphoning the essence of a soul!")
- return
- var/mob_UID = target.UID()
- if(mob_UID in drained_mobs)
- to_chat(src, "[target]'s soul is dead and empty.")
- return
- if(!target.stat)
- to_chat(src, "This being's soul is too strong to harvest.")
- if(prob(10))
- to_chat(target, "You feel as if you are being watched.")
- return
- draining = TRUE
- essence_drained = rand(15, 20)
- to_chat(src, "You search for the soul of [target].")
- if(do_after(src, 10, 0, target = target)) //did they get deleted in that second?
- if(target.ckey)
- to_chat(src, "Their soul burns with intelligence.")
- essence_drained += rand(20, 30)
- if(target.stat != DEAD)
- to_chat(src, "Their soul blazes with life!")
- essence_drained += rand(40, 50)
- else
- to_chat(src, "Their soul is weak and faltering.")
- if(do_after(src, 20, 0, target = target)) //did they get deleted NOW?
- switch(essence_drained)
- if(1 to 30)
- to_chat(src, "[target] will not yield much essence. Still, every bit counts.")
- if(30 to 70)
- to_chat(src, "[target] will yield an average amount of essence.")
- if(70 to 90)
- to_chat(src, "Such a feast! [target] will yield much essence to you.")
- if(90 to INFINITY)
- to_chat(src, "Ah, the perfect soul. [target] will yield massive amounts of essence to you.")
- if(do_after(src, 20, 0, target = target)) //how about now
- if(!target.stat)
- to_chat(src, "They are now powerful enough to fight off your draining.")
- to_chat(target, "You feel something tugging across your body before subsiding.")
- draining = FALSE
- return //hey, wait a minute...
- to_chat(src, "You begin siphoning essence from [target]'s soul.")
- if(target.stat != DEAD)
- to_chat(target, "You feel a horribly unpleasant draining sensation as your grip on life weakens...")
- icon_state = "revenant_draining"
- reveal(27)
- stun(27)
- target.visible_message("[target] suddenly rises slightly into the air, [target.p_their()] skin turning an ashy gray.")
- target.Beam(src,icon_state="drain_life",icon='icons/effects/effects.dmi',time=26)
- if(do_after(src, 30, 0, target)) //As one cannot prove the existance of ghosts, ghosts cannot prove the existance of the target they were draining.
- change_essence_amount(essence_drained, 0, target)
- if(essence_drained > 90)
- essence_regen_cap += 25
- perfectsouls += 1
- to_chat(src, "The perfection of [target]'s soul has increased your maximum essence level. Your new maximum essence is [essence_regen_cap].")
- to_chat(src, "[target]'s soul has been considerably weakened and will yield no more essence for the time being.")
- target.visible_message("[target] slumps onto the ground.", \
- "Violets lights, dancing in your vision, getting clo--")
- drained_mobs.Add(mob_UID)
- add_attack_logs(src, target, "revenant harvested soul")
- target.death()
- else
- to_chat(src, "[target ? "[target] has":"They have"] been drawn out of your grasp. The link has been broken.")
- draining = 0
- essence_drained = 0
- if(target) //Wait, target is WHERE NOW?
- target.visible_message("[target] slumps onto the ground.", \
- "Violets lights, dancing in your vision, receding--")
- return
- else
- to_chat(src, "You are not close enough to siphon [target ? "[target]'s":"their"] soul. The link has been broken.")
- draining = FALSE
- essence_drained = 0
- return
- draining = FALSE
- essence_drained = 0
- return
-
-//Toggle night vision: lets the revenant toggle its night vision
-/datum/spell/night_vision/revenant
- base_cooldown = 0
- message = "You toggle your night vision."
- action_icon_state = "r_nightvision"
- action_background_icon_state = "bg_revenant"
-
-//Transmit: the revemant's only direct way to communicate. Sends a single message silently to a single mob
-/datum/spell/revenant_transmit
- name = "Transmit"
- desc = "Telepathically transmits a message to the target."
- base_cooldown = 0
- clothes_req = FALSE
- action_icon_state = "r_transmit"
- action_background_icon_state = "bg_revenant"
- antimagic_flags = MAGIC_RESISTANCE_HOLY|MAGIC_RESISTANCE_MIND
-
-/datum/spell/revenant_transmit/create_new_targeting()
- var/datum/spell_targeting/targeted/T = new()
- T.allowed_type = /mob/living
- return T
-
-/datum/spell/revenant_transmit/cast(list/targets, mob/living/simple_animal/revenant/user = usr)
- for(var/mob/living/M in targets)
- spawn(0)
- var/msg = tgui_input_text(user, "What do you wish to tell [M]?", "Transmit")
- if(!msg)
- cooldown_handler.revert_cast()
- return
- log_say("(REVENANT to [key_name(M)]) [msg]", user)
- to_chat(user, "You transmit to [M]: [msg]")
- to_chat(M, "An alien voice resonates from all around... [msg]")
-
-/datum/spell/aoe/revenant
- name = "Spell"
- clothes_req = FALSE
- action_background_icon_state = "bg_revenant"
- /// How long it reveals the revenant in deciseconds
- var/reveal = 8 SECONDS
- /// How long it stuns the revenant in deciseconds
- var/stun = 2 SECONDS
- /// If it's locked and needs to be unlocked before use
- var/locked = TRUE
- /// How much essence it costs to unlock
- var/unlock_amount = 100
- /// How much essence it costs to use
- var/cast_amount = 50
- antimagic_flags = MAGIC_RESISTANCE_HOLY
-
-/datum/spell/aoe/revenant/New()
- ..()
- if(locked)
- name = "[initial(name)] ([unlock_amount]E)"
- else
- name = "[initial(name)] ([cast_amount]E)"
- action.name = name
- action.desc = desc
- action.UpdateButtons()
-
-/datum/spell/aoe/revenant/revert_cast(mob/user)
- . = ..()
- to_chat(user, "Your ability wavers and fails!")
- var/mob/living/simple_animal/revenant/R = user
- R?.essence += cast_amount //refund the spell and reset
-
-/datum/spell/aoe/revenant/can_cast(mob/living/simple_animal/revenant/user = usr, charge_check = TRUE, show_message = FALSE)
- if(user.inhibited)
- return FALSE
- if(cooldown_handler.is_on_cooldown())
- return FALSE
- if(locked)
- if(user.essence <= unlock_amount)
- return FALSE
- if(user.essence <= cast_amount)
- return FALSE
- return TRUE
-
-/datum/spell/aoe/revenant/proc/attempt_cast(mob/living/simple_animal/revenant/user = usr)
- if(locked)
- if(!user.castcheck(-unlock_amount))
- cooldown_handler.revert_cast()
- return FALSE
- name = "[initial(name)] ([cast_amount]E)"
- to_chat(user, "You have unlocked [initial(name)]!")
- locked = FALSE
- cooldown_handler.revert_cast()
- return FALSE
- if(!user.castcheck(-cast_amount))
- cooldown_handler.revert_cast()
- return FALSE
- name = "[initial(name)] ([cast_amount]E)"
- user.reveal(reveal)
- user.stun(stun)
- if(action)
- action.UpdateButtons()
- return TRUE
-
-//Overload Light: Breaks a light that's online and sends out lightning bolts to all nearby people.
-/datum/spell/aoe/revenant/overload
- name = "Overload Lights"
- desc = "Directs a large amount of essence into nearby electrical lights, causing lights to shock those nearby."
- base_cooldown = 20 SECONDS
- stun = 3 SECONDS
- cast_amount = 45
- var/shock_range = 2
- var/shock_damage = 40
- action_icon_state = "overload_lights"
- aoe_range = 5
-
-/datum/spell/aoe/revenant/overload/create_new_targeting()
- var/datum/spell_targeting/aoe/targeting = new()
- targeting.range = aoe_range
- targeting.allowed_type = /obj/machinery/light
- return targeting
-
-/datum/spell/aoe/revenant/overload/cast(list/targets, mob/living/simple_animal/revenant/user = usr)
- if(attempt_cast(user))
- for(var/obj/machinery/light/L as anything in targets)
- INVOKE_ASYNC(src, PROC_REF(shock_lights), L, user)
-
-/datum/spell/aoe/revenant/overload/proc/shock_lights(obj/machinery/light/L, mob/living/simple_animal/revenant/user)
- if(!L.on)
- return
- L.visible_message("\The [L] suddenly flares brightly and begins to spark!")
- do_sparks(4, 0, L)
- new /obj/effect/temp_visual/revenant(L.loc)
- sleep(2 SECONDS)
- if(!L.on) //wait, wait, don't shock me
- return
- flick("[L.base_state]2", L)
- for(var/mob/living/M in view(shock_range, L))
- if(M == user)
- continue
- M.Beam(L, icon_state = "purple_lightning", icon = 'icons/effects/effects.dmi', time = 0.5 SECONDS)
- M.electrocute_act(shock_damage, L, flags = SHOCK_NOGLOVES)
- M.Stun(3 SECONDS)
- do_sparks(4, 0, M)
- playsound(M, 'sound/machines/defib_zap.ogg', 50, TRUE, -1)
-
-//Defile: Corrupts nearby stuff, unblesses floor tiles.
-/datum/spell/aoe/revenant/defile
- name = "Defile"
- desc = "Twists and corrupts the nearby area as well as dispelling holy auras on floors."
- base_cooldown = 15 SECONDS
- stun = 1 SECONDS
- reveal = 4 SECONDS
- unlock_amount = 75
- cast_amount = 30
- action_icon_state = "defile"
- aoe_range = 4
-
-/datum/spell/aoe/revenant/defile/create_new_targeting()
- var/datum/spell_targeting/aoe/turf/targeting = new()
- targeting.range = aoe_range
- return targeting
-
-/datum/spell/aoe/revenant/defile/cast(list/targets, mob/living/simple_animal/revenant/user = usr)
- if(!attempt_cast(user))
- return
- for(var/turf/T in targets)
- T.defile()
- for(var/atom/A in T.contents)
- A.defile()
-
-//Malfunction: Makes bad stuff happen to robots and machines.
-/datum/spell/aoe/revenant/malfunction
- name = "Malfunction"
- desc = "Corrupts and damages nearby machines and mechanical objects."
- base_cooldown = 200
- cast_amount = 45
- unlock_amount = 150
- action_icon_state = "malfunction"
- aoe_range = 2
-
-/datum/spell/aoe/revenant/malfunction/create_new_targeting()
- var/datum/spell_targeting/aoe/turf/targeting = new()
- targeting.range = aoe_range
- return targeting
-
-//A note to future coders: do not replace this with an EMP because it will wreck malf AIs and gang dominators and everyone will hate you.
-/datum/spell/aoe/revenant/malfunction/cast(list/targets, mob/living/simple_animal/revenant/user = usr)
- if(attempt_cast(user))
- for(var/turf/T in targets)
- INVOKE_ASYNC(src, PROC_REF(effect), user, T)
-
-/datum/spell/aoe/revenant/malfunction/proc/effect(mob/living/simple_animal/revenant/user, turf/T)
- T.rev_malfunction(TRUE)
- for(var/atom/A in T.contents)
- A.rev_malfunction(TRUE)
-
-/**
- * Makes objects be haunted and then throws them at conscious people to do damage, spooky!
- */
-/datum/spell/aoe/revenant/haunt_object
- name = "Haunt Objects"
- desc = "Empower nearby objects to you with ghostly energy, causing them to attack nearby mortals. \
- Items closer to you are more likely to be haunted."
- action_icon_state = "haunt"
- base_cooldown = 60 SECONDS
- unlock_amount = 150
- cast_amount = 50
- stun = 3 SECONDS
- reveal = 10 SECONDS
- aoe_range = 7
- /// The maximum number of objects to haunt
- var/max_targets = 7
- /// A list of all attack timers started by this spell being cast
- var/list/attack_timers = list()
-
-/datum/spell/aoe/revenant/haunt_object/create_new_targeting()
- var/datum/spell_targeting/aoe/targeting = new()
- targeting.range = aoe_range
- targeting.allowed_type = /obj/item
- return targeting
-
-/datum/spell/aoe/revenant/haunt_object/cast(list/targets, mob/living/simple_animal/revenant/user = usr)
- if(!attempt_cast(user))
- return
-
- var/successes = 0
- for(var/obj/item/nearby_item as anything in targets)
- if(successes >= max_targets) // End loop if we've already got 7 spooky items
- break
-
- // Don't throw around anchored things or dense things
- // (Or things not on a turf but I am not sure if range can catch that)
- if(nearby_item.anchored || nearby_item.density || nearby_item.move_resist == INFINITY || !isturf(nearby_item.loc))
- continue
- // Don't throw abstract things
- if(nearby_item.flags & ABSTRACT)
- continue
- // Don't throw things we can't see
- if(nearby_item.invisibility > user.see_invisible)
- continue
-
- var/distance_from_user = max(get_dist(get_turf(nearby_item), get_turf(user)), 1) // get_dist() for same tile dists return -1, we do not want that
- var/chance_of_haunting = 150 / distance_from_user // The further away things are, the less likely they are to be picked
- if(!prob(chance_of_haunting))
- continue
-
- make_spooky(nearby_item, user)
- successes++
-
- if(!successes) //no items to throw
- revert_cast()
- return
-
- // Stop the looping attacks after 65 seconds, roughly 14 attack cycles depending on lag
- addtimer(CALLBACK(src, PROC_REF(stop_timers)), 65 SECONDS, TIMER_UNIQUE)
-
-/// Handles making an object haunted and setting it up to attack
-/datum/spell/aoe/revenant/haunt_object/proc/make_spooky(obj/item/item_to_possess, mob/living/simple_animal/revenant/user)
- new /obj/effect/temp_visual/revenant(get_turf(item_to_possess)) // Thematic spooky visuals
- var/mob/living/simple_animal/possessed_object/possessed_object = new(item_to_possess) // Begin haunting object
- item_to_possess.throwforce = min(item_to_possess.throwforce + 5, 15) // Damage it should do? throwforce+5 or 15, whichever is lower
- set_outline(possessed_object)
- possessed_object.maxHealth = 100 // Double the regular HP of possessed objects
- possessed_object.health = 100
- possessed_object.escape_chance = 100 // We cannot be contained
- ADD_TRAIT(possessed_object, TRAIT_DODGE_ALL_OBJECTS, "Revenant")
-
- addtimer(CALLBACK(src, PROC_REF(attack__legacy__attackchain), possessed_object, user), 1 SECONDS, TIMER_UNIQUE) // Short warm-up for floaty ambience
- attack_timers.Add(addtimer(CALLBACK(src, PROC_REF(attack__legacy__attackchain), possessed_object, user), 4 SECONDS, TIMER_UNIQUE|TIMER_LOOP|TIMER_STOPPABLE)) // 5 second looping attacks
- addtimer(CALLBACK(possessed_object, TYPE_PROC_REF(/mob/living/simple_animal/possessed_object, death)), 70 SECONDS, TIMER_UNIQUE) // De-haunt the object
-
-/// Handles finding a valid target and throwing us at it
-/datum/spell/aoe/revenant/haunt_object/proc/attack__legacy__attackchain(mob/living/simple_animal/possessed_object/possessed_object, mob/living/simple_animal/revenant/user)
- var/list/potential_victims = list()
- for(var/turf/turf_to_search in spiral_range_turfs(aoe_range, get_turf(possessed_object)))
- for(var/mob/living/carbon/potential_victim in turf_to_search)
- if(QDELETED(possessed_object) || !can_see(possessed_object, potential_victim, aoe_range)) // You can't see me
- continue
- if(potential_victim.stat != CONSCIOUS) // Don't kill our precious essence-filled sleepy mobs
- continue
- potential_victims.Add(potential_victim)
-
- potential_victims.Cut((length(potential_victims) * 0.8) + 1.2) // Only consider the people near us
-
- if(!length(potential_victims))
- possessed_object.possessed_item.throwforce = min(possessed_object.possessed_item.throwforce + 5, 15) // If an item is stood still for a while it can gather power
- set_outline(possessed_object)
- return
-
- var/mob/living/carbon/victim = pick(potential_victims)
- possessed_object.throw_at(victim, aoe_range, 2, user, dodgeable = FALSE)
-
-/// Sets the glow on the haunted object, scales up based on throwforce
-/datum/spell/aoe/revenant/haunt_object/proc/set_outline(mob/living/simple_animal/possessed_object/possessed_object)
- possessed_object.remove_filter("haunt_glow")
- var/outline_size = min((possessed_object.possessed_item.throwforce / 15) * 3, 3)
- possessed_object.add_filter("haunt_glow", 2, list("type" = "outline", "color" = "#7A4FA9", "size" = outline_size)) // Give it spooky purple outline
-
-/// Stop all attack timers cast by the previous spell use
-/datum/spell/aoe/revenant/haunt_object/proc/stop_timers()
- for(var/I in attack_timers)
- deltimer(I)
-
-/**
- * Gives everyone in a 7 tile radius 2 minutes of hallucinations
- */
-/datum/spell/aoe/revenant/hallucinations
- name = "Hallucination Aura"
- desc = "Toy with the living nearby, giving them glimpses of things that could be or once were."
- action_icon_state = "hallucinations"
- base_cooldown = 15 SECONDS
- unlock_amount = 50
- cast_amount = 25
- stun = 1 SECONDS
- reveal = 3 SECONDS
-
-/datum/spell/aoe/revenant/hallucinations/create_new_targeting()
- var/datum/spell_targeting/aoe/targeting = new()
- targeting.range = aoe_range
- targeting.allowed_type = /mob/living/carbon
- return targeting
-
-/datum/spell/aoe/revenant/hallucinations/cast(list/targets, mob/living/simple_animal/revenant/user = usr)
- if(!attempt_cast(user))
- return
-
- for(var/mob/living/carbon/M as anything in targets)
- M.AdjustHallucinate(120 SECONDS, bound_upper = 300 SECONDS) //Lets not let them get more than 5 minutes of hallucinations
- new /obj/effect/temp_visual/revenant(get_turf(M))
-
-/// Begin defile and malfunction on-atom definitions
-
-/atom/proc/defile()
- return
-
-/atom/proc/rev_malfunction(cause_emp = TRUE)
- return
-
-/mob/living/carbon/human/rev_malfunction(cause_emp = TRUE)
- to_chat(src, "You feel [pick("your sense of direction flicker out", "a stabbing pain in your head", "your mind fill with static")].")
- new /obj/effect/temp_visual/revenant(loc)
- if(cause_emp)
- emp_act(EMP_HEAVY)
-
-/mob/living/simple_animal/bot/rev_malfunction(cause_emp = TRUE)
- if(!emagged)
- new /obj/effect/temp_visual/revenant(loc)
- locked = FALSE
- open = TRUE
- emag_act(usr)
-
-/obj/rev_malfunction(cause_emp = TRUE)
- if(prob(20))
- if(prob(50))
- new /obj/effect/temp_visual/revenant(loc)
- emag_act(usr)
- else if(cause_emp)
- emp_act(EMP_HEAVY)
-
-/obj/machinery/clonepod/rev_malfunction(cause_emp = TRUE)
- ..(cause_emp = FALSE)
-
-/obj/machinery/power/apc/rev_malfunction(cause_emp = TRUE)
- return
-
-/obj/machinery/power/smes/rev_malfunction(cause_emp = TRUE)
- return
-
-/mob/living/silicon/robot/rev_malfunction(cause_emp = TRUE)
- playsound(src, 'sound/machines/warning-buzzer.ogg', 50, 1)
- new /obj/effect/temp_visual/revenant(loc)
- spark_system.start()
- if(cause_emp)
- emp_act(EMP_HEAVY)
-
-/turf/defile()
- if(flags & BLESSED_TILE)
- flags &= ~BLESSED_TILE
- new /obj/effect/temp_visual/revenant(loc)
-
-/turf/simulated/wall/defile()
- ..()
- if(prob(15))
- new/obj/effect/temp_visual/revenant(loc)
- magic_rust_turf()
-
-/turf/simulated/wall/indestructible/defile()
- return
-
-/turf/simulated/wall/r_wall/defile()
- ..()
- if(prob(15))
- new/obj/effect/temp_visual/revenant(loc)
- magic_rust_turf()
-
-/mob/living/carbon/human/defile()
- to_chat(src, "You suddenly feel [pick("sick and tired", "tired and confused", "nauseated", "dizzy")].")
- apply_damage(60, STAMINA)
- adjustToxLoss(5)
- AdjustConfused(40 SECONDS, bound_lower = 0, bound_upper = 60 SECONDS)
- new /obj/effect/temp_visual/revenant(loc)
-
-/obj/structure/window/defile()
- take_damage(rand(30,80))
- if(fulltile)
- new /obj/effect/temp_visual/revenant/cracks(loc)
-
-/obj/structure/closet/defile()
- open()
-
-/turf/simulated/floor/defile()
- ..()
- if(prob(15))
- if(intact && floor_tile)
- new floor_tile(src)
- broken = FALSE
- burnt = FALSE
- make_plating(1)
- magic_rust_turf()
-
-/turf/simulated/floor/plating/defile()
- magic_rust_turf()
- if(flags & BLESSED_TILE)
- flags &= ~BLESSED_TILE
- new /obj/effect/temp_visual/revenant(loc)
-
-/turf/simulated/floor/engine/cult/defile()
- if(flags & BLESSED_TILE)
- flags &= ~BLESSED_TILE
- new /obj/effect/temp_visual/revenant(loc)
-
-/obj/machinery/light/defile()
- flicker(30)
diff --git a/code/game/gamemodes/nuclear/nuclear_challenge.dm b/code/game/gamemodes/nuclear/nuclear_challenge.dm
deleted file mode 100644
index e0ce8b2b03864..0000000000000
--- a/code/game/gamemodes/nuclear/nuclear_challenge.dm
+++ /dev/null
@@ -1,107 +0,0 @@
-#define CHALLENGE_TELECRYSTALS 1400
-#define CHALLENGE_TIME_LIMIT 10 MINUTES
-#define CHALLENGE_SCALE_PLAYER 1 // How many player per scaling bonus
-#define CHALLENGE_SCALE_BONUS 10 // How many TC per scaling bonus
-#define CHALLENGE_MIN_PLAYERS 30
-#define CHALLENGE_SHUTTLE_DELAY 30 MINUTES // So the ops have at least 10 minutes before the shuttle is callable. Gives the nuke ops at least 15 minutes before shuttle arrive.
-
-/obj/item/nuclear_challenge
- name = "Declaration of War (Challenge Mode)"
- icon = 'icons/obj/device.dmi'
- icon_state = "gangtool-red"
- item_state = "walkietalkie"
- desc = "Use to send a declaration of hostilities to the target, delaying your shuttle departure for 20 minutes while they prepare for your assault. \
- Such a brazen move will attract the attention of powerful benefactors within the Syndicate, who will supply your team with a massive amount of bonus telecrystals. \
- Must be used within ten minutes, or your benefactors will lose interest."
- var/declaring_war = FALSE
- var/total_tc = 0 //Total amount of telecrystals shared between nuke ops
-
-/obj/item/nuclear_challenge/attack_self__legacy__attackchain(mob/living/user)
- if(!check_allowed(user))
- return
-
- declaring_war = TRUE
- var/are_you_sure = tgui_alert(user, "Consult your team carefully before you declare war on [station_name()]. Are you sure you want to alert the enemy crew? You have [-round((world.time-SSticker.round_start_time - CHALLENGE_TIME_LIMIT)/10)] seconds to decide.", "Declare war?", list("Yes", "No"))
- declaring_war = FALSE
-
- if(!check_allowed(user))
- return
-
- if(are_you_sure != "Yes")
- to_chat(user, "On second thought, the element of surprise isn't so bad after all.")
- return
-
- var/war_declaration = "[user.real_name] has declared [user.p_their()] intent to utterly destroy [station_name()] with a nuclear device, and dares the crew to try and stop them."
-
- declaring_war = TRUE
- var/custom_threat = tgui_alert(user, "Do you want to customize your declaration?", "Customize?", list("Yes", "No"))
- declaring_war = FALSE
-
- if(!check_allowed(user))
- return
-
- if(custom_threat == "Yes")
- declaring_war = TRUE
- war_declaration = tgui_input_text(user, "Insert your custom declaration", "Declaration")
- declaring_war = FALSE
-
- if(!check_allowed(user) || !war_declaration)
- return
-
- GLOB.major_announcement.Announce(war_declaration, "Declaration of War", 'sound/effects/siren.ogg', msg_sanitized = TRUE)
- addtimer(CALLBACK(SSsecurity_level, TYPE_PROC_REF(/datum/controller/subsystem/security_level, set_level), SEC_LEVEL_GAMMA), 30 SECONDS)
-
- to_chat(user, "You've attracted the attention of powerful forces within the syndicate. A bonus bundle of telecrystals has been granted to your team. Great things await you if you complete the mission.")
- to_chat(user, "Your bonus telecrystals have been split between your team's uplinks.")
-
- for(var/obj/machinery/computer/shuttle/syndicate/S in GLOB.machines)
- S.challenge = TRUE
- S.challenge_time = world.time
-
- // No. of player - Min. Player to dec, divided by player per bonus, then multipled by TC per bonus. Rounded.
- total_tc = CHALLENGE_TELECRYSTALS + round(((length(get_living_players(exclude_nonhuman = FALSE, exclude_offstation = TRUE)) - CHALLENGE_MIN_PLAYERS)/CHALLENGE_SCALE_PLAYER) * CHALLENGE_SCALE_BONUS)
- share_telecrystals()
- SSshuttle.refuel_delay = CHALLENGE_SHUTTLE_DELAY
- qdel(src)
-
-/obj/item/nuclear_challenge/proc/share_telecrystals()
- var/player_tc
- var/remainder
-
- player_tc = round(total_tc / length(GLOB.nuclear_uplink_list)) //round to get an integer and not floating point
- remainder = total_tc % length(GLOB.nuclear_uplink_list)
-
- for(var/obj/item/radio/uplink/nuclear/U in GLOB.nuclear_uplink_list)
- U.hidden_uplink.uses += player_tc
- while(remainder > 0)
- for(var/obj/item/radio/uplink/nuclear/U in GLOB.nuclear_uplink_list)
- if(remainder <= 0)
- break
- U.hidden_uplink.uses++
- remainder--
-
-/obj/item/nuclear_challenge/proc/check_allowed(mob/living/user)
- if(declaring_war)
- to_chat(user, "You are already in the process of declaring war! Make your mind up.")
- return FALSE
- if(length(get_living_players(exclude_nonhuman = FALSE, exclude_offstation = TRUE)) < CHALLENGE_MIN_PLAYERS)
- to_chat(user, "The enemy crew is too small to be worth declaring war on.")
- return FALSE
- if(!is_admin_level(user.z))
- to_chat(user, "You have to be at your base to use this.")
- return FALSE
- if((world.time - SSticker.round_start_time) > CHALLENGE_TIME_LIMIT) // Only count after the round started
- to_chat(user, "It's too late to declare hostilities. Your benefactors are already busy with other schemes. You'll have to make do with what you have on hand.")
- return FALSE
- for(var/obj/machinery/computer/shuttle/syndicate/S in GLOB.machines)
- if(S.moved)
- to_chat(user, "The shuttle has already been moved! You have forfeit the right to declare war.")
- return FALSE
- return TRUE
-
-#undef CHALLENGE_TIME_LIMIT
-#undef CHALLENGE_MIN_PLAYERS
-#undef CHALLENGE_SHUTTLE_DELAY
-#undef CHALLENGE_TELECRYSTALS
-#undef CHALLENGE_SCALE_PLAYER
-#undef CHALLENGE_SCALE_BONUS
diff --git a/code/game/gamemodes/nuclear/pinpointer.dm b/code/game/gamemodes/nuclear/pinpointer.dm
deleted file mode 100644
index c316b9a78cd66..0000000000000
--- a/code/game/gamemodes/nuclear/pinpointer.dm
+++ /dev/null
@@ -1,565 +0,0 @@
-#define MODE_OFF 0
-#define MODE_DISK 1
-#define MODE_NUKE 2
-#define MODE_ADV 3
-#define MODE_SHIP 4
-#define MODE_OPERATIVE 5
-#define MODE_CREW 6
-// #define PINPOINTER_MODE_DET 7 // This mode is not defined here because it is used across multiple files, but it still exists.
-#define MODE_TENDRIL 8
-#define SETTING_DISK 0
-#define SETTING_LOCATION 1
-#define SETTING_OBJECT 2
-
-/obj/item/pinpointer
- name = "pinpointer"
- icon = 'icons/obj/device.dmi'
- icon_state = "pinoff"
- flags = CONDUCT
- slot_flags = ITEM_SLOT_PDA | ITEM_SLOT_BELT
- w_class = WEIGHT_CLASS_SMALL
- item_state = "electronic"
- throw_speed = 4
- throw_range = 20
- materials = list(MAT_METAL=500)
- resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ACID_PROOF
- var/obj/item/disk/nuclear/the_disk = null
- var/obj/machinery/nuclearbomb/the_bomb = null
- var/obj/machinery/nuclearbomb/syndicate/the_s_bomb = null // used by syndicate pinpointers.
- var/cur_index = 1 // Which index the current mode is
- var/mode = MODE_OFF // On which mode the pointer is at
- var/modes = list(MODE_DISK, MODE_NUKE) // Which modes are there
- var/shows_nuke_timer = TRUE
- var/syndicate = FALSE // Indicates pointer is syndicate, and points to the syndicate nuke.
- var/icon_off = "pinoff"
- var/icon_null = "pinonnull"
- var/icon_direct = "pinondirect"
- var/icon_close = "pinonclose"
- var/icon_medium = "pinonmedium"
- var/icon_far = "pinonfar"
-
-/obj/item/pinpointer/New()
- ..()
- GLOB.pinpointer_list += src
-
-/obj/item/pinpointer/Destroy()
- STOP_PROCESSING(SSfastprocess, src)
- GLOB.pinpointer_list -= src
- mode = MODE_OFF
- the_disk = null
- return ..()
-
-/obj/item/pinpointer/process()
- if(mode == MODE_DISK)
- workdisk()
- else if(mode == MODE_NUKE)
- workbomb()
-
-/obj/item/pinpointer/attack_self__legacy__attackchain(mob/user)
- if(mode == PINPOINTER_MODE_DET)
- return
- cycle(user)
-
-/obj/item/pinpointer/proc/cycle(mob/user)
- if(cur_index > length(modes))
- mode = MODE_OFF
- to_chat(user, "You deactivate [src].")
- STOP_PROCESSING(SSfastprocess, src)
- icon_state = icon_off
- cur_index = 1
- return
- if(cur_index == 1)
- START_PROCESSING(SSfastprocess, src)
- mode = modes[cur_index++]
- activate_mode(mode, user)
- to_chat(user, "[get_mode_text(mode)]")
-
-/obj/item/pinpointer/proc/get_mode_text(mode)
- switch(mode)
- if(MODE_DISK)
- return "Authentication Disk Locator active."
- if(MODE_NUKE)
- return "Nuclear Device Locator active."
- if(MODE_ADV)
- return "Advanced Pinpointer Online."
- if(MODE_SHIP)
- return "Shuttle Locator active."
- if(MODE_OPERATIVE)
- return "You point the pinpointer to the nearest operative."
- if(MODE_CREW)
- return "You turn on the pinpointer."
- if(MODE_TENDRIL)
- return "High energy scanner active"
-
-/obj/item/pinpointer/proc/activate_mode(mode, mob/user) //for crew pinpointer
- return
-
-/obj/item/pinpointer/proc/scandisk()
- if(!the_disk)
- the_disk = locate() in GLOB.nad_list
-
-/obj/item/pinpointer/proc/scanbomb()
- if(!syndicate)
- if(!the_bomb)
- the_bomb = locate() in GLOB.nuke_list
- else
- if(!the_s_bomb)
- the_s_bomb = locate() in GLOB.syndi_nuke_list
-
-/obj/item/pinpointer/proc/point_at_target(atom/target)
- if(!target)
- icon_state = icon_null
- return
-
- var/turf/T = get_turf(target)
- var/turf/L = get_turf(src)
-
- if(!(T && L) || (T.z != L.z))
- icon_state = icon_null
- else
- dir = get_dir(L, T)
- switch(get_dist(L, T))
- if(-1)
- icon_state = icon_direct
- if(1 to 8)
- icon_state = icon_close
- if(9 to 16)
- icon_state = icon_medium
- if(16 to INFINITY)
- icon_state = icon_far
-
-/obj/item/pinpointer/proc/workdisk()
- scandisk()
- point_at_target(the_disk)
-
-/obj/item/pinpointer/proc/workbomb()
- if(!syndicate)
- scanbomb()
- point_at_target(the_bomb)
- else
- scanbomb()
- point_at_target(the_s_bomb)
-
-/obj/item/pinpointer/examine(mob/user)
- . = ..()
- if(shows_nuke_timer)
- for(var/obj/machinery/nuclearbomb/bomb in GLOB.machines)
- if(bomb.timing)
- . += "Extreme danger. Arming signal detected. Time remaining: [bomb.timeleft]"
-
-/obj/item/pinpointer/advpinpointer
- name = "advanced pinpointer"
- desc = "A larger version of the normal pinpointer, this unit features a helpful quantum entanglement detection system to locate various objects that do not broadcast a locator signal. \n \
- Alt-click to toggle mode."
- modes = list(MODE_ADV)
- var/modelocked = FALSE // If true, user cannot change mode.
- var/turf/location = null
- var/obj/target = null
- var/setting = 0
-
-/obj/item/pinpointer/advpinpointer/process()
- switch(setting)
- if(SETTING_DISK)
- workdisk()
- if(SETTING_LOCATION)
- point_at_target(location)
- if(SETTING_OBJECT)
- point_at_target(target)
-
-/obj/item/pinpointer/advpinpointer/workdisk() //since mode works diffrently for advpinpointer
- scandisk()
- point_at_target(the_disk)
-
-/obj/item/pinpointer/advpinpointer/AltClick(mob/user)
- if(!isliving(user) || !Adjacent(user))
- return ..()
- toggle_mode(user)
-
-/obj/item/pinpointer/advpinpointer/proc/toggle_mode(mob/user)
- if(user.stat || HAS_TRAIT(user, TRAIT_HANDS_BLOCKED) || !Adjacent(user))
- return
-
- if(modelocked)
- to_chat(user, "[src] is locked. It can only track one specific target.")
- return
-
- target = null
- location = null
-
- switch(tgui_alert(user, "Please select the mode you want to put the pinpointer in.", "Pinpointer Mode Select", list("Location", "Disk Recovery", "Other Signature")))
- if("Location")
- setting = SETTING_LOCATION
-
- var/locationx = input(user, "Please input the x coordinate to search for.", "Location?" , "") as num
- if(!locationx || !(user in view(1,src)))
- return
- var/locationy = input(user, "Please input the y coordinate to search for.", "Location?" , "") as num
- if(!locationy || !(user in view(1,src)))
- return
-
- var/turf/Z = get_turf(src)
-
- location = locate(locationx,locationy,Z.z)
-
- to_chat(user, "You set the pinpointer to locate [locationx],[locationy]")
-
- toggle_on()
-
- if("Disk Recovery")
- setting = SETTING_DISK
- toggle_on()
-
- if("Other Signature")
- setting = SETTING_OBJECT
- switch(tgui_alert(user, "Search for item signature or DNA fragment?", "Signature Mode Select", list("Item", "DNA")))
- if("Item")
- var/list/item_names[0]
- var/list/item_paths[0]
- for(var/objective in GLOB.potential_theft_objectives)
- var/datum/theft_objective/T = objective
- var/name = initial(T.name)
- item_names += name
- item_paths[name] = initial(T.typepath)
- var/targetitem = tgui_input_list(user, "Select item to search for", "Select Item", item_names)
- if(!targetitem)
- return
-
- var/priority
- var/backup
- var/list/target_candidates = get_all_of_type(item_paths[targetitem], subtypes = TRUE)
- for(var/obj/item/candidate in target_candidates)
- var/cand_z = (get_turf(candidate)).z
- if(is_admin_level(cand_z))
- continue
- if(user.z != cand_z)
- if(!backup)
- backup = candidate
- continue
- // no candidate set yet, or check if there is a closer one
- if(!priority || (get_dist(user, candidate) < get_dist(user, priority)))
- priority = candidate
-
- if(priority)
- target = priority
- else
- target = backup
- if(target)
- to_chat(user, "Unable to find [targetitem] in this sector, falling back to off-sector tracking.")
-
- if(!target)
- to_chat(user, "Failed to locate [targetitem]!")
- return
-
- to_chat(user, "You set the pinpointer to locate [targetitem].")
-
- if("DNA")
- var/DNAstring = input("Input DNA string to search for." , "Please Enter String." , "")
- if(!DNAstring)
- return
- for(var/mob/living/carbon/C in GLOB.mob_list)
- if(!C.dna)
- continue
- if(C.dna.unique_enzymes == DNAstring)
- target = C
- break
-
- toggle_on()
-
-/obj/item/pinpointer/advpinpointer/proc/toggle_on()
- if(mode == MODE_OFF)
- cur_index = 1
- cycle(usr)
-
-///////////////////////
-//nuke op pinpointers//
-///////////////////////
-/obj/item/pinpointer/nukeop
- var/obj/docking_port/mobile/home = null
- slot_flags = ITEM_SLOT_BELT | ITEM_SLOT_PDA
- syndicate = TRUE
- modes = list(MODE_DISK, MODE_NUKE)
-
-/obj/item/pinpointer/nukeop/process()
- switch(mode)
- if(MODE_DISK)
- workdisk()
- if(MODE_NUKE)
- workbomb()
- if(MODE_SHIP)
- worklocation()
-
-/obj/item/pinpointer/nukeop/workdisk()
- if(GLOB.bomb_set) //If the bomb is set, lead to the shuttle
- mode = MODE_SHIP //Ensures worklocation() continues to work
- modes = list(MODE_SHIP)
- playsound(loc, 'sound/machines/twobeep.ogg', 50, 1) //Plays a beep
- visible_message("Shuttle Locator mode actived.") //Lets the mob holding it know that the mode has changed
- return //Get outta here
- scandisk()
- point_at_target(the_disk)
-
-/obj/item/pinpointer/nukeop/workbomb()
- if(GLOB.bomb_set) //If the bomb is set, lead to the shuttle
- mode = MODE_SHIP //Ensures worklocation() continues to work
- modes = list(MODE_SHIP)
- playsound(loc, 'sound/machines/twobeep.ogg', 50, 1) //Plays a beep
- visible_message("Shuttle Locator mode actived.") //Lets the mob holding it know that the mode has changed
- return //Get outta here
- scanbomb()
- point_at_target(the_s_bomb)
-
-/obj/item/pinpointer/nukeop/proc/worklocation()
- if(!GLOB.bomb_set)
- mode = MODE_DISK
- modes = list(MODE_DISK, MODE_NUKE)
- playsound(loc, 'sound/machines/twobeep.ogg', 50, 1)
- visible_message("Authentication Disk Locator mode actived.")
- return
- if(!home)
- home = SSshuttle.getShuttle("syndicate")
- if(!home)
- icon_state = icon_null
- return
- if(loc.z != home.z) //If you are on a different z-level from the shuttle
- icon_state = icon_null
- else
- point_at_target(home)
-
-/obj/item/pinpointer/operative
- name = "operative pinpointer"
- desc = "A pinpointer that leads to the first Syndicate operative detected."
- var/mob/living/carbon/nearest_op = null
- modes = list(MODE_OPERATIVE)
-
-/obj/item/pinpointer/operative/process()
- if(mode == MODE_OPERATIVE)
- workop()
- else
- icon_state = icon_off
-
-/obj/item/pinpointer/operative/proc/scan_for_ops()
- if(mode != MODE_OPERATIVE)
- return
- nearest_op = null //Resets nearest_op every time it scans
-
- var/closest_distance = 1000
- for(var/datum/mind/Mind in SSticker.mode.syndicates)
- var/mob/M = Mind.current
- if(!ishuman(M))
- continue
- var/current_dist = get_dist(M, get_turf(src))
- if(current_dist < closest_distance)
- nearest_op = M
- closest_distance = current_dist
-
-/obj/item/pinpointer/operative/proc/workop()
- if(mode == MODE_OPERATIVE)
- scan_for_ops()
- point_at_target(nearest_op, FALSE)
- else
- return FALSE
-
-/obj/item/pinpointer/operative/examine(mob/user)
- . = ..()
- if(mode == MODE_OPERATIVE)
- if(nearest_op)
- . += "Nearest operative detected is [nearest_op.real_name]."
- else
- . += "No operatives detected within scanning range."
-
-/obj/item/pinpointer/operative/nad
- desc = "A pinpointer that leads to the first Syndicate operative detected. Also has a mode to point towards the NAD."
- modes = list(MODE_OPERATIVE, MODE_DISK)
-
-/obj/item/pinpointer/operative/nad/process()
- switch(mode)
- if(MODE_DISK)
- workdisk()
- if(MODE_OPERATIVE)
- scan_for_ops()
- point_at_target(nearest_op, FALSE)
-
-/obj/item/pinpointer/crew
- name = "crew pinpointer"
- desc = "A handheld tracking device that points to crew suit sensors."
- shows_nuke_timer = FALSE
- icon_state = "pinoff_crew"
- icon_off = "pinoff_crew"
- icon_null = "pinonnull_crew"
- icon_direct = "pinondirect_crew"
- icon_close = "pinonclose_crew"
- icon_medium = "pinonmedium_crew"
- icon_far = "pinonfar_crew"
- modes = list(MODE_CREW)
- var/target = null //for targeting in processing
- var/target_set = FALSE //have we set a target at any point?
- ///Var to track the linked detective gun
- var/linked_gun_UID
-
-/obj/item/pinpointer/crew/attackby__legacy__attackchain(obj/item/I, mob/living/user)
- . = ..()
- if(istype(I, /obj/item/gun/energy/detective))
- link_gun(I.UID())
-
-/obj/item/pinpointer/crew/emp_act(severity)
- var/obj/item/gun/energy/detective/D = locateUID(linked_gun_UID)
- if(!D)
- return
- D.unlink()
- atom_say("EMP detected. Connection to revolver tracking system lost.")
-
-/obj/item/pinpointer/crew/proc/link_gun(gun_UID)
- var/obj/item/gun/energy/detective/D = locateUID(gun_UID)
- if(!D)
- return
- if((D.linked_pinpointer_UID && D.linked_pinpointer_UID != UID()) || linked_gun_UID)
- visible_message("The pinpointer pings to indicate either it or the gun is already linked.", "You hear a pinpointer pinging.")
- return
- D.link_pinpointer(UID())
- linked_gun_UID = gun_UID
- visible_message("The pinpointer pings twice to indicate a successful link.", "You hear a pinpointer pinging twice.")
-
-/obj/item/pinpointer/crew/proc/start_tracking()
- if(!linked_gun_UID)
- return
- var/obj/item/gun/energy/detective/D = locateUID(linked_gun_UID)
- if(!D)
- return
- var/target_UID = D.tracking_target_UID
- target = locateUID(target_UID)
- target_set = TRUE
- mode = PINPOINTER_MODE_DET
- visible_message("The pinpointer flickers as it begins tracking a target relayed from a detective's revolver.", "You hear a pinpointer flickering.")
- addtimer(CALLBACK(src, PROC_REF(stop_tracking)), 1 MINUTES, TIMER_UNIQUE)
- START_PROCESSING(SSfastprocess, src)
-
-/obj/item/pinpointer/crew/proc/stop_tracking()
- visible_message("The pinpointer powers down, no longer receiving signals from a detective's revolver.", "You hear a pinpointer powering down.")
- target = null
- target_set = FALSE
- mode = MODE_OFF
- icon_state = icon_off
- STOP_PROCESSING(SSfastprocess, src)
-
-/obj/item/pinpointer/crew/proc/trackable(mob/living/carbon/human/H)
- if(mode == PINPOINTER_MODE_DET) // Sensors? Where we're going, we dont need sensors!
- var/turf/here = get_turf(src)
- var/turf/there = get_turf(H)
- return istype(there) && istype(here) && there.z == here.z
- if(H && istype(H.w_uniform, /obj/item/clothing/under))
- var/turf/here = get_turf(src)
- var/obj/item/clothing/under/U = H.w_uniform
- // Suit sensors must be on maximum.
- if(!U.has_sensor || U.sensor_mode < 3)
- return FALSE
- var/turf/there = get_turf(U)
- return istype(there) && there.z == here.z
- return FALSE
-
-/obj/item/pinpointer/crew/process()
- if(mode != MODE_OFF && target_set)
- point_at_target(target)
-
-/obj/item/pinpointer/crew/point_at_target(atom/target)
- if(!target || !trackable(target))
- icon_state = icon_null
- return
-
- ..(target)
-
-/obj/item/pinpointer/crew/activate_mode(mode, mob/user)
- var/list/name_counts = list()
- var/list/names = list()
-
- for(var/thing in GLOB.human_list)
- var/mob/living/carbon/human/H = thing
- if(!trackable(H))
- continue
-
- var/name = "Unknown"
- if(H.wear_id)
- var/obj/item/card/id/I = H.wear_id.GetID()
- if(I)
- name = I.registered_name
-
- while(name in name_counts)
- name_counts[name]++
- name = "[name] ([name_counts[name]])"
- names[name] = H
- name_counts[name] = 1
-
- if(!length(names))
- user.visible_message("[user]'s pinpointer fails to detect a signal.", "Your pinpointer fails to detect a signal.")
- return
-
- var/A = tgui_input_list(user, "Person to track", "Pinpoint", names)
- if(!src || !user || (user.get_active_hand() != src) || user.incapacitated() || !A)
- return
-
- target = names[A]
- target_set = TRUE
- user.visible_message("[user] activates [user.p_their()] pinpointer.", "You activate your pinpointer.")
-
-/obj/item/pinpointer/crew/centcom
- name = "centcom pinpointer"
- desc = "A handheld tracking device that tracks crew based on remote centcom sensors."
-
-/obj/item/pinpointer/crew/centcom/trackable(mob/living/carbon/human/H)
- var/turf/here = get_turf(src)
- var/turf/there = get_turf(H)
- return istype(there) && istype(here) && there.z == here.z
-
-/obj/item/pinpointer/tendril
- name = "ancient scanning unit"
- desc = "Convenient that the scanning unit for the robot survived. Seems to point to the tendrils around here."
- icon_state = "pinoff_ancient"
- icon_off = "pinoff_ancient"
- icon_null = "pinonnull_ancient"
- icon_direct = "pinondirect_ancient"
- icon_close = "pinonclose_ancient"
- icon_medium = "pinonmedium_ancient"
- icon_far = "pinonfar_ancient"
- modes = list(MODE_TENDRIL)
- var/obj/structure/spawner/lavaland/target
-
-/obj/item/pinpointer/tendril/process()
- if(mode == MODE_TENDRIL)
- worktendril()
- point_at_target(target, FALSE)
- else
- icon_state = icon_off
-
-/obj/item/pinpointer/tendril/proc/worktendril()
- if(mode == MODE_TENDRIL)
- scan_for_tendrils()
- point_at_target(target)
- else
- return FALSE
-
-/obj/item/pinpointer/tendril/proc/scan_for_tendrils()
- if(mode == MODE_TENDRIL)
- target = null //Resets nearest_op every time it scans
- var/closest_distance = 1000
- for(var/obj/structure/spawner/lavaland/T in GLOB.tendrils)
- var/temp_distance = get_dist(T, get_turf(src))
- if(temp_distance < closest_distance)
- target = T
- closest_distance = temp_distance
-
-/obj/item/pinpointer/tendril/examine(mob/user)
- . = ..()
- if(mode == MODE_TENDRIL)
- . += "Number of high energy signatures remaining: [length(GLOB.tendrils)]"
-
-
-#undef MODE_OFF
-#undef MODE_DISK
-#undef MODE_NUKE
-#undef MODE_ADV
-#undef MODE_SHIP
-#undef MODE_OPERATIVE
-#undef MODE_CREW
-#undef MODE_TENDRIL
-#undef SETTING_DISK
-#undef SETTING_LOCATION
-#undef SETTING_OBJECT
diff --git a/code/game/gamemodes/objective.dm b/code/game/gamemodes/objective.dm
deleted file mode 100644
index d324ad250fe2c..0000000000000
--- a/code/game/gamemodes/objective.dm
+++ /dev/null
@@ -1,946 +0,0 @@
-/// Stores a reference to every [objective][/datum/objective] which currently exists.
-GLOBAL_LIST_EMPTY(all_objectives)
-// Used in admin procs to give them a pretty list to look at, and to also have sane reusable code.
-/// Stores objective [names][/datum/objective/var/name] as list keys, and their corresponding typepaths as list values.
-GLOBAL_LIST_EMPTY(admin_objective_list)
-
-GLOBAL_LIST_INIT(potential_theft_objectives, (subtypesof(/datum/theft_objective) - /datum/theft_objective/number - /datum/theft_objective/unique))
-
-/datum/objective
- /**
- * Proper name of the objective. Not player facing, only shown to admins when adding objectives.
- * Leave as null (or override to null) if you don't want admins to see that objective as a viable one to add (such as the mindslave objective).
- */
- var/name
- /**
- * Owner of the objective.
- * Note that it's fine to set this directly, but when needing to check completion of the objective or otherwise check conditions on the owner of the objective,
- * always use `get_owners()`, and check against ALL the owners. `get_owners()` accounts for objectives that may be team based and therefore have multiple owners.
- */
- var/datum/mind/owner
- /// The target of the objective.
- var/datum/mind/target
- /// The team the objective belongs to, if any.
- var/datum/team/team
- /// What the owner is supposed to do to complete the objective.
- var/explanation_text = "Nothing"
- /// If the objective should have `find_target()` called for it.
- var/needs_target = TRUE
- /// If they are focused on a particular number. Steal objectives have their own counter.
- var/target_amount = 0
- /// If the objective has been completed.
- var/completed = FALSE
- /// If the objective is compatible with martyr objective, i.e. if you can still do it while dead.
- var/martyr_compatible = FALSE
- /// List of jobs that the objective will target if possible, any crew if not.
- var/list/target_jobs = list()
- /// The department that'll be targeted by this objective. If set, fills target_jobs with jobs from that department.
- var/target_department
- /// If set, steal targets will be pulled from this list
- var/list/steal_list = list()
- /// Contains the flags needed to meet the conditions of a valid target, such as mindshielded or syndicate agent.
- var/flags_target
- var/datum/objective_holder/holder
-
- /// What is the text we show when our objective is delayed?
- var/delayed_objective_text = "This is a bug! Report it on the github and ask an admin what type of objective"
-
-/datum/objective/New(text, datum/team/team_to_join, datum/mind/_owner)
- . = ..()
- SHOULD_CALL_PARENT(TRUE)
- GLOB.all_objectives += src
- if(text)
- explanation_text = text
- if(team_to_join)
- team = team_to_join
- if(target_department)
- target_jobs = setup_target_jobs()
- if(_owner)
- owner = _owner
-
-/datum/objective/Destroy()
- GLOB.all_objectives -= src
- owner = null
- target = null
- team = null
- holder = null
- return ..()
-
-/datum/objective/proc/check_completion()
- return completed
-
-/datum/objective/proc/found_target()
- return target
-
-/**
- * This is for objectives that need to register signals, so place them in here.
- */
-/datum/objective/proc/establish_signals()
- return
-
-/**
- * This is for objectives that have reason to update their text, such as target changes.
- */
-/datum/objective/proc/update_explanation_text()
- stack_trace("Objective [type]'s update_explanation_text was not overridden.")
-
-/**
- * Get all owners of the objective, including ones from the objective's team, if it has one.
- *
- * Use this over directly referencing `owner` in most cases.
- */
-/datum/objective/proc/get_owners()
- . = length(team?.members) ? team.members.Copy() : list()
- if(owner)
- . += owner
-
-/datum/proc/is_invalid_target(datum/mind/possible_target) // Originally an Objective proc. Changed to a datum proc to allow for the proc to be run on minds, before the objective is created
- if(!ishuman(possible_target.current))
- return TARGET_INVALID_NOT_HUMAN
- if(possible_target.current.stat == DEAD)
- return TARGET_INVALID_DEAD
- if(!possible_target.key)
- return TARGET_INVALID_NOCKEY
- if(possible_target.current)
- var/turf/current_location = get_turf(possible_target.current)
- if(current_location && !is_level_reachable(current_location.z))
- return TARGET_INVALID_UNREACHABLE
- if(isgolem(possible_target.current))
- return TARGET_INVALID_GOLEM
- if(possible_target.offstation_role)
- return TARGET_INVALID_EVENT
- if(HAS_TRAIT(possible_target.current, TRAIT_CRYO_DESPAWNING))
- return TARGET_CRYOING
-
-/datum/objective/is_invalid_target(datum/mind/possible_target)
- . = ..()
- if(.)
- return
- for(var/datum/mind/M in get_owners())
- if(possible_target == M)
- return TARGET_INVALID_IS_OWNER
- if(possible_target in M.targets)
- return TARGET_INVALID_IS_TARGET
- if(SEND_SIGNAL(src, COMSIG_OBJECTIVE_CHECK_VALID_TARGET, possible_target) & OBJECTIVE_INVALID_TARGET)
- return TARGET_INVALID_BLACKLISTED
-
-
-/datum/objective/proc/find_target(list/target_blacklist)
- if(!needs_target)
- return
-
- var/list/possible_targets = list()
- for(var/datum/mind/possible_target in SSticker.minds)
- if(is_invalid_target(possible_target) || (possible_target in target_blacklist))
- continue
- if((flags_target & MINDSHIELDED_TARGET) && !ismindshielded(possible_target.current))
- continue
- if((flags_target & UNMINDSHIELDED_TARGET) && ismindshielded(possible_target.current))
- continue
- if((flags_target & SYNDICATE_TARGET) && possible_target.special_role != SPECIAL_ROLE_TRAITOR)
- continue
- if(length(target_jobs) && !(possible_target.assigned_role in target_jobs))
- continue
- possible_targets += possible_target
-
- if(!length(possible_targets)) // If we can't find anyone, try with less restrictions
- for(var/datum/mind/possible_target in SSticker.minds)
- if(is_invalid_target(possible_target) || (possible_target in target_blacklist))
- continue
- possible_targets += possible_target
-
- if(length(possible_targets) > 0)
- target = pick(possible_targets)
-
- SEND_SIGNAL(src, COMSIG_OBJECTIVE_TARGET_FOUND, target)
- update_explanation_text()
- return target
-
-/**
- * Called when the objective's target goes to cryo.
- */
-/datum/objective/proc/on_target_cryo()
- var/list/owners = get_owners()
- for(var/datum/mind/M in owners)
- to_chat(M.current, " You get the feeling your target is no longer within reach. Time for Plan [pick("A","B","C","D","X","Y","Z")]. Objectives updated!")
- SEND_SOUND(M.current, sound('sound/ambience/alarm4.ogg'))
- target = null
- INVOKE_ASYNC(src, PROC_REF(post_target_cryo), owners)
-
-/datum/objective/proc/post_target_cryo(list/owners)
- find_target()
- if(!target)
- holder.remove_objective(src)
- // even if we have to remove the objective, still announce it
- for(var/datum/mind/M in owners)
- var/list/messages = M.prepare_announce_objectives(FALSE)
- to_chat(M.current, chat_box_red(messages.Join(" ")))
-
-// Borgs, brains, AIs, etc count as dead for traitor objectives
-/datum/objective/proc/is_special_dead(mob/target_current, check_silicon = TRUE)
- if(check_silicon && issilicon(target_current))
- return TRUE
- return isbrain(target_current) || istype(target_current, /mob/living/simple_animal/spiderbot)
-
-// Setup and return the objective target jobs list based on target department
-/datum/objective/proc/setup_target_jobs()
- if(!target_department)
- return
- . = list()
- switch(target_department)
- if(DEPARTMENT_COMMAND)
- . = GLOB.command_head_positions.Copy()
- if(DEPARTMENT_MEDICAL)
- . = GLOB.medical_positions.Copy()
- if(DEPARTMENT_ENGINEERING)
- . = GLOB.engineering_positions.Copy()
- if(DEPARTMENT_SCIENCE)
- . = GLOB.science_positions.Copy()
- if(DEPARTMENT_SECURITY)
- . = GLOB.active_security_positions.Copy()
- if(DEPARTMENT_SUPPLY)
- . = GLOB.supply_positions.Copy()
- if(DEPARTMENT_SERVICE)
- . = GLOB.service_positions.Copy()
-
-/datum/objective/assassinate
- name = "Assassinate"
- martyr_compatible = TRUE
- delayed_objective_text = "Your objective is to assassinate another crewmember. You will receive further information in a few minutes."
-
-/datum/objective/assassinate/update_explanation_text()
- if(target?.current)
- explanation_text = "Assassinate [target.current.real_name], the [target.assigned_role]."
- else
- explanation_text = "Free Objective"
-
-/datum/objective/assassinate/check_completion()
- if(target?.current)
- if(target.current.stat == DEAD)
- return TRUE
- if(is_special_dead(target.current)) //Borgs/brains/AIs count as dead for traitor objectives. --NeoFite
- return TRUE
- if(!target.current.ckey)
- return TRUE
- return FALSE
- return TRUE
-
-/datum/objective/assassinateonce
- name = "Assassinate once"
- martyr_compatible = TRUE
- delayed_objective_text = "Your objective is to teach another crewmember a lesson. You will receive further information in a few minutes."
- var/won = FALSE
-
-/datum/objective/assassinateonce/update_explanation_text()
- if(target?.current)
- explanation_text = "Teach [target.current.real_name], the [target.assigned_role], a lesson they will not forget. The target only needs to die once for success."
- establish_signals()
- else
- explanation_text = "Free Objective"
-
-/datum/objective/assassinateonce/establish_signals()
- RegisterSignal(target.current, list(COMSIG_MOB_DEATH, COMSIG_PARENT_QDELETING), PROC_REF(check_midround_completion))
-
-/datum/objective/assassinateonce/check_completion()
- return won || completed || !target?.current?.ckey
-
-/datum/objective/assassinateonce/proc/check_midround_completion()
- won = TRUE
- UnregisterSignal(target.current, list(COMSIG_MOB_DEATH, COMSIG_PARENT_QDELETING))
-
-/datum/objective/assassinateonce/on_target_cryo()
- if(won)
- return
- return ..()
-
-/datum/objective/mutiny
- name = "Mutiny"
- martyr_compatible = TRUE
-
-/datum/objective/mutiny/update_explanation_text()
- if(target?.current)
- explanation_text = "Assassinate or exile [target.current.real_name], the [target.assigned_role]."
- else
- explanation_text = "Free Objective"
-
-/datum/objective/mutiny/is_invalid_target(datum/mind/possible_target)
- . = ..()
- if(.)
- return
- if(!(possible_target in SSticker.mode.get_all_heads()))
- return TARGET_INVALID_NOTHEAD
-
-/datum/objective/mutiny/check_completion()
- if(target?.current)
- if(target.current.stat == DEAD || !ishuman(target.current) || !target.current.ckey || !target.current.client)
- return TRUE
- var/turf/T = get_turf(target.current)
- if(T && !is_station_level(T.z)) //If they leave the station they count as dead for this
- return TRUE
- return FALSE
- return TRUE
-
-/datum/objective/mutiny/on_target_cryo()
- // We don't want revs to get objectives that aren't for heads of staff. Letting
- // them win or lose based on cryo is silly so we remove the objective.
- if(team)
- team.remove_team_objective(src)
- return
- qdel(src)
-
-/datum/objective/maroon
- name = "Maroon"
- martyr_compatible = FALSE
- delayed_objective_text = "Your objective is to make sure another crewmember doesn't leave on the Escape Shuttle. You will receive further information in a few minutes."
-
-/datum/objective/maroon/update_explanation_text()
- if(target?.current)
- explanation_text = "Prevent [target.current.real_name], the [target.assigned_role] from escaping alive."
- else
- explanation_text = "Free Objective"
-
-/datum/objective/maroon/check_completion()
- if(target?.current)
- if(target.current.stat == DEAD)
- return TRUE
- if(!target.current.ckey)
- return TRUE
- if(is_special_dead(target.current))
- return TRUE
- var/turf/T = get_turf(target.current)
- if(is_admin_level(T.z))
- return FALSE
- return TRUE
- return TRUE
-
-/// I want braaaainssss
-/datum/objective/debrain
- name = "Debrain"
- martyr_compatible = FALSE
- delayed_objective_text = "Your objective is to steal another crewmember's brain. You will receive further information in a few minutes."
-
-/datum/objective/debrain/is_invalid_target(datum/mind/possible_target)
- . = ..()
- if(.)
- return
- // If the target is a changeling, then it's an invalid target. Since changelings can not be debrained.
- if(IS_CHANGELING(possible_target.current))
- return TARGET_INVALID_CHANGELING
-
-/datum/objective/debrain/update_explanation_text()
- if(target?.current)
- explanation_text = "Steal the brain of [target.current.real_name], the [target.assigned_role]."
- else
- explanation_text = "Free Objective"
-
-/datum/objective/debrain/check_completion()
- if(!target) // If it's a free objective.
- return TRUE
- if(!target.current || !isbrain(target.current))
- return FALSE
- for(var/datum/mind/M in get_owners())
- if(QDELETED(M.current))
- continue // Maybe someone who's alive has the brain.
- if(target.current in M.current.GetAllContents())
- return TRUE
- return FALSE
-
-
-/// The opposite of killing a dude.
-/datum/objective/protect
- name = "Protect"
- martyr_compatible = TRUE
-
-/datum/objective/protect/update_explanation_text()
- if(target?.current)
- explanation_text = "Protect [target.current.real_name], the [target.assigned_role]."
- else
- explanation_text = "Free Objective"
-
-/datum/objective/protect/check_completion()
- if(!target) //If it's a free objective.
- return TRUE
- if(target.current)
- if(target.current.stat == DEAD)
- return FALSE
- if(is_special_dead(target.current))
- return FALSE
- return TRUE
- return FALSE
-
-/// subytpe for mindslave implants
-/datum/objective/protect/mindslave
- needs_target = FALSE // To be clear, this objective should have a target, but it will always be manually set to the mindslaver through the mindslave antag datum.
-
-// This objective should only be given to a single owner. We can use `owner` and not `get_owners()`.
-/datum/objective/protect/mindslave/on_target_cryo()
- if(owner?.current)
- SEND_SOUND(owner.current, sound('sound/ambience/alarm4.ogg'))
- owner.remove_antag_datum(/datum/antagonist/mindslave)
- to_chat(owner.current, " You notice that your master has entered cryogenic storage, and revert to your normal self.")
- log_admin("[key_name(owner.current)]'s mindslave master has cryo'd, and is no longer a mindslave.")
- message_admins("[key_name_admin(owner.current)]'s mindslave master has cryo'd, and is no longer a mindslave.") //Since they were on antag hud earlier, this feels important to log
- qdel(src)
-
-/datum/objective/hijack
- name = "Hijack"
- martyr_compatible = FALSE //Technically you won't get both anyway.
- explanation_text = "Hijack the shuttle by escaping on it with no loyalist Nanotrasen crew on board and free. \
- Syndicate agents, other enemies of Nanotrasen, cyborgs, pets, and cuffed/restrained hostages may be allowed on the shuttle alive. \
- Alternatively, hack the shuttle console multiple times (by alt clicking on it) until the shuttle directions are corrupted."
- needs_target = FALSE
-
-/datum/objective/hijack/check_completion()
- if(SSshuttle.emergency.mode < SHUTTLE_ENDGAME)
- return FALSE
- for(var/datum/mind/M in get_owners())
- if(QDELETED(M.current) || M.current.stat != CONSCIOUS || issilicon(M.current) || get_area(M.current) != SSshuttle.emergency.areaInstance)
- return FALSE
- return SSshuttle.emergency.is_hijacked()
-
-/datum/objective/hijackclone
- name = "Hijack (with clones)"
- explanation_text = "Hijack the shuttle by ensuring only you (or your copies) escape."
- martyr_compatible = FALSE
- needs_target = FALSE
-
-// This objective should only be given to a single owner, because the "copies" can only copy one person.
-// We're fine to use `owner` instead of `get_owners()`.
-/datum/objective/hijackclone/check_completion()
- if(SSshuttle.emergency.mode < SHUTTLE_ENDGAME || !owner.current)
- return FALSE
-
- var/area/A = SSshuttle.emergency.areaInstance
-
- for(var/mob/living/player in GLOB.player_list) //Make sure nobody else is onboard
- if(player.mind && player.mind != owner)
- if(player.stat != DEAD)
- if(issilicon(player))
- continue
- if(get_area(player) == A)
- if(player.real_name != owner.current.real_name && !istype(get_turf(player.mind.current), /turf/simulated/floor/mineral/plastitanium/red/brig))
- return FALSE
-
- for(var/mob/living/player in GLOB.player_list) //Make sure at least one of you is onboard
- if(player.mind && player.mind != owner)
- if(player.stat != DEAD)
- if(issilicon(player))
- continue
- if(get_area(player) == A)
- if(player.real_name == owner.current.real_name && !istype(get_turf(player.mind.current), /turf/simulated/floor/mineral/plastitanium/red/brig))
- return TRUE
- return FALSE
-
-/datum/objective/block
- name = "Silicon hijack"
- explanation_text = "Hijack the shuttle by alt-clicking on the shuttle console. Do not let the crew wipe you off of it! \
- Crew and agents can be on the shuttle when you do this, and may try to wipe you! \
- Using the doomsday device successfully is also an option."
- martyr_compatible = FALSE
- needs_target = FALSE
-
-/datum/objective/block/check_completion()
- for(var/datum/mind/M in get_owners())
- if(!M.current || !issilicon(M.current))
- return FALSE
- if(SSticker.mode.station_was_nuked)
- return TRUE
- if(SSshuttle.emergency.aihacked)
- return TRUE
- if(SSshuttle.emergency.mode < SHUTTLE_ENDGAME)
- return FALSE
- if(!SSshuttle.emergency.is_hijacked(TRUE))
- return FALSE
- return TRUE
-
-/datum/objective/escape
- name = "Escape"
- explanation_text = "Escape on the shuttle or an escape pod alive and free."
- needs_target = FALSE
-
-/datum/objective/escape/check_completion()
- var/list/owners = get_owners()
- for(var/datum/mind/M in owners)
- // These are mandatory conditions, they should come before the freebie conditions below.
- if(QDELETED(M.current) || M.current.stat == DEAD || is_special_dead(M.current))
- return FALSE
-
- if(SSticker.force_ending) // This one isn't their fault, so lets just assume good faith.
- return TRUE
- if(SSticker.mode.station_was_nuked) // If they escaped the blast somehow, let them win.
- return TRUE
- if(SSshuttle.emergency.mode < SHUTTLE_ENDGAME)
- return FALSE
-
- for(var/datum/mind/M in owners)
- var/turf/location = get_turf(M.current)
- if(istype(location, /turf/simulated/floor/mineral/plastitanium/red/brig))
- return FALSE
- if(!location.onCentcom() && !location.onSyndieBase())
- return FALSE
-
- return TRUE
-
-/datum/objective/escape/escape_with_identity
- name = "Escape With Identity"
- /// Stored because the target's `[mob/var/real_name]` can change over the course of the round.
- var/target_real_name
- /// If the objective has an assassinate objective tied to it.
- var/has_assassinate_objective = FALSE
-
-/datum/objective/escape/escape_with_identity/New(text, datum/team/team_to_join, datum/mind/_owner, datum/objective/assassinate/assassinate)
- ..()
- if(!assassinate)
- return
- target = assassinate.target
- target_real_name = assassinate.target.current.real_name
- explanation_text = "Escape on the shuttle or an escape pod with the identity of [target_real_name], the [target.assigned_role] while wearing [target.p_their()] identification card."
- has_assassinate_objective = TRUE
- RegisterSignal(assassinate, COMSIG_OBJECTIVE_TARGET_FOUND, PROC_REF(assassinate_found_target))
- RegisterSignal(assassinate, COMSIG_OBJECTIVE_CHECK_VALID_TARGET, PROC_REF(assassinate_checking_target))
-
-/datum/objective/escape/escape_with_identity/is_invalid_target(datum/mind/possible_target)
- if(..() || !possible_target.current.client)
- return TRUE
- // If the target is geneless, then it's an invalid target.
- return HAS_TRAIT(possible_target.current, TRAIT_GENELESS)
-
-/datum/objective/escape/escape_with_identity/update_explanation_text()
- if(target?.current)
- target_real_name = target.current.real_name
- explanation_text = "Escape on the shuttle or an escape pod with the identity of [target_real_name], the [target.assigned_role] while wearing [target.p_their()] identification card."
- else
- explanation_text = "Free Objective"
-
-/datum/objective/escape/escape_with_identity/proc/assassinate_checking_target(datum/source, datum/mind/possible_target)
- SIGNAL_HANDLER
- if(!possible_target.current.client || HAS_TRAIT(possible_target.current, TRAIT_GENELESS))
- // Stop our linked assassinate objective from choosing a clientless/geneless target.
- return OBJECTIVE_INVALID_TARGET
- return OBJECTIVE_VALID_TARGET
-
-/datum/objective/escape/escape_with_identity/proc/assassinate_found_target(datum/source, datum/mind/new_target)
- SIGNAL_HANDLER
- if(new_target)
- target = new_target
- update_explanation_text()
- return
- // The assassinate objective was unable to find a new target after the old one cryo'd as was qdel'd. We're on our own.
- find_target()
- has_assassinate_objective = FALSE
-
-/datum/objective/escape/escape_with_identity/on_target_cryo()
- if(has_assassinate_objective)
- return // Our assassinate objective will handle this.
- ..()
-
-/datum/objective/escape/escape_with_identity/post_target_cryo()
- if(has_assassinate_objective)
- return // Our assassinate objective will handle this.
- ..()
-
-// This objective should only be given to a single owner since only 1 person can have the ID card of the target.
-// We're fine to use `owner` instead of `get_owners()`.
-/datum/objective/escape/escape_with_identity/check_completion()
- if(!target_real_name)
- return TRUE
- if(!ishuman(owner.current))
- return FALSE
- var/mob/living/carbon/human/H = owner.current
- if(..())
- if(H.dna.real_name == target_real_name)
- if(H.get_id_name() == target_real_name)
- return TRUE
- return FALSE
-
-/datum/objective/survive
- name = "Survive"
- explanation_text = "Stay alive until the end."
- needs_target = FALSE
-
-/datum/objective/survive/check_completion()
- for(var/datum/mind/M in get_owners())
- if(QDELETED(M.current) || M.current.stat == DEAD || is_special_dead(M.current, check_silicon = FALSE))
- return FALSE
- if(issilicon(M.current) && !M.is_original_mob(M.current))
- return FALSE
- return TRUE
-
-/datum/objective/nuclear
- name = "Nuke station"
- explanation_text = "Destroy the station with a nuclear device."
- martyr_compatible = TRUE
- needs_target = FALSE
-
-/datum/objective/steal
- name = "Steal Item"
- martyr_compatible = FALSE
- delayed_objective_text = "Your objective is to steal a high-value item. You will receive further information in a few minutes."
- var/theft_area
- var/datum/theft_objective/steal_target
-
-/datum/objective/steal/found_target()
- return steal_target
-
-/datum/objective/steal/proc/get_location()
- return steal_target.location_override || "an unknown area"
-
-/datum/objective/steal/find_target(list/target_blacklist)
- var/potential
- if(length(steal_list))
- potential = steal_list.Copy()
- else
- potential = GLOB.potential_theft_objectives.Copy()
- while(!steal_target && length(potential))
- var/thefttype = pick_n_take(potential)
- if(locate(thefttype) in target_blacklist)
- continue
- var/datum/theft_objective/O = new thefttype
- var/has_invalid_owner = FALSE
- for(var/datum/mind/M in get_owners())
- if((M.assigned_role in O.protected_jobs) || (O in M.targets))
- has_invalid_owner = TRUE
- break
- if(has_invalid_owner)
- continue
- if(!O.check_objective_conditions())
- continue
- if(O.flags & THEFT_FLAG_UNIQUE)
- continue
-
- steal_target = O
- update_explanation_text()
- if(steal_target.special_equipment)
- give_kit(steal_target.special_equipment)
- return
- explanation_text = "Free Objective."
-
-/datum/objective/steal/proc/select_target()
- var/list/possible_items_all = GLOB.potential_theft_objectives + "custom" + "random"
- var/new_target = input("Select target:", "Objective target", null) as null|anything in possible_items_all
- if(!new_target)
- return
-
- if(new_target == "custom")
- var/obj/item/steal_target_path = input("Select type:","Type") as null|anything in typesof(/obj/item)
- if(!steal_target_path)
- return
-
- var/theft_objective_name = sanitize(copytext_char(input("Enter target name:", "Objective target", initial(steal_target_path.name)) as text|null, 1, MAX_NAME_LEN))
- if(!theft_objective_name)
- return
-
- var/datum/theft_objective/target_theft_objective = new
- target_theft_objective.typepath = steal_target_path
- target_theft_objective.name = theft_objective_name
- steal_target = target_theft_objective
- explanation_text = "Steal [theft_objective_name]."
- return steal_target
- else if(new_target == "random")
- return TRUE
-
- steal_target = new new_target
- update_explanation_text()
- if(steal_target.special_equipment) // We have to do it with a callback because mind/Topic creates the objective without an owner
- addtimer(CALLBACK(src, PROC_REF(hand_out_equipment)), 5 SECONDS, TIMER_DELETE_ME)
- return steal_target
-
-/datum/objective/steal/proc/hand_out_equipment()
- give_kit(steal_target?.special_equipment)
-
-/datum/objective/steal/update_explanation_text()
- explanation_text = "Steal [steal_target.name]. One was last seen in [get_location()]. "
- if(length(steal_target.protected_jobs) && steal_target.job_possession)
- explanation_text += "It may also be in the possession of the [english_list(steal_target.protected_jobs, and_text = " or ")]. "
- explanation_text += steal_target.extra_information
-
-/datum/objective/steal/check_completion()
- if(!steal_target)
- return TRUE // Free Objective
-
- for(var/datum/mind/M in get_owners())
- if(!M.current)
- continue
- for(var/obj/I in M.current.GetAllContents())
- if((istype(I, steal_target.typepath) || (I.type in steal_target.altitems)) && steal_target.check_special_completion(I))
- return TRUE
- return FALSE
-
-/datum/objective/steal/proc/give_kit(obj/item/item_path)
- var/list/datum/mind/objective_owners = get_owners()
- if(!length(objective_owners))
- return
-
- var/obj/item/item_to_give = new item_path
- var/static/list/slots = list(
- "backpack" = ITEM_SLOT_IN_BACKPACK,
- "left pocket" = ITEM_SLOT_LEFT_POCKET,
- "right pocket" = ITEM_SLOT_RIGHT_POCKET,
- "left hand" = ITEM_SLOT_LEFT_HAND,
- "right hand" = ITEM_SLOT_RIGHT_HAND,
- )
-
- for(var/datum/mind/kit_receiver_mind as anything in shuffle(objective_owners))
- var/mob/living/carbon/human/kit_receiver = kit_receiver_mind.current
- if(!kit_receiver)
- continue
- var/where = kit_receiver.equip_in_one_of_slots(item_to_give, slots)
- if(!where)
- continue
-
- to_chat(kit_receiver, "
In your [where] is a box containing items and instructions to help you with your steal objective. ")
- for(var/datum/mind/objective_owner as anything in objective_owners)
- if(kit_receiver_mind == objective_owner || !objective_owner.current)
- continue
-
- to_chat(objective_owner.current, "
[kit_receiver] has received a box containing items and instructions to help you with your steal objective. ")
-
- return
-
- qdel(item_to_give)
-
- for(var/datum/mind/objective_owner as anything in objective_owners)
- var/mob/living/carbon/human/failed_receiver = objective_owner.current
- if(!failed_receiver)
- continue
-
- to_chat(failed_receiver, "Unfortunately, you weren't able to get a stealing kit. This is very bad and you should adminhelp immediately (press F1).")
- message_admins("[ADMIN_LOOKUPFLW(failed_receiver)] Failed to spawn with their [item_path] theft kit.")
-
-/datum/objective/absorb
- name = "Absorb DNA"
- needs_target = FALSE
-
-/datum/objective/absorb/New(text, datum/team/team_to_join)
- . = ..()
- gen_amount_goal()
-
-/datum/objective/absorb/proc/gen_amount_goal(lowbound = 6, highbound = 8)
- target_amount = rand (lowbound,highbound)
- if(SSticker)
- var/n_p = 1 //autowin
- if(SSticker.current_state == GAME_STATE_SETTING_UP)
- for(var/mob/new_player/P in GLOB.player_list)
- if(P.client && P.ready && !(P.mind in get_owners()))
- if(P.client.prefs && (P.client.prefs.active_character.species == "Machine")) // Special check for species that can't be absorbed. No better solution.
- continue
- n_p++
- else if(SSticker.current_state == GAME_STATE_PLAYING)
- for(var/mob/living/carbon/human/P in GLOB.player_list)
- if(HAS_TRAIT(P, TRAIT_GENELESS))
- continue
- if(P.client && !(P.mind in SSticker.mode.changelings) && !(P.mind in get_owners()))
- n_p++
- target_amount = min(target_amount, n_p)
- update_explanation_text()
- return target_amount
-
-/datum/objective/absorb/update_explanation_text()
- explanation_text = "Acquire [target_amount] compatible genomes. The 'Extract DNA Sting' can be used to stealthily get genomes without killing somebody."
-
-/datum/objective/absorb/check_completion()
- for(var/datum/mind/M in get_owners())
- var/datum/antagonist/changeling/cling = M?.has_antag_datum(/datum/antagonist/changeling)
- if(cling?.absorbed_dna && (cling.absorbed_count >= target_amount))
- return TRUE
- return FALSE
-
-/datum/objective/destroy
- name = "Destroy AI"
- martyr_compatible = TRUE
- delayed_objective_text = "Your objective is to destroy an Artificial Intelligence. You will receive further information in a few minutes."
-
-/datum/objective/destroy/find_target(list/target_blacklist)
- var/list/possible_targets = active_ais(1)
- var/mob/living/silicon/ai/target_ai = pick(possible_targets)
- target = target_ai.mind
- update_explanation_text()
- return target
-
-/datum/objective/destroy/update_explanation_text()
- if(target?.current)
- explanation_text = "Destroy [target.current.real_name], the AI."
- else
- explanation_text = "Free Objective"
-
-/datum/objective/destroy/check_completion()
- if(target?.current)
- if(target.current.stat == DEAD || is_away_level(target.current.z) || !target.current.ckey)
- return TRUE
- return FALSE
- return TRUE
-
-/datum/objective/destroy/post_target_cryo(list/owners)
- holder.replace_objective(src, /datum/objective/assassinate)
-
-/datum/objective/steal_five_of_type
- name = "Steal Five Items"
- explanation_text = "Steal at least five items!"
- needs_target = FALSE
- var/list/wanted_items = list()
-
-/datum/objective/steal_five_of_type/New()
- ..()
- wanted_items = typecacheof(wanted_items)
-
-/datum/objective/steal_five_of_type/check_completion()
- var/stolen_count = 0
- var/list/owners = get_owners()
- var/list/all_items = list()
- for(var/datum/mind/M in owners)
- if(!isliving(M.current))
- continue
- all_items += M.current.GetAllContents() //this should get things in cheesewheels, books, etc.
- for(var/obj/I in all_items) //Check for wanted items
- if(is_type_in_typecache(I, wanted_items))
- stolen_count++
- return stolen_count >= 5
-
-/datum/objective/steal_five_of_type/summon_guns
- name = "Steal Five Guns"
- explanation_text = "Steal at least five guns!"
- wanted_items = list(/obj/item/gun)
-
-/datum/objective/steal_five_of_type/summon_magic
- name = "Steal Five Artefacts"
- explanation_text = "Steal at least five magical artefacts!"
- wanted_items = list()
-
-/datum/objective/steal_five_of_type/summon_magic/New()
- wanted_items = GLOB.summoned_magic_objectives
- ..()
-
-/datum/objective/steal_five_of_type/summon_magic/check_completion()
- var/stolen_count = 0
- var/list/owners = get_owners()
- var/list/all_items = list()
- for(var/datum/mind/M in owners)
- if(!isliving(M.current))
- continue
- all_items += M.current.GetAllContents() //this should get things in cheesewheels, books, etc.
- for(var/obj/I in all_items) //Check for wanted items
- if(istype(I, /obj/item/spellbook) && !istype(I, /obj/item/spellbook/oneuse))
- var/obj/item/spellbook/spellbook = I
- if(spellbook.uses) //if the book still has powers...
- stolen_count++ //it counts. nice.
- if(istype(I, /obj/item/spellbook/oneuse))
- var/obj/item/spellbook/oneuse/oneuse = I
- if(!oneuse.used)
- stolen_count++
- else if(is_type_in_typecache(I, wanted_items))
- stolen_count++
- return stolen_count >= 5
-
-/datum/objective/blood
- name = "Drink blood"
- needs_target = FALSE
-
-/datum/objective/blood/New()
- gen_amount_goal()
- . = ..()
-
-/datum/objective/blood/proc/gen_amount_goal(low = 150, high = 400)
- target_amount = rand(low,high)
- target_amount = round(round(target_amount/5)*5)
- update_explanation_text()
- return target_amount
-
-/datum/objective/blood/update_explanation_text()
- explanation_text = "Accumulate at least [target_amount] total units of blood."
-
-/datum/objective/blood/check_completion()
- for(var/datum/mind/M in get_owners())
- var/datum/antagonist/vampire/V = M.has_antag_datum(/datum/antagonist/vampire)
- if(V.bloodtotal >= target_amount)
- return TRUE
- else
- return FALSE
-
-#define SWARM_GOAL_LOWER_BOUND 130
-#define SWARM_GOAL_UPPER_BOUND 400
-
-/datum/objective/swarms
- name = "Gain swarms"
- needs_target = FALSE
-
-/datum/objective/swarms/New()
- gen_amount_goal()
- return ..()
-
-/datum/objective/swarms/proc/gen_amount_goal(low = SWARM_GOAL_LOWER_BOUND, high = SWARM_GOAL_UPPER_BOUND)
- target_amount = round(rand(low, high), 5)
- update_explanation_text()
- return target_amount
-
-/datum/objective/swarms/update_explanation_text()
- explanation_text = "Accumulate at least [target_amount] worth of swarms."
-
-/datum/objective/swarms/check_completion()
- for(var/datum/mind/M in get_owners())
- var/datum/antagonist/mindflayer/flayer = M.has_antag_datum(/datum/antagonist/mindflayer)
- return flayer?.total_swarms_gathered >= target_amount
-
-#undef SWARM_GOAL_LOWER_BOUND
-#undef SWARM_GOAL_UPPER_BOUND
-
-// Traders
-// These objectives have no check_completion, they exist only to tell Sol Traders what to aim for.
-
-/datum/objective/trade
- needs_target = FALSE
- completed = TRUE
-
-/datum/objective/trade/plasma
- explanation_text = "Acquire at least 15 sheets of plasma through trade."
-
-/datum/objective/trade/credits
- explanation_text = "Acquire at least 10,000 credits through trade."
-
-//wizard
-
-/datum/objective/wizchaos
- explanation_text = "Wreak havoc upon the station as much you can. Send those wandless Nanotrasen scum a message!"
- needs_target = FALSE
- completed = TRUE
-
-/datum/objective/zombie
- explanation_text = "Hunger grows within us, we need to feast on the brains of the uninfected. Scratch, bite, and spread the plague."
- needs_target = FALSE
- completed = TRUE
-
-// Placeholder objectives that will replace themselves
-
-/datum/objective/delayed
- needs_target = FALSE
- var/datum/objective/objective_to_replace_with
-
-/datum/objective/delayed/New(datum/objective/delayed_objective)
- ..()
- if(!ispath(delayed_objective))
- stack_trace("A delayed objective has been given a non-path. Given was instead [delayed_objective]")
- return
- objective_to_replace_with = delayed_objective
- explanation_text = initial(delayed_objective.delayed_objective_text)
-
-/datum/objective/delayed/update_explanation_text()
- return
-
-/datum/objective/delayed/proc/reveal_objective()
- return holder.replace_objective(src, new objective_to_replace_with(null, team, owner), target_department, steal_list)
-
-// A warning objective for that an agent is after you and knows you are an agent (or that you are paranoid)
-/datum/objective/potentially_backstabbed
- name = "Potentially Backstabbed"
- explanation_text = "Our intelligence suggests that you are likely to be the target of a rival member of the Syndicate. \
- Remain vigilant, they know who you are and what you can do."
- needs_target = FALSE
-
-/datum/objective/potentially_backstabbed/check_completion()
- for(var/datum/mind/M in get_owners())
- var/datum/antagonist/traitor/T = M.has_antag_datum(/datum/antagonist/traitor)
- for(var/datum/objective/our_objective in T.get_antag_objectives(FALSE))
- if(istype(our_objective, /datum/objective/potentially_backstabbed))
- continue
- if(!our_objective.check_completion())
- return FALSE
- return TRUE
diff --git a/code/game/gamemodes/objective_holder.dm b/code/game/gamemodes/objective_holder.dm
deleted file mode 100644
index b447f67713c20..0000000000000
--- a/code/game/gamemodes/objective_holder.dm
+++ /dev/null
@@ -1,132 +0,0 @@
-/**
- * An objective holder for minds, antag datums, and teams.
- */
-
-/datum/objective_holder
- /// Our list of current objectives
- VAR_PRIVATE/list/datum/objective/objectives = list()
- /// Who do we belong to [mind, antagonist, team]
- VAR_PRIVATE/datum/objective_owner
- /// A list of strings which contain [targets][/datum/objective/var/target] of the antagonist's objectives. Used to prevent duplicate objectives.
- VAR_PRIVATE/list/assigned_targets = list()
- /// A callback invoked when a new objective is added. This is required because sometimes objectives are added directly without going through objective_owner. Not currently used.
- VAR_PRIVATE/datum/callback/on_add_callback
- /// A callback invoked when a new objective is added. This is required because sometimes objectives are removed directly without going through objective_owner (EX: replace_objective(), clear()). Not currently used.
- VAR_PRIVATE/datum/callback/on_remove_callback
-
-/datum/objective_holder/New(new_owner)
- . = ..()
- objective_owner = new_owner
-
-/datum/objective_holder/Destroy(force, ...)
- clear()
- objective_owner = null
- QDEL_NULL(on_add_callback)
- QDEL_NULL(on_remove_callback)
- return ..()
-
-/**
- * Clear all objectives of a certain type
- * * checktype - The type to check, if null, remoe all objectives.
- */
-/datum/objective_holder/proc/clear(check_type)
- for(var/datum/objective/Objective as anything in objectives)
- if(check_type && !istype(Objective, check_type))
- return
- remove_objective(Objective)
- . = TRUE
-
-/**
- * Sets the callbacks, not on new because that can be irreliable for subtypes.
- */
-/datum/objective_holder/proc/set_callbacks(_on_add_callback, _on_remove_callback)
- on_add_callback = _on_add_callback
- on_remove_callback = _on_remove_callback
-
-/**
- * Do we have any objectives
- */
-/datum/objective_holder/proc/has_objectives()
- return length(objectives) > 0
-
-/**
- * Get all of the objectives we own
- */
-/datum/objective_holder/proc/get_objectives()
- return objectives
-
-/**
- * Replace old_objective with new_objective
- */
-/datum/objective_holder/proc/replace_objective(datum/objective/old_objective, datum/objective/new_objective, datum/original_target_department, list/original_steal_list)
- new_objective.target_department = original_target_department
- new_objective.steal_list = original_steal_list
- new_objective = add_objective(new_objective, add_to_list = FALSE)
- // Replace where the old objective was, with the new one
- objectives.Insert(objectives.Find(old_objective), new_objective)
- remove_objective(old_objective)
-
-/**
- * Add an objective.
- *
- * * Objective - The objective to add [/datum/objective, path]
- * * _explanation_text - Optional, will assign this text to the objective
- * * target_override - A target override, will prevent finding a target
- * * add_to_list - Do we add the new objective to our list? Or will it be handled elsewhere (like replace_objective). Should not be set to false outside of this file.
- */
-
-/datum/objective_holder/proc/add_objective(datum/objective/Objective, _explanation_text, mob/target_override, add_to_list = TRUE)
- if(ispath(Objective))
- Objective = new Objective()
-
- Objective.holder = src
-
- if(add_to_list)
- objectives += Objective
-
- if(target_override)
- Objective.target = target_override
- else if(Objective.needs_target && !Objective.found_target())
- handle_objective(Objective)
-
- var/found = Objective.found_target() // in case we are given a target override
- if(found)
- assigned_targets |= found
-
- if(_explanation_text)
- Objective.explanation_text = _explanation_text
-
- on_add_callback?.Invoke(objective_owner, Objective)
- return Objective
-
-/**
- * Handles the searching of targets for objectives that need it.
- */
-
-/datum/objective_holder/proc/handle_objective(datum/objective/Objective)
- for(var/loop in 1 to 5)
- Objective.find_target(assigned_targets)
- if(Objective.found_target()) // Handles normal objectives, and steal objectives
- return
-
- // We failed to find any target. Oh well...
- Objective.explanation_text = "Free Objective"
- Objective.target = null
-
-/**
- * Remove an objective and deletes it. You should never need to transfer an objective.
- */
-/datum/objective_holder/proc/remove_objective(datum/objective/Objective)
- objectives -= Objective
- assigned_targets -= Objective.found_target()
-
- on_remove_callback?.Invoke(objective_owner, Objective)
- if(!QDELETED(Objective))
- qdel(Objective)
-
-/**
- * Returns `objective_owner`
- */
-/datum/objective_holder/proc/get_holder_owner()
- RETURN_TYPE(/datum)
- return objective_owner
diff --git a/code/game/gamemodes/revolution/revolution.dm b/code/game/gamemodes/revolution/revolution.dm
deleted file mode 100644
index 46ed0c4f3fea8..0000000000000
--- a/code/game/gamemodes/revolution/revolution.dm
+++ /dev/null
@@ -1,266 +0,0 @@
-// 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
-
-#define REV_VICTORY 1
-#define STATION_VICTORY 2
-
-/datum/game_mode/revolution
- name = "revolution"
- config_tag = "revolution"
- restricted_jobs = list("Security Officer", "Warden", "Detective", "Internal Affairs Agent", "AI", "Cyborg","Captain", "Head of Personnel", "Head of Security", "Chief Engineer", "Research Director", "Chief Medical Officer", "Blueshield", "Nanotrasen Representative", "Magistrate", "Quartermaster", "Nanotrasen Career Trainer")
- required_players = 20
- required_enemies = 1
- recommended_enemies = 3
-
- var/finished
- var/check_counter = 0
- var/list/pre_revolutionaries = list()
-
-///////////////////////////
-//Announces the game type//
-///////////////////////////
-/datum/game_mode/revolution/announce()
- to_chat(world, "The current game mode is - Revolution!")
- to_chat(world, "Some crewmembers are attempting to start a revolution! \nRevolutionaries - Kill the Captain, HoP, HoS, CE, QM, RD and CMO. Convert other crewmembers (excluding the heads of staff, and security officers) to your cause by flashing them. Protect your leaders. \nPersonnel - Protect the heads of staff. Kill the leaders of the revolution, and brainwash the other revolutionaries (by beating them in the head).")
-
-
-///////////////////////////////////////////////////////////////////////////////
-//Gets the round setup, cancelling if there's not enough players at the start//
-///////////////////////////////////////////////////////////////////////////////
-/datum/game_mode/revolution/pre_setup()
- var/list/datum/mind/possible_revolutionaries = get_players_for_role(ROLE_REV)
-
- if(GLOB.configuration.gamemode.prevent_mindshield_antags)
- restricted_jobs |= protected_jobs
-
- for(var/i in 1 to REVOLUTION_MAX_HEADREVS)
- if(!length(possible_revolutionaries))
- break
- var/datum/mind/new_headrev = pick_n_take(possible_revolutionaries)
- pre_revolutionaries |= new_headrev
- new_headrev.restricted_roles = restricted_jobs
- new_headrev.special_role = SPECIAL_ROLE_HEAD_REV
-
- if(length(pre_revolutionaries) < required_enemies)
- return FALSE
-
- return TRUE
-
-
-/datum/game_mode/revolution/post_setup()
-
- 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)
-
- ..()
-
-/datum/game_mode/revolution/Destroy(force, ...)
- pre_revolutionaries.Cut()
- return ..()
-
-/datum/game_mode/revolution/process() // to anyone who thinks "why don't we use a normal actually sane check here instead of process for checking Z level changes" It's because equip code is dogshit and you change z levels upon spawning in
- check_counter++
- if(check_counter >= 5)
- if(!finished)
- rev_team.check_all_victory()
- check_counter = 0
- return FALSE
-
-/datum/game_mode/proc/get_rev_team()
- if(!rev_team)
- new /datum/team/revolution() // assignment happens in create_team()
- return rev_team
-
-//////////////////////////////////////
-//Checks if the revs have won or not//
-//////////////////////////////////////
-/datum/game_mode/revolution/check_win()
- if(rev_team.check_rev_victory())
- finished = REV_VICTORY
- else if(rev_team.check_heads_victory())
- finished = STATION_VICTORY
-
-///////////////////////////////
-//Checks if the round is over//
-///////////////////////////////
-/datum/game_mode/revolution/check_finished()
- if(GLOB.configuration.gamemode.disable_certain_round_early_end)
- if(finished)
- SSshuttle.clearHostileEnvironment(rev_team)
- if(SSshuttle.emergency.mode == SHUTTLE_STRANDED)
- SSshuttle.emergency.mode = SHUTTLE_DOCKED
- SSshuttle.emergency.timer = world.time
- GLOB.major_announcement.Announce("Hostile environment resolved. You have 3 minutes to board the Emergency Shuttle.", null, 'sound/AI/eshuttle_dock.ogg')
- return ..()
- if(finished)
- return TRUE
- return ..()
-
-//////////////////////////////////////////////////////////////////////////////
-//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.
-//////////////////////////////////////////////////////////////////////////////
-/datum/game_mode/proc/remove_revolutionary(datum/mind/rev_mind, beingborged, activate_protection)
- var/mob/revolutionary = rev_mind.current
- 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)
- // 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(
- "The frame beeps contentedly, purging the hostile memory engram from the MMI before initalizing it.",
- "The frame's firmware detects and deletes your neural reprogramming! You remember nothing[remove_head ? "." : " but the name of the one who flashed you."]")
- message_admins("[key_name_admin(rev_mind.current)] [ADMIN_QUE(rev_mind.current,"?")] ([ADMIN_FLW(rev_mind.current,"FLW")]) has been borged while being a [remove_head ? "leader" : " member"] of the revolution.")
- else
- var/class = activate_protection ? "biggerdanger" : "userdanger" // biggerdanger only shows up when protection happens (usually in a red-flood of combat text)
- revolutionary.visible_message(
- "[rev_mind.current] looks like [rev_mind.current.p_they()] just remembered [rev_mind.current.p_their()] real allegiance!",
- "You have been brainwashed! You are no longer a revolutionary! Your memory is hazy from the time you were a rebel... the only thing you remember is the name of the one who brainwashed you...")
- rev_mind.current.Paralyse(10 SECONDS)
- if(activate_protection && isliving(revolutionary))
- var/mob/living/living_rev = revolutionary
- living_rev.apply_status_effect(STATUS_EFFECT_REVOLUTION_PROTECT)
- return TRUE
-
-//////////////////////////////////////////////////////////////////////
-//Announces the end of the game with all relavent information stated//
-//////////////////////////////////////////////////////////////////////
-/datum/game_mode/revolution/declare_completion()
- if(finished == REV_VICTORY)
- SSticker.mode_result = "revolution win - heads killed"
- to_chat(world, "The heads of staff were killed or exiled! The revolutionaries win!")
- else if(finished == STATION_VICTORY)
- SSticker.mode_result = "revolution loss - rev heads killed"
- to_chat(world, "The heads of staff managed to stop the revolution!")
- ..()
- return TRUE
-
-/datum/game_mode/proc/auto_declare_completion_revolution()
- var/list/targets = list()
- if(length(rev_team?.members) || GAMEMODE_IS_REVOLUTION)
- var/num_revs = 0
- var/num_survivors = 0
- for(var/mob/living/carbon/human/survivor in GLOB.player_list)
- if(!istype(survivor) || survivor.stat == DEAD)
- continue
- num_survivors++
- if(survivor.mind?.has_antag_datum(/datum/antagonist/rev))
- num_revs++
- if(num_survivors)
- to_chat(world, "[TAB]Command's Approval Rating: [100 - round((num_revs/num_survivors)*100, 0.1)]%") // % of loyal crew
- var/list/text = list(" The head revolutionaries were:")
- for(var/datum/mind/headrev in head_revolutionaries)
- text += printplayer(headrev, 1)
- text += " "
-
- // we dont show the revolutionaries because there are a LOT of them
-
- text = list(" The heads of staff were:")
- var/list/heads = get_all_heads()
- for(var/datum/mind/head in heads)
- var/target = (head in targets)
- if(target)
- text += "Target"
- text += printplayer(head, 1)
- text += " "
- return text.Join("")
-
-/datum/game_mode/revolution/set_scoreboard_vars() // this proc is never called, someone remove it
- var/datum/scoreboard/scoreboard = SSticker.score
- var/foecount = 0
-
- for(var/datum/mind/M in SSticker.mode.head_revolutionaries)
- foecount++
- if(!M || !M.current)
- scoreboard.score_ops_killed++
- continue
-
- if(M.current.stat == DEAD)
- scoreboard.score_ops_killed++
-
- else if(M.current.restrained())
- scoreboard.score_arrested++
-
- if(foecount == scoreboard.score_arrested)
- scoreboard.all_arrested = TRUE
-
- for(var/datum/mind/head_mind in get_all_heads())
- var/mob/living/carbon/human/H = head_mind.current
- if(isnull(H) || H.stat == DEAD)
- scoreboard.score_dead_command++
-
-
- var/arrestpoints = scoreboard.score_arrested * 1000
- var/killpoints = scoreboard.score_ops_killed * 500
- var/comdeadpts = scoreboard.score_dead_command * 500
- if(scoreboard.score_greentext)
- scoreboard.crewscore -= 10000
-
- scoreboard.crewscore += arrestpoints
- scoreboard.crewscore += killpoints
- scoreboard.crewscore -= comdeadpts
-
-
-/datum/game_mode/revolution/get_scoreboard_stats()
- var/datum/scoreboard/scoreboard = SSticker.score
- var/foecount = 0
- var/comcount = 0
- var/revcount = 0
- var/loycount = 0
- for(var/datum/mind/M in SSticker.mode.head_revolutionaries)
- if(M.current && M.current.stat != DEAD)
- foecount++
- for(var/datum/mind/M in SSticker.mode.revolutionaries)
- if(M.current && M.current.stat != DEAD)
- revcount++
-
- var/list/real_command_positions = GLOB.command_positions.Copy() - "Nanotrasen Representative"
- for(var/mob/living/carbon/human/player in GLOB.human_list)
- if(player.mind)
- if(player.mind.assigned_role in real_command_positions)
- if(player.stat != DEAD)
- comcount++
- continue
- if(player.mind.has_antag_datum(/datum/antagonist/rev))
- continue
- loycount++
-
- // we dont count silicons because well, they follow their laws, not the crew, and there is no easy way to tell if theyre subverted
-
-
- var/dat = ""
-
- dat += "Mode Statistics "
- dat += "Number of Surviving Revolution Heads: [foecount] "
- dat += "Number of Surviving Command Staff: [comcount] "
- dat += "Number of Surviving Revolutionaries: [revcount] "
- dat += "Number of Surviving Loyal Crew: [loycount] "
-
- dat += " "
- dat += "Revolution Heads Arrested: [scoreboard.score_arrested] ([scoreboard.score_arrested * 1000] Points) "
- dat += "All Revolution Heads Arrested: [scoreboard.all_arrested ? "Yes" : "No"] (Score tripled) "
-
- dat += "Revolution Heads Slain: [scoreboard.score_ops_killed] ([scoreboard.score_ops_killed * 500] Points) "
- dat += "Command Staff Slain: [scoreboard.score_dead_command] (-[scoreboard.score_dead_command * 500] Points) "
- dat += "Revolution Successful: [scoreboard.score_greentext ? "Yes" : "No"] (-[scoreboard.score_greentext * 10000] Points) "
- dat += " "
-
- return dat
-
-/proc/is_revolutionary(mob/living/M)
- return istype(M) && M?.mind?.has_antag_datum(/datum/antagonist/rev, FALSE)
-
-/proc/is_headrev(mob/living/M)
- return istype(M) && M?.mind?.has_antag_datum(/datum/antagonist/rev/head)
-
-/proc/is_any_revolutionary(mob/living/M)
- return istype(M) && M?.mind?.has_antag_datum(/datum/antagonist/rev)
-
-#undef REV_VICTORY
-#undef STATION_VICTORY
diff --git a/code/game/gamemodes/steal_items.dm b/code/game/gamemodes/steal_items.dm
deleted file mode 100644
index 5da86e51a2633..0000000000000
--- a/code/game/gamemodes/steal_items.dm
+++ /dev/null
@@ -1,209 +0,0 @@
-// Theft objectives.
-//
-// Separated into datums so we can prevent roles from getting certain objectives.
-
-/datum/theft_objective
- var/name = "this objective is impossible, yell at a coder"
- var/typepath=/obj/effect/debugging
- var/list/protected_jobs = list()
- var/list/altitems = list()
- var/flags = 0
- var/location_override
- /// Do we have a special item we give to somewhen when they get this objective?
- var/special_equipment = null
- /// If a steal objective has forbidden jobs, and the forbidden jobs would not be in the possession of this item, set this to false
- var/job_possession = TRUE
- /// Any extra information about the objective
- var/extra_information = ""
-
-/datum/theft_objective/proc/check_completion(datum/mind/owner)
- if(!owner.current)
- return 0
- if(!isliving(owner.current))
- return 0
- var/list/all_items = owner.current.get_contents()
- for(var/obj/I in all_items) //Check for items
- if(istype(I, typepath) && check_special_completion(I))
- return 1
- return 0
-
-//This proc is to be used for not granting objectives if a special requirement other than job is not met.
-
-/datum/theft_objective/proc/check_objective_conditions()
- return TRUE
-
-/datum/proc/check_special_completion() //for objectives with special checks (is that slime extract unused? does that intellicard have an ai in it? etcetc)
- return 1
-
-/datum/theft_objective/antique_laser_gun
- name = "the captain's antique laser gun"
- typepath = /obj/item/gun/energy/laser/captain
- protected_jobs = list("Captain")
- location_override = "the Captain's Office"
-
-/datum/theft_objective/captains_modsuit
- name = "the captain's Magnate MODsuit"
- typepath = /obj/item/mod/control/pre_equipped/magnate
- protected_jobs = list("Captain")
- location_override = "the Captain's Office"
-
-/datum/theft_objective/captains_saber
- name = "the captain's saber"
- typepath = /obj/item/melee/saber
- protected_jobs = list("Captain")
- location_override = "the Captain's Office"
-
-/datum/theft_objective/hoslaser
- name = "the head of security's X-01 multiphase energy gun"
- typepath = /obj/item/gun/energy/gun/hos
- protected_jobs = list("Head Of Security")
- location_override = "the Head of Security's Office"
-
-/datum/theft_objective/hand_tele
- name = "a hand teleporter"
- typepath = /obj/item/hand_tele
- protected_jobs = list("Captain", "Research Director", "Chief Engineer")
- location_override = "the AI Satellite, or the Captain's Office"
-
-/datum/theft_objective/defib
- name = "the chief medical officer's advanced compact defibrillator"
- typepath = /obj/item/defibrillator/compact/advanced
- protected_jobs = list("Chief Medical Officer", "Paramedic")
- location_override = "the Chief Medical Officer's Office"
-
-/datum/theft_objective/magboots
- name = "the chief engineer's advanced magnetic boots"
- typepath = /obj/item/clothing/shoes/magboots/advance
- protected_jobs = list("Chief Engineer")
- location_override = "the Chief Engineer's Office"
-
-/datum/theft_objective/blueprints
- name = "the station blueprints"
- typepath = /obj/item/areaeditor/blueprints/ce
- protected_jobs = list("Chief Engineer")
- altitems = list(/obj/item/photo)
- location_override = "the Chief Engineer's Office"
- extra_information = "Obtaining a photograph of the blueprints is also an option."
-
-/datum/theft_objective/blueprints/check_special_completion(obj/item/I)
- if(istype(I, /obj/item/areaeditor/blueprints/ce))
- return 1
- if(istype(I, /obj/item/photo))
- var/obj/item/photo/P = I
- if(P.blueprints)
- return 1
- return 0
-
-/datum/theft_objective/capmedal
- name = "the medal of captaincy"
- typepath = /obj/item/clothing/accessory/medal/gold/captain
- protected_jobs = list("Captain")
- location_override = "the Captain's Office"
-
-/datum/theft_objective/nukedisc
- name = "the nuclear authentication disk"
- typepath = /obj/item/disk/nuclear
- protected_jobs = list("Captain")
- location_override = "the Captain's Office"
-
-/datum/theft_objective/nukedisc/check_special_completion(obj/item/I)
- if(istype(I, /obj/item/disk/nuclear/training)) //Haha no
- return FALSE
- return TRUE
-
-/datum/theft_objective/reactive
- name = "any type of reactive armor"
- typepath = /obj/item/clothing/suit/armor/reactive
- protected_jobs = list("Research Director", "Scientist", "Roboticist") //no one with protolathe access, who will often be handed a core
- location_override = "the Research Director's Office"
-
-/datum/theft_objective/documents
- name = "any set of secret documents of any organization"
- typepath = /obj/item/documents //Any set of secret documents. Doesn't have to be NT's
- location_override = "the Vault"
- protected_jobs = list("Quartermaster")
- job_possession = FALSE
-
-/datum/theft_objective/hypospray
- name = "the chief medical officer's advanced hypospray"
- typepath = /obj/item/reagent_containers/hypospray/cmo
- protected_jobs = list("Chief Medical Officer")
- location_override = "the Chief Medical Officer's Office"
-
-/datum/theft_objective/krav
- name = "the warden's krav maga martial arts gloves"
- typepath = /obj/item/clothing/gloves/color/black/krav_maga/sec
- protected_jobs = list("Head Of Security", "Warden")
- location_override = "the Warden's Office"
-
-/datum/theft_objective/supermatter_sliver
- name = "a supermatter sliver"
- typepath = /obj/item/nuke_core/supermatter_sliver
- protected_jobs = list("Chief Engineer", "Station Engineer", "Life Support Specialist") //Unlike other steal objectives, all jobs in the department have easy access, and would not be noticed at all stealing this
- location_override = "Engineering. You can use the box and instructions provided to harvest the sliver"
- special_equipment = /obj/item/storage/box/syndie_kit/supermatter
- job_possession = FALSE //The CE / engineers / atmos techs do not carry around supermater slivers.
-
-/datum/theft_objective/supermatter_sliver/check_objective_conditions() //If there is no supermatter, you don't get the objective. Yes, one could order it from cargo, but I don't think that is fair, especially if we get a map without a supermatter
- return !isnull(GLOB.main_supermatter_engine)
-
-/datum/theft_objective/plutonium_core
- name = "the plutonium core from the station's nuclear device"
- typepath = /obj/item/nuke_core/plutonium
- location_override = "the Vault. You can use the box and instructions provided to remove the core, with some extra tools"
- special_equipment = /obj/item/storage/box/syndie_kit/nuke
- protected_jobs = list("Quartermaster")
- job_possession = FALSE
-
-/datum/theft_objective/engraved_dusters
- name = "the quartermaster's engraved knuckledusters"
- typepath = /obj/item/melee/knuckleduster/nanotrasen
- protected_jobs = list("Quartermaster")
- location_override = "the Quartermaster's Cargo Office"
-
-/datum/theft_objective/number
- var/min=0
- var/max=0
- var/step=1
-
- var/required_amount=0
-
-/datum/theft_objective/number/New()
- if(min==max)
- required_amount=min
- else
- var/lower=min/step
- var/upper=min/step
- required_amount=rand(lower,upper)*step
- name = "[required_amount] [name]"
-
-/datum/theft_objective/number/check_completion(datum/mind/owner)
- if(!owner.current)
- return 0
- if(!isliving(owner.current))
- return 0
- var/list/all_items = owner.current.get_contents()
- var/found_amount=0.0
- for(var/obj/item/I in all_items)
- if(istype(I, typepath))
- found_amount += getAmountStolen(I)
- return found_amount >= required_amount
-
-/datum/theft_objective/number/proc/getAmountStolen(obj/item/I)
- return I:amount
-
-/datum/theft_objective/unique
- flags = THEFT_FLAG_UNIQUE
-
-/datum/theft_objective/unique/docs_red
- name = "the \"Red\" secret documents"
- typepath = /obj/item/documents/syndicate/red
- location_override = "a Syndicate agent's possession"
-
-/datum/theft_objective/unique/docs_blue
- name = "the \"Blue\" secret documents"
- typepath = /obj/item/documents/syndicate/blue
- location_override = "a Syndicate agent's possession"
-
-#undef THEFT_FLAG_SPECIAL
-#undef THEFT_FLAG_UNIQUE
diff --git a/code/game/gamemodes/traitor/traitor.dm b/code/game/gamemodes/traitor/traitor.dm
deleted file mode 100644
index 6ed1248aa9b40..0000000000000
--- a/code/game/gamemodes/traitor/traitor.dm
+++ /dev/null
@@ -1,172 +0,0 @@
-/datum/game_mode/traitor
- name = "traitor"
- config_tag = "traitor"
- restricted_jobs = list("Cyborg")//They are part of the AI if he is traitor so are they, they use to get double chances
- protected_jobs = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Blueshield", "Nanotrasen Representative", "Magistrate", "Internal Affairs Agent", "Nanotrasen Navy Officer", "Special Operations Officer", "Syndicate Officer", "Trans-Solar Federation General", "Nanotrasen Career Trainer")
- required_players = 0
- required_enemies = 1
- recommended_enemies = 4
- /// Hard limit on traitors if scaling is turned off.
- var/traitors_possible = 4
- /// How much the amount of players get divided by to determine the number of traitors.
- var/const/traitor_scaling_coeff = 5
-
-/datum/game_mode/traitor/announce()
- to_chat(world, "The current game mode is - Traitor!")
- to_chat(world, "There is a syndicate traitor on the station. Do not let the traitor succeed!")
-
-/datum/game_mode/traitor/pre_setup()
-
- if(GLOB.configuration.gamemode.prevent_mindshield_antags)
- restricted_jobs += protected_jobs
-
- var/list/possible_traitors = get_players_for_role(ROLE_TRAITOR)
-
- for(var/datum/mind/candidate in possible_traitors)
- if(candidate.special_role == SPECIAL_ROLE_VAMPIRE || candidate.special_role == SPECIAL_ROLE_CHANGELING || candidate.special_role == SPECIAL_ROLE_MIND_FLAYER) // no traitor vampires, changelings, or mindflayers
- possible_traitors.Remove(candidate)
-
- // stop setup if no possible traitors
- if(!length(possible_traitors))
- return FALSE
-
- var/num_traitors = 1
-
- num_traitors = traitors_to_add()
-
- for(var/i in 1 to num_traitors)
- if(!length(possible_traitors))
- break
- var/datum/mind/traitor = pick_n_take(possible_traitors)
- pre_traitors += traitor
- traitor.special_role = SPECIAL_ROLE_TRAITOR
- traitor.restricted_roles = restricted_jobs
-
- if(!length(pre_traitors))
- return FALSE
- return TRUE
-
-/datum/game_mode/traitor/post_setup()
- . = ..()
-
- var/random_time = rand(5 MINUTES, 15 MINUTES)
- if(length(pre_traitors))
- addtimer(CALLBACK(src, PROC_REF(fill_antag_slots)), random_time)
-
- for(var/datum/mind/traitor in pre_traitors)
- var/datum/antagonist/traitor/traitor_datum = new(src)
- if(ishuman(traitor.current))
- traitor_datum.delayed_objectives = TRUE
- traitor_datum.addtimer(CALLBACK(traitor_datum, TYPE_PROC_REF(/datum/antagonist/traitor, reveal_delayed_objectives)), random_time, TIMER_DELETE_ME)
-
- traitor.add_antag_datum(traitor_datum)
-
-/datum/game_mode/traitor/traitors_to_add()
- if(GLOB.configuration.gamemode.traitor_scaling)
- . = max(1, round(num_players() / traitor_scaling_coeff))
- else
- . = max(1, min(num_players(), traitors_possible))
-
- if(!length(traitors))
- return
-
- for(var/datum/mind/traitor_mind as anything in traitors)
- if(!QDELETED(traitor_mind) && traitor_mind.current) // Explicitly no client check in case you happen to fall SSD when this gets ran
- continue
- .++
- traitors -= traitor_mind
-
-/datum/game_mode/traitor/declare_completion()
- ..()
- return//Traitors will be checked as part of check_extra_completion. Leaving this here as a reminder.
-
-/datum/game_mode/proc/auto_declare_completion_traitor()
- if(length(traitors))
- var/list/text = list("The traitors were: ")
- for(var/datum/mind/traitor in traitors)
- var/traitorwin = TRUE
- text += printplayer(traitor)
-
- var/TC_uses = 0
- var/used_uplink = FALSE
- var/purchases = ""
- for(var/obj/item/uplink/H in GLOB.world_uplinks)
- if(H && H.uplink_owner && H.uplink_owner == traitor.key)
- TC_uses += H.used_TC
- used_uplink = TRUE
- purchases += H.purchase_log
-
- if(used_uplink)
- text += " (used [TC_uses] TC) [purchases]"
-
- var/all_objectives = traitor.get_all_objectives(include_team = FALSE)
-
- if(length(all_objectives))//If the traitor had no objectives, don't need to process this.
- var/count = 1
- for(var/datum/objective/objective in all_objectives)
- if(objective.check_completion())
- text += " Objective #[count]: [objective.explanation_text] Success!"
- if(istype(objective, /datum/objective/steal))
- var/datum/objective/steal/S = objective
- SSblackbox.record_feedback("nested tally", "traitor_steal_objective", 1, list("Steal [S.steal_target]", "SUCCESS"))
- else
- SSblackbox.record_feedback("nested tally", "traitor_objective", 1, list("[objective.type]", "SUCCESS"))
- else
- text += " Objective #[count]: [objective.explanation_text] Fail."
- if(istype(objective, /datum/objective/steal))
- var/datum/objective/steal/S = objective
- SSblackbox.record_feedback("nested tally", "traitor_steal_objective", 1, list("Steal [S.steal_target]", "FAIL"))
- else
- SSblackbox.record_feedback("nested tally", "traitor_objective", 1, list("[objective.type]", "FAIL"))
- traitorwin = FALSE
- count++
-
- var/special_role_text
- if(traitor.special_role)
- special_role_text = lowertext(traitor.special_role)
- else
- special_role_text = "antagonist"
-
- var/datum/contractor_hub/H = LAZYACCESS(GLOB.contractors, traitor)
- if(H)
- var/count = 1
- var/earned_tc = H.reward_tc_paid_out
- for(var/c in H.contracts)
- var/datum/syndicate_contract/C = c
- // Locations
- var/locations = list()
- for(var/a in C.contract.candidate_zones)
- var/area/A = a
- locations += (A == C.contract.extraction_zone ? "[A.map_name]" : A.map_name)
- var/display_locations = english_list(locations, and_text = " or ")
- // Result
- var/result = ""
- if(C.status == CONTRACT_STATUS_COMPLETED)
- result = "Success!"
- else if(C.status != CONTRACT_STATUS_INACTIVE)
- result = "Fail."
- text += " Contract #[count]: Kidnap and extract [C.target_name] at [display_locations]. [result]"
- count++
- text += " [earned_tc] TC were earned from the contracts."
-
- if(traitorwin)
- text += " The [special_role_text] was successful! "
- SSblackbox.record_feedback("tally", "traitor_success", 1, "SUCCESS")
- else
- text += " The [special_role_text] has failed! "
- SSblackbox.record_feedback("tally", "traitor_success", 1, "FAIL")
-
- if(length(SSticker.mode.implanted))
- text += "
The mindslaves were: "
- for(var/datum/mind/mindslave in SSticker.mode.implanted)
- text += printplayer(mindslave)
- var/datum/mind/master_mind = SSticker.mode.implanted[mindslave]
- text += " (slaved by: [master_mind.current]) "
-
- var/phrases = jointext(GLOB.syndicate_code_phrase, ", ")
- var/responses = jointext(GLOB.syndicate_code_response, ", ")
-
- text += "
The code phrases were: [phrases] \
- The code responses were: [responses]
"
-
- return text.Join("")
diff --git a/code/game/gamemodes/trifecta/trifecta.dm b/code/game/gamemodes/trifecta/trifecta.dm
deleted file mode 100644
index 783de206c6bdb..0000000000000
--- a/code/game/gamemodes/trifecta/trifecta.dm
+++ /dev/null
@@ -1,150 +0,0 @@
-#define TOT_COST 5
-#define VAMP_COST 10
-#define CLING_COST 10
-
-
-/datum/game_mode/trifecta
- name = "Trifecta"
- config_tag = "trifecta"
- protected_jobs = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Blueshield", "Nanotrasen Representative", "Magistrate", "Internal Affairs Agent", "Nanotrasen Career Trainer", "Nanotrasen Navy Officer", "Special Operations Officer", "Trans-Solar Federation General", "Nanotrasen Career Trainer")
- restricted_jobs = list("Cyborg")
- secondary_restricted_jobs = list("AI")
- required_players = 25
- required_enemies = 1 // how many of each type are required
- recommended_enemies = 3
- species_to_mindflayer = list("Machine")
- var/vampire_restricted_jobs = list("Chaplain")
- var/amount_vamp = 1
- var/amount_cling = 1
- var/amount_tot = 1
- /// How many points did we get at roundstart
- var/cost_at_roundstart
-
-/datum/game_mode/trifecta/announce()
- to_chat(world, "The current game mode is - Trifecta")
- to_chat(world, "Vampires, traitors, and changelings, oh my! Stay safe as these forces work to bring down the station.")
-
-/datum/game_mode/trifecta/pre_setup()
- calculate_quantities()
- cost_at_roundstart = num_players()
- if(GLOB.configuration.gamemode.prevent_mindshield_antags)
- restricted_jobs += protected_jobs
- var/list/datum/mind/possible_vampires = get_players_for_role(ROLE_VAMPIRE)
-
- if(!length(possible_vampires))
- return FALSE
-
- for(var/datum/mind/vampire as anything in shuffle(possible_vampires))
- if(length(pre_vampires) >= amount_vamp)
- break
- vampire.restricted_roles = restricted_jobs + secondary_restricted_jobs + vampire_restricted_jobs
- if(vampire.current.client.prefs.active_character.species in species_to_mindflayer)
- pre_mindflayers += vampire
- amount_vamp -= 1 //It's basically the same thing as incrementing pre_vampires
- vampire.special_role = SPECIAL_ROLE_MIND_FLAYER
- continue
- pre_vampires += vampire
- vampire.special_role = SPECIAL_ROLE_VAMPIRE
-
- //Vampires made, off to changelings
- var/list/datum/mind/possible_changelings = get_players_for_role(ROLE_CHANGELING)
-
- if(!length(possible_changelings))
- return FALSE
-
- for(var/datum/mind/changeling as anything in shuffle(possible_changelings))
- if(length(pre_changelings) >= amount_cling)
- break
- if(changeling.special_role == SPECIAL_ROLE_VAMPIRE || changeling.special_role == SPECIAL_ROLE_MIND_FLAYER)
- continue
- changeling.restricted_roles = (restricted_jobs + secondary_restricted_jobs)
- if(changeling.current?.client?.prefs.active_character.species in species_to_mindflayer)
- pre_mindflayers += changeling
- amount_cling -= 1
- changeling.special_role = SPECIAL_ROLE_MIND_FLAYER
- continue
- pre_changelings += changeling
- changeling.special_role = SPECIAL_ROLE_CHANGELING
-
- //And now traitors
- var/list/datum/mind/possible_traitors = get_players_for_role(ROLE_TRAITOR)
-
- //stop setup if no possible traitors
- if(!length(possible_traitors))
- return FALSE
-
- for(var/datum/mind/traitor as anything in shuffle(possible_traitors))
- if(length(pre_traitors) >= amount_tot)
- break
- if(traitor.special_role == SPECIAL_ROLE_VAMPIRE || traitor.special_role == SPECIAL_ROLE_CHANGELING || traitor.special_role == SPECIAL_ROLE_MIND_FLAYER) // no traitor vampires or changelings
- continue
- pre_traitors += traitor
- traitor.special_role = SPECIAL_ROLE_TRAITOR
- traitor.restricted_roles = restricted_jobs
-
- return TRUE
-
-/datum/game_mode/trifecta/proc/calculate_quantities()
- var/points = num_players()
- // So. to ensure that we had at least one vamp / changeling / traitor, I set the number of ammount to 1. I never subtracted points, leading to 25 players worth of antags added for free. Whoops.
- points -= TOT_COST + VAMP_COST + CLING_COST
- while(points > 0)
- if(points < TOT_COST)
- amount_tot++
- points = 0
- return
-
- switch(rand(1, 4))
- if(1 to 2)
- amount_tot++
- points -= TOT_COST
- if(3)
- amount_vamp++
- points -= VAMP_COST
- if(4)
- amount_cling++
- points -= CLING_COST
-
-/datum/game_mode/trifecta/post_setup()
- for(var/datum/mind/vampire as anything in pre_vampires)
- vampire.add_antag_datum(/datum/antagonist/vampire)
-
- for(var/datum/mind/changeling as anything in pre_changelings)
- changeling.add_antag_datum(/datum/antagonist/changeling)
-
- var/random_time
- if(length(pre_traitors))
- random_time = rand(5 MINUTES, 15 MINUTES)
- addtimer(CALLBACK(src, PROC_REF(fill_antag_slots)), random_time)
-
- for(var/datum/mind/traitor in pre_traitors)
- var/datum/antagonist/traitor/traitor_datum = new(src)
- if(ishuman(traitor.current))
- traitor_datum.delayed_objectives = TRUE
- traitor_datum.addtimer(CALLBACK(traitor_datum, TYPE_PROC_REF(/datum/antagonist/traitor, reveal_delayed_objectives)), random_time, TIMER_DELETE_ME)
-
- traitor.add_antag_datum(traitor_datum)
-
- ..()
-
-/datum/game_mode/trifecta/traitors_to_add()
- . = 0
- for(var/datum/mind/traitor_mind as anything in traitors)
- if(!QDELETED(traitor_mind) && traitor_mind.current) // Explicitly no client check in case you happen to fall SSD when this gets ran
- continue
- .++
- traitors -= traitor_mind
-
- var/extra_points = num_players_started() - cost_at_roundstart
- if(extra_points - TOT_COST < 0)
- return 0 // Not enough new players to add extra tots
-
- while(extra_points)
- .++
- if(extra_points < TOT_COST) // The leftover change is enough for us to buy another traitor with, what a deal!
- return
- extra_points -= TOT_COST
-
-#undef TOT_COST
-#undef VAMP_COST
-#undef CLING_COST
diff --git a/code/game/gamemodes/vampire/vampire_chan.dm b/code/game/gamemodes/vampire/vampire_chan.dm
deleted file mode 100644
index f0cd8300ccd68..0000000000000
--- a/code/game/gamemodes/vampire/vampire_chan.dm
+++ /dev/null
@@ -1,45 +0,0 @@
-/datum/game_mode/vampire/changeling
- name = "vampire_changeling"
- config_tag = "vampchan"
- restricted_jobs = list("AI", "Cyborg")
- protected_jobs = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Blueshield", "Nanotrasen Representative", "Magistrate", "Chaplain", "Internal Affairs Agent", "Nanotrasen Career Trainer", "Nanotrasen Navy Officer", "Special Operations Officer", "Syndicate Officer", "Solar Federation General")
- species_to_mindflayer = list("Machine")
- required_players = 15
- required_enemies = 1
- recommended_enemies = 3
- secondary_enemies_scaling = 0.025
- vampire_penalty = 0.4 // Cut out 40% of the vampires since we'll replace some with changelings
-
-/datum/game_mode/vampire/changeling/pre_setup()
- if(GLOB.configuration.gamemode.prevent_mindshield_antags)
- restricted_jobs += protected_jobs
-
- var/list/datum/mind/possible_changelings = get_players_for_role(ROLE_CHANGELING)
- secondary_enemies = CEILING((secondary_enemies_scaling * num_players()), 1)
-
- if(!length(possible_changelings))
- return ..()
-
- for(var/I in possible_changelings)
- if(length(pre_changelings) >= secondary_enemies)
- break
- var/datum/mind/changeling = pick_n_take(possible_changelings)
- changeling.restricted_roles = (restricted_jobs + secondary_restricted_jobs)
- if(changeling.current?.client?.prefs.active_character.species in species_to_mindflayer)
- pre_mindflayers += changeling
- secondary_enemies -= 1 // Again, since we aren't increasing pre_changeling we'll just decrement what it's compared to.
- changeling.special_role = SPECIAL_ROLE_MIND_FLAYER
- continue
- pre_changelings += changeling
- changeling.special_role = SPECIAL_ROLE_CHANGELING
-
- return ..()
-
-/datum/game_mode/vampire/announce()
- to_chat(world, "The current game mode is - Vampires+Changelings!")
- to_chat(world, "Some of your co-workers are vampires, and others might not even be your coworkers!")
-
-/datum/game_mode/vampire/changeling/post_setup()
- for(var/datum/mind/changeling as anything in pre_changelings)
- changeling.add_antag_datum(/datum/antagonist/changeling)
- ..()
diff --git a/code/game/gamemodes/wizard/artefact.dm b/code/game/gamemodes/wizard/artefact.dm
deleted file mode 100644
index e4f6698fe3060..0000000000000
--- a/code/game/gamemodes/wizard/artefact.dm
+++ /dev/null
@@ -1,971 +0,0 @@
-/////////Apprentice Contract//////////
-
-/obj/item/contract
- name = "contract"
- desc = "A magic contract previously signed by an apprentice. In exchange for instruction in the magical arts, they are bound to answer your call for aid."
- icon = 'icons/obj/wizard.dmi'
- icon_state ="scroll2"
- throw_speed = 1
- throw_range = 5
- w_class = WEIGHT_CLASS_TINY
- var/used = FALSE
-
-/obj/item/contract/ui_state(mob/user)
- return GLOB.inventory_state
-
-/obj/item/contract/ui_interact(mob/user, datum/tgui/ui = null)
- ui = SStgui.try_update_ui(user, src, ui)
- if(!ui)
- ui = new(user, src, "WizardApprenticeContract", name)
- ui.open()
-
-/obj/item/contract/ui_data(mob/user)
- var/list/data = list()
- data["used"] = used
- return data
-
-/obj/item/contract/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
- if(..())
- return
-
- if(used)
- return
- INVOKE_ASYNC(src, PROC_REF(async_find_apprentice), action, ui.user)
- SStgui.close_uis(src)
-
-/obj/item/contract/proc/async_find_apprentice(action, user)
- if(!ishuman(user))
- return
- var/mob/living/carbon/human/H = user
- used = TRUE
-
- var/image/source = image('icons/obj/cardboard_cutout.dmi', "cutout_wizard")
- var/list/candidates = SSghost_spawns.poll_candidates("Do you want to play as the wizard apprentice of [H.real_name]?", ROLE_WIZARD, TRUE, source = source)
-
- if(!length(candidates))
- used = FALSE
- to_chat(H, "Unable to reach your apprentice! You can either attack the spellbook with the contract to refund your points, or wait and try again later.")
- return
- new /obj/effect/particle_effect/smoke(get_turf(H))
-
- var/mob/C = pick(candidates)
- var/mob/living/carbon/human/M = new /mob/living/carbon/human(get_turf(H))
- M.key = C.key
-
- var/datum/antagonist/wizard/apprentice/apprentice = new /datum/antagonist/wizard/apprentice()
- apprentice.my_teacher = H
- apprentice.class_type = action
- M.mind.add_antag_datum(apprentice)
-
- dust_if_respawnable(C)
-
-/obj/item/contract/attack_self__legacy__attackchain(mob/user as mob)
- if(..())
- return
-
- if(used)
- to_chat(user, "You've already summoned an apprentice or you are in process of summoning one.")
- return
-
- ui_interact(user)
-
-
-///////////////////////////Veil Render//////////////////////
-
-/obj/item/veilrender
- name = "veil render"
- desc = "A wicked curved blade of alien origin, recovered from the ruins of a vast city."
- icon = 'icons/obj/wizard.dmi'
- icon_state = "render"
- item_state = "render"
- force = 15
- throwforce = 10
- w_class = WEIGHT_CLASS_NORMAL
- hitsound = 'sound/weapons/bladeslice.ogg'
- var/charged = 1
- var/spawn_type = /obj/singularity/narsie/wizard
- var/spawn_amt = 1
- var/activate_descriptor = "reality"
- var/rend_desc = "You should run now."
-
-/obj/item/veilrender/attack_self__legacy__attackchain(mob/user as mob)
- if(charged)
- new /obj/effect/rend(get_turf(user), spawn_type, spawn_amt, rend_desc)
- charged = 0
- user.visible_message("[src] hums with power as [user] deals a blow to [activate_descriptor] itself!")
- else
- to_chat(user, "The unearthly energies that powered the blade are now dormant.")
-
-
-/obj/effect/rend
- name = "tear in the fabric of reality"
- desc = "You should run now."
- icon = 'icons/obj/biomass.dmi'
- icon_state = "rift"
- density = TRUE
- anchored = TRUE
- var/spawn_path = /mob/living/simple_animal/cow //defaulty cows to prevent unintentional narsies
- var/spawn_amt_left = 20
-
-/obj/effect/rend/New(loc, spawn_type, spawn_amt, desc)
- ..()
- src.spawn_path = spawn_type
- src.spawn_amt_left = spawn_amt
- src.desc = desc
-
- START_PROCESSING(SSobj, src)
- //return
-
-/obj/effect/rend/Destroy()
- STOP_PROCESSING(SSobj, src)
- return ..()
-
-/obj/effect/rend/process()
- for(var/mob/M in loc)
- return
- new spawn_path(loc)
- spawn_amt_left--
- if(spawn_amt_left <= 0)
- qdel(src)
-
-/obj/effect/rend/attackby__legacy__attackchain(obj/item/I as obj, mob/user as mob)
- if(istype(I, /obj/item/nullrod))
- user.visible_message("[user] seals \the [src] with \the [I].")
- qdel(src)
- return
- return ..()
-
-/obj/effect/rend/singularity_pull()
- return
-
-/obj/item/veilrender/vealrender
- name = "veal render"
- desc = "A wicked curved blade of alien origin, recovered from the ruins of a vast farm."
- spawn_type = /mob/living/simple_animal/cow
- spawn_amt = 20
- activate_descriptor = "hunger"
- rend_desc = "Reverberates with the sound of ten thousand moos."
-
-/obj/item/veilrender/honkrender
- name = "honk render"
- desc = "A wicked curved blade of alien origin, recovered from the ruins of a vast circus."
- spawn_type = /mob/living/simple_animal/hostile/retaliate/clown
- spawn_amt = 10
- activate_descriptor = "depression"
- rend_desc = "Gently wafting with the sounds of endless laughter."
- icon_state = "clownrender"
-
-
-/obj/item/veilrender/crabrender
- name = "crab render"
- desc = "A wicked curved blade of alien origin, recovered from the ruins of a vast aquarium."
- spawn_type = /mob/living/simple_animal/crab
- spawn_amt = 10
- activate_descriptor = "sea life"
- rend_desc = "Gently wafting with the sounds of endless clacking."
-
-/////////////////////////////////////////Scrying///////////////////
-
-/obj/item/scrying
- name = "scrying orb"
- desc = "An incandescent orb of otherworldly energy, staring into it gives you vision beyond mortal means. Also works well as a throwing weapon."
- icon = 'icons/obj/wizard.dmi'
- icon_state ="scrying_orb"
- throw_speed = 7
- throw_range = 15
- throwforce = 25
- damtype = BURN
- force = 15
- hitsound = 'sound/items/welder2.ogg'
- var/mob/current_owner
- var/mob/dead/observer/ghost // owners ghost when active
-
-/obj/item/scrying/Initialize(mapload)
- . = ..()
- START_PROCESSING(SSobj, src)
- AddComponent(/datum/component/boomerang, throw_range, TRUE)
-
-/obj/item/scrying/Destroy()
- STOP_PROCESSING(SSobj, src)
- current_owner = null
- return ..()
-
-/obj/item/scrying/process()
- var/mob/holder = get(loc, /mob)
- if(current_owner && current_owner != holder)
-
- to_chat(current_owner, "Your otherworldly vision fades...")
-
- REMOVE_TRAIT(current_owner, TRAIT_XRAY_VISION, SCRYING_ORB)
- REMOVE_TRAIT(current_owner, TRAIT_NIGHT_VISION, SCRYING_ORB)
- current_owner.update_sight()
- current_owner.update_icons()
-
- current_owner = null
-
- if(!current_owner && holder)
- current_owner = holder
-
- to_chat(current_owner, "You can see...everything!")
-
- ADD_TRAIT(current_owner, TRAIT_XRAY_VISION, SCRYING_ORB)
- ADD_TRAIT(current_owner, TRAIT_NIGHT_VISION, SCRYING_ORB)
- current_owner.update_sight()
- current_owner.update_icons()
-
-/obj/item/scrying/attack_self__legacy__attackchain(mob/user as mob)
- if(in_use)
- return
- in_use = TRUE
- ADD_TRAIT(user, SCRYING, SCRYING_ORB)
- user.visible_message("[user] stares into [src], [user.p_their()] eyes glazing over.",
- "You stare into [src], you can see the entire universe!")
- ghost = user.ghostize(TRUE, COLOR_BLUE, "Magic Spirit of [user.name]")
- while(!QDELETED(user))
- if(user.key || QDELETED(src))
- user.visible_message("[user] blinks, returning to the world around [user.p_them()].",
- "You look away from [src].")
- break
- if(user.get_active_hand() != src)
- user.grab_ghost()
- user.visible_message("[user]'s focus is forced away from [src].",
- "Your vision is ripped away from [src].")
- break
- sleep(5)
- in_use = FALSE
- if(QDELETED(user))
- return
- user.remove_atom_colour(ADMIN_COLOUR_PRIORITY, COLOR_BLUE)
- REMOVE_TRAIT(user, SCRYING, SCRYING_ORB)
-
-/obj/item/scrying/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum)
- . = ..()
- if(!ishuman(hit_atom) || !throwingdatum || iswizard(hit_atom))
- return
- var/mob/living/carbon/human/crushee = hit_atom
- var/zone = ran_zone(throwingdatum.target_zone) // Base 80% to hit the zone you're aiming for
- var/obj/item/organ/external/hit_limb = crushee.get_organ(zone)
- if(hit_limb)
- hit_limb.fracture()
-
-
-/////////////////////Multiverse Blade////////////////////
-GLOBAL_LIST_EMPTY(multiverse)
-
-/obj/item/multisword
- name = "multiverse sword"
- desc = "A weapon capable of conquering the universe and beyond. Activate it to summon copies of yourself from others dimensions to fight by your side."
- icon = 'icons/obj/weapons/energy_melee.dmi'
- lefthand_file = 'icons/mob/inhands/weapons_lefthand.dmi'
- righthand_file = 'icons/mob/inhands/weapons_righthand.dmi'
- icon_state = "energy_katana"
- item_state = "energy_katana"
- hitsound = 'sound/weapons/bladeslice.ogg'
- flags = CONDUCT
- slot_flags = ITEM_SLOT_BELT
- force = 20
- throwforce = 10
- sharp = TRUE
- w_class = WEIGHT_CLASS_SMALL
- attack_verb = list("attacked", "slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut")
- var/faction = list("unassigned")
- var/cooldown = 0
- var/cooldown_between_uses = 400 //time in deciseconds between uses--default of 40 seconds.
- var/assigned = "unassigned"
- var/evil = TRUE
- var/probability_evil = 30 //what's the probability this sword will be evil when activated?
- var/duplicate_self = 0 //Do we want the species randomized along with equipment should the user be duplicated in their entirety?
- var/sword_type = /obj/item/multisword //type of sword to equip.
-
-/obj/item/multisword/New()
- ..()
- GLOB.multiverse |= src
-
-
-/obj/item/multisword/Destroy()
- GLOB.multiverse.Remove(src)
- return ..()
-
-/obj/item/multisword/attack__legacy__attackchain(mob/living/M as mob, mob/living/user as mob) //to prevent accidental friendly fire or out and out grief.
- if(M.real_name == user.real_name)
- to_chat(user, "[src] detects benevolent energies in your target and redirects your attack!")
- return
- ..()
-
-/obj/item/multisword/attack_self__legacy__attackchain(mob/user)
- if(user.mind.special_role == SPECIAL_ROLE_WIZARD_APPRENTICE)
- to_chat(user, "You know better than to touch your teacher's stuff.")
- return
- if(cooldown < world.time)
- var/faction_check = 0
- for(var/F in faction)
- if(F in user.faction)
- faction_check = 1
- break
- if(faction_check == 0)
- faction = list("[user.real_name]")
- assigned = "[user.real_name]"
- user.faction = list("[user.real_name]")
- to_chat(user, "You bind the sword to yourself. You can now use it to summon help.")
- if(!usr.mind.special_role)
- if(prob(probability_evil))
- to_chat(user, "With your new found power you could easily conquer the station!")
-
- var/datum/objective/hijackclone/hijack_objective = new /datum/objective/hijackclone
- hijack_objective.explanation_text = "Ensure only [usr.real_name] and [usr.p_their()] copies are on the shuttle!"
- usr.mind.add_mind_objective(hijack_objective)
- var/list/messages = user.mind.prepare_announce_objectives(FALSE)
- to_chat(user, chat_box_red(messages.Join(" ")))
-
- SSticker.mode.traitors += usr.mind
- usr.mind.special_role = "[usr.real_name] Prime"
- evil = TRUE
- else
- to_chat(user, "With your new found power you could easily defend the station!")
-
- var/datum/objective/survive/new_objective = new /datum/objective/survive
- new_objective.explanation_text = "Survive, and help defend the innocent from the mobs of multiverse clones."
- usr.mind.add_mind_objective(new_objective)
- var/list/messages = user.mind.prepare_announce_objectives(FALSE)
- to_chat(user, chat_box_red(messages.Join(" ")))
-
- SSticker.mode.traitors += usr.mind
- usr.mind.special_role = "[usr.real_name] Prime"
- evil = FALSE
- else
- cooldown = world.time + cooldown_between_uses
- for(var/obj/item/multisword/M in GLOB.multiverse)
- if(M.assigned == assigned)
- M.cooldown = cooldown
-
- var/image/source = image('icons/obj/cardboard_cutout.dmi', "cutout_wizard")
- var/list/candidates = SSghost_spawns.poll_candidates("Do you want to play as the wizard apprentice of [user.real_name]?", ROLE_WIZARD, TRUE, 10 SECONDS, source = source)
- if(length(candidates))
- var/mob/C = pick(candidates)
- spawn_copy(C.client, get_turf(user.loc), user)
- to_chat(user, "The sword flashes, and you find yourself face to face with...you!")
- dust_if_respawnable(C)
-
- else
- to_chat(user, "You fail to summon any copies of yourself. Perhaps you should try again in a bit.")
- else
- to_chat(user, "[src] is recharging! Keep in mind it shares a cooldown with the swords wielded by your copies.")
-
-
-/obj/item/multisword/proc/spawn_copy(client/C, turf/T, mob/user)
- var/mob/living/carbon/human/M = new/mob/living/carbon/human(T)
- if(duplicate_self)
- user.client.prefs.active_character.copy_to(M)
- else
- C.prefs.active_character.copy_to(M)
- M.key = C.key
- M.mind.name = user.real_name
- to_chat(M, "You are an alternate version of [user.real_name] from another universe! Help [user.p_them()] accomplish [user.p_their()] goals at all costs.")
- M.faction = list("[user.real_name]")
- if(duplicate_self)
- M.set_species(user.dna.species.type) //duplicate the sword user's species.
- else
- if(prob(50))
- var/list/list_all_species = list(/datum/species/human, /datum/species/unathi, /datum/species/skrell, /datum/species/tajaran, /datum/species/kidan, /datum/species/golem, /datum/species/diona, /datum/species/machine, /datum/species/slime, /datum/species/grey, /datum/species/vulpkanin)
- M.set_species(pick(list_all_species))
- M.real_name = user.real_name //this is clear down here in case the user happens to become a golem; that way they have the proper name.
- M.name = user.real_name
- if(duplicate_self)
- M.dna = user.dna.Clone()
- M.UpdateAppearance()
- domutcheck(M)
- M.update_body()
- M.update_hair()
- M.update_fhair()
-
- equip_copy(M)
-
- if(evil)
- var/datum/objective/hijackclone/hijack_objective = new /datum/objective/hijackclone
- hijack_objective.explanation_text = "Ensure only [usr.real_name] and [usr.p_their()] copies are on the shuttle!"
- M.mind.add_mind_objective(hijack_objective)
- var/list/messages = M.mind.prepare_announce_objectives(FALSE)
- to_chat(M, chat_box_red(messages.Join(" ")))
-
- M.mind.special_role = SPECIAL_ROLE_MULTIVERSE
- log_game("[M.key] was made a multiverse traveller with the objective to help [usr.real_name] hijack.")
- else
- var/datum/objective/protect/new_objective = new /datum/objective/protect
- new_objective.target = usr.mind
- new_objective.explanation_text = "Protect [usr.real_name], your copy, and help [usr.p_them()] defend the innocent from the mobs of multiverse clones."
- M.mind.add_mind_objective(new_objective)
- var/list/messages = M.mind.prepare_announce_objectives(FALSE)
- to_chat(M, chat_box_red(messages.Join(" ")))
-
- M.mind.special_role = SPECIAL_ROLE_MULTIVERSE
- log_game("[M.key] was made a multiverse traveller with the objective to help [usr.real_name] protect the station.")
-
-/obj/item/multisword/proc/equip_copy(mob/living/carbon/human/M)
-
- var/obj/item/multisword/sword = new sword_type
- sword.assigned = assigned
- sword.faction = list("[assigned]")
- sword.evil = evil
-
- if(duplicate_self)
- //Duplicates the user's current equipent
- var/mob/living/carbon/human/H = usr
-
- var/obj/head = H.get_item_by_slot(ITEM_SLOT_HEAD)
- if(head)
- M.equip_to_slot_or_del(new head.type(M), ITEM_SLOT_HEAD)
-
- var/obj/mask = H.get_item_by_slot(ITEM_SLOT_MASK)
- if(mask)
- M.equip_to_slot_or_del(new mask.type(M), ITEM_SLOT_MASK)
-
- var/obj/glasses = H.get_item_by_slot(ITEM_SLOT_EYES)
- if(glasses)
- M.equip_to_slot_or_del(new glasses.type(M), ITEM_SLOT_EYES)
-
- var/obj/left_ear = H.get_item_by_slot(ITEM_SLOT_LEFT_EAR)
- if(left_ear)
- M.equip_to_slot_or_del(new left_ear.type(M), ITEM_SLOT_LEFT_EAR)
-
- var/obj/right_ear = H.get_item_by_slot(ITEM_SLOT_RIGHT_EAR)
- if(right_ear)
- M.equip_to_slot_or_del(new right_ear.type(M), ITEM_SLOT_RIGHT_EAR)
-
- var/obj/uniform = H.get_item_by_slot(ITEM_SLOT_JUMPSUIT)
- if(uniform)
- M.equip_to_slot_or_del(new uniform.type(M), ITEM_SLOT_JUMPSUIT)
-
- var/obj/suit = H.get_item_by_slot(ITEM_SLOT_OUTER_SUIT)
- if(suit)
- M.equip_to_slot_or_del(new suit.type(M), ITEM_SLOT_OUTER_SUIT)
-
- var/obj/gloves = H.get_item_by_slot(ITEM_SLOT_GLOVES)
- if(gloves)
- M.equip_to_slot_or_del(new gloves.type(M), ITEM_SLOT_GLOVES)
-
- var/obj/shoes = H.get_item_by_slot(ITEM_SLOT_SHOES)
- if(shoes)
- M.equip_to_slot_or_del(new shoes.type(M), ITEM_SLOT_SHOES)
-
- var/obj/belt = H.get_item_by_slot(ITEM_SLOT_BELT)
- if(belt)
- M.equip_to_slot_or_del(new belt.type(M), ITEM_SLOT_BELT)
-
- var/obj/pda = H.get_item_by_slot(ITEM_SLOT_PDA)
- if(pda)
- M.equip_to_slot_or_del(new pda.type(M), ITEM_SLOT_PDA)
-
- var/obj/back = H.get_item_by_slot(ITEM_SLOT_BACK)
- if(back)
- M.equip_to_slot_or_del(new back.type(M), ITEM_SLOT_BACK)
-
- var/obj/suit_storage = H.get_item_by_slot(ITEM_SLOT_SUIT_STORE)
- if(suit_storage)
- M.equip_to_slot_or_del(new suit_storage.type(M), ITEM_SLOT_SUIT_STORE)
-
- var/obj/left_pocket = H.get_item_by_slot(ITEM_SLOT_LEFT_POCKET)
- if(left_pocket)
- M.equip_to_slot_or_del(new left_pocket.type(M), ITEM_SLOT_LEFT_POCKET)
-
- var/obj/right_pocket = H.get_item_by_slot(ITEM_SLOT_RIGHT_POCKET)
- if(right_pocket)
- M.equip_to_slot_or_del(new right_pocket.type(M), ITEM_SLOT_RIGHT_POCKET)
-
- M.equip_to_slot_or_del(sword, ITEM_SLOT_RIGHT_HAND) //Don't duplicate what's equipped to hands, or else duplicate swords could be generated...or weird cases of factionless swords.
- else
- if(istajaran(M) || isunathi(M))
- M.equip_to_slot_or_del(new /obj/item/clothing/shoes/sandal(M), ITEM_SLOT_SHOES) //If they can't wear shoes, give them a pair of sandals.
-
- var/randomize = pick("mobster","roman","wizard","cyborg","syndicate","assistant", "animu", "cultist", "highlander", "clown", "killer", "pirate", "soviet", "officer", "gladiator")
-
- switch(randomize)
- if("mobster")
- M.equip_to_slot_or_del(new /obj/item/clothing/head/fedora(M), ITEM_SLOT_HEAD)
- M.equip_to_slot_or_del(new /obj/item/clothing/shoes/laceup(M), ITEM_SLOT_SHOES)
- M.equip_to_slot_or_del(new /obj/item/clothing/gloves/color/black(M), ITEM_SLOT_GLOVES)
- M.equip_to_slot_or_del(new /obj/item/radio/headset(M), ITEM_SLOT_LEFT_EAR)
- M.equip_to_slot_or_del(new /obj/item/clothing/glasses/sunglasses(M), ITEM_SLOT_EYES)
- M.equip_to_slot_or_del(new /obj/item/clothing/under/suit/really_black(M), ITEM_SLOT_JUMPSUIT)
- M.equip_to_slot_or_del(sword, ITEM_SLOT_RIGHT_HAND)
-
- if("roman")
- var/hat = pick(/obj/item/clothing/head/helmet/roman, /obj/item/clothing/head/helmet/roman/legionaire)
- M.equip_to_slot_or_del(new hat(M), ITEM_SLOT_HEAD)
- M.equip_to_slot_or_del(new /obj/item/clothing/under/costume/roman(M), ITEM_SLOT_JUMPSUIT)
- M.equip_to_slot_or_del(new /obj/item/clothing/shoes/roman(M), ITEM_SLOT_SHOES)
- M.equip_to_slot_or_del(new /obj/item/shield/riot/roman(M), ITEM_SLOT_LEFT_HAND)
- M.equip_to_slot_or_del(sword, ITEM_SLOT_RIGHT_HAND)
-
- if("wizard")
- M.equip_to_slot_or_del(new /obj/item/clothing/under/color/lightpurple(M), ITEM_SLOT_JUMPSUIT)
- M.equip_to_slot_or_del(new /obj/item/clothing/suit/wizrobe/red(M), ITEM_SLOT_OUTER_SUIT)
- M.equip_to_slot_or_del(new /obj/item/clothing/shoes/sandal(M), ITEM_SLOT_SHOES)
- M.equip_to_slot_or_del(new /obj/item/radio/headset(M), ITEM_SLOT_LEFT_EAR)
- M.equip_to_slot_or_del(new /obj/item/clothing/head/wizard/red(M), ITEM_SLOT_HEAD)
- M.equip_to_slot_or_del(sword, ITEM_SLOT_RIGHT_HAND)
-
- if("cyborg")
- if(!ismachineperson(M))
- for(var/obj/item/organ/O in M.bodyparts)
- O.robotize(make_tough = 1)
- M.equip_to_slot_or_del(new /obj/item/clothing/glasses/thermal/eyepatch(M), ITEM_SLOT_EYES)
- M.equip_to_slot_or_del(sword, ITEM_SLOT_RIGHT_HAND)
-
- if("syndicate")
- M.equip_to_slot_or_del(new /obj/item/clothing/under/syndicate(M), ITEM_SLOT_JUMPSUIT)
- M.equip_to_slot_or_del(new /obj/item/clothing/shoes/combat(M), ITEM_SLOT_SHOES)
- M.equip_to_slot_or_del(new /obj/item/clothing/gloves/combat(M), ITEM_SLOT_GLOVES)
- M.equip_to_slot_or_del(new /obj/item/clothing/head/helmet/swat(M), ITEM_SLOT_HEAD)
- M.equip_to_slot_or_del(new /obj/item/radio/headset(M), ITEM_SLOT_LEFT_EAR)
- M.equip_to_slot_or_del(new /obj/item/clothing/suit/armor/vest(M), ITEM_SLOT_OUTER_SUIT)
- M.equip_to_slot_or_del(new /obj/item/clothing/mask/gas(M),ITEM_SLOT_MASK)
- M.equip_to_slot_or_del(sword, ITEM_SLOT_RIGHT_HAND)
-
- if("assistant")
- M.equip_to_slot_or_del(new /obj/item/clothing/under/color/grey(M), ITEM_SLOT_JUMPSUIT)
- M.equip_to_slot_or_del(new /obj/item/radio/headset(M), ITEM_SLOT_LEFT_EAR)
- M.equip_to_slot_or_del(new /obj/item/clothing/shoes/black(M), ITEM_SLOT_SHOES)
- M.equip_to_slot_or_del(sword, ITEM_SLOT_RIGHT_HAND)
-
- if("animu")
- M.equip_to_slot_or_del(new /obj/item/clothing/shoes/sandal(M), ITEM_SLOT_SHOES)
- M.equip_to_slot_or_del(new /obj/item/radio/headset(M), ITEM_SLOT_LEFT_EAR)
- M.equip_to_slot_or_del(new /obj/item/clothing/head/kitty(M), ITEM_SLOT_HEAD)
- M.equip_to_slot_or_del(new /obj/item/clothing/under/dress/schoolgirl(M), ITEM_SLOT_JUMPSUIT)
- M.equip_to_slot_or_del(sword, ITEM_SLOT_RIGHT_HAND)
-
- if("cultist")
- M.equip_to_slot_or_del(new /obj/item/clothing/suit/hooded/cultrobes/alt(M), ITEM_SLOT_OUTER_SUIT)
- M.equip_to_slot_or_del(new /obj/item/clothing/shoes/cult(M), ITEM_SLOT_SHOES)
- M.equip_to_slot_or_del(new /obj/item/radio/headset(M), ITEM_SLOT_LEFT_EAR)
- M.equip_to_slot_or_del(sword, ITEM_SLOT_RIGHT_HAND)
-
- if("highlander")
- M.equip_to_slot_or_del(new /obj/item/clothing/under/costume/kilt(M), ITEM_SLOT_JUMPSUIT)
- M.equip_to_slot_or_del(new /obj/item/radio/headset(M), ITEM_SLOT_LEFT_EAR)
- M.equip_to_slot_or_del(new /obj/item/clothing/head/beret(M), ITEM_SLOT_HEAD)
- M.equip_to_slot_or_del(new /obj/item/clothing/shoes/combat(M), ITEM_SLOT_SHOES)
- M.equip_to_slot_or_del(sword, ITEM_SLOT_RIGHT_HAND)
-
- if("clown")
- M.equip_to_slot_or_del(new /obj/item/clothing/under/rank/civilian/clown(M), ITEM_SLOT_JUMPSUIT)
- M.equip_to_slot_or_del(new /obj/item/radio/headset(M), ITEM_SLOT_LEFT_EAR)
- M.equip_to_slot_or_del(new /obj/item/clothing/shoes/clown_shoes(M), ITEM_SLOT_SHOES)
- M.equip_to_slot_or_del(new /obj/item/clothing/mask/gas/clown_hat(M), ITEM_SLOT_MASK)
- M.equip_to_slot_or_del(new /obj/item/bikehorn(M), ITEM_SLOT_LEFT_POCKET)
- M.equip_to_slot_or_del(sword, ITEM_SLOT_RIGHT_HAND)
-
- if("killer")
- M.equip_to_slot_or_del(new /obj/item/clothing/under/misc/overalls(M), ITEM_SLOT_JUMPSUIT)
- M.equip_to_slot_or_del(new /obj/item/clothing/shoes/white(M), ITEM_SLOT_SHOES)
- M.equip_to_slot_or_del(new /obj/item/clothing/gloves/color/latex(M), ITEM_SLOT_GLOVES)
- M.equip_to_slot_or_del(new /obj/item/clothing/mask/surgical(M), ITEM_SLOT_MASK)
- M.equip_to_slot_or_del(new /obj/item/clothing/head/welding(M), ITEM_SLOT_HEAD)
- M.equip_to_slot_or_del(new /obj/item/radio/headset(M), ITEM_SLOT_LEFT_EAR)
- M.equip_to_slot_or_del(new /obj/item/clothing/suit/apron(M), ITEM_SLOT_OUTER_SUIT)
- M.equip_to_slot_or_del(new /obj/item/kitchen/knife(M), ITEM_SLOT_LEFT_POCKET)
- M.equip_to_slot_or_del(new /obj/item/scalpel(M), ITEM_SLOT_RIGHT_POCKET)
- M.equip_to_slot_or_del(sword, ITEM_SLOT_RIGHT_HAND)
- for(var/obj/item/carried_item in M.contents)
- if(!istype(carried_item, /obj/item/bio_chip))
- carried_item.add_mob_blood(M)
-
- if("pirate")
- M.equip_to_slot_or_del(new /obj/item/clothing/under/costume/pirate(M), ITEM_SLOT_JUMPSUIT)
- M.equip_to_slot_or_del(new /obj/item/clothing/shoes/brown(M), ITEM_SLOT_SHOES)
- M.equip_to_slot_or_del(new /obj/item/clothing/head/bandana(M), ITEM_SLOT_HEAD)
- M.equip_to_slot_or_del(new /obj/item/clothing/glasses/eyepatch(M), ITEM_SLOT_EYES)
- M.equip_to_slot_or_del(new /obj/item/radio/headset(M), ITEM_SLOT_LEFT_EAR)
- M.equip_to_slot_or_del(sword, ITEM_SLOT_RIGHT_HAND)
-
- if("soviet")
- M.equip_to_slot_or_del(new /obj/item/clothing/head/sovietofficerhat(M), ITEM_SLOT_HEAD)
- M.equip_to_slot_or_del(new /obj/item/clothing/shoes/combat(M), ITEM_SLOT_SHOES)
- M.equip_to_slot_or_del(new /obj/item/clothing/gloves/combat(M), ITEM_SLOT_GLOVES)
- M.equip_to_slot_or_del(new /obj/item/radio/headset(M), ITEM_SLOT_LEFT_EAR)
- M.equip_to_slot_or_del(new /obj/item/clothing/suit/sovietcoat(M), ITEM_SLOT_OUTER_SUIT)
- M.equip_to_slot_or_del(new /obj/item/clothing/under/new_soviet/sovietofficer(M), ITEM_SLOT_JUMPSUIT)
- M.equip_to_slot_or_del(sword, ITEM_SLOT_RIGHT_HAND)
-
- if("officer")
- M.equip_to_slot_or_del(new /obj/item/clothing/head/helmet/space/deathsquad/beret(M), ITEM_SLOT_HEAD)
- M.equip_to_slot_or_del(new /obj/item/clothing/shoes/combat(M), ITEM_SLOT_SHOES)
- M.equip_to_slot_or_del(new /obj/item/clothing/gloves/combat(M), ITEM_SLOT_GLOVES)
- M.equip_to_slot_or_del(new /obj/item/clothing/mask/cigarette/cigar/havana(M), ITEM_SLOT_MASK)
- M.equip_to_slot_or_del(new /obj/item/radio/headset(M), ITEM_SLOT_LEFT_EAR)
- M.equip_to_slot_or_del(new /obj/item/clothing/suit/jacket/miljacket(M), ITEM_SLOT_OUTER_SUIT)
- M.equip_to_slot_or_del(new /obj/item/clothing/under/syndicate(M), ITEM_SLOT_JUMPSUIT)
- M.equip_to_slot_or_del(new /obj/item/clothing/glasses/eyepatch(M), ITEM_SLOT_EYES)
- M.equip_to_slot_or_del(sword, ITEM_SLOT_RIGHT_HAND)
-
- if("gladiator")
- M.equip_to_slot_or_del(new /obj/item/clothing/head/helmet/gladiator(M), ITEM_SLOT_HEAD)
- M.equip_to_slot_or_del(new /obj/item/clothing/under/costume/gladiator(M), ITEM_SLOT_JUMPSUIT)
- M.equip_to_slot_or_del(new /obj/item/radio/headset(M), ITEM_SLOT_LEFT_EAR)
- M.equip_to_slot_or_del(new /obj/item/clothing/shoes/sandal(M), ITEM_SLOT_SHOES)
- M.equip_to_slot_or_del(sword, ITEM_SLOT_RIGHT_HAND)
-
-
- else
- return
-
- var/obj/item/card/id/W = new /obj/item/card/id
- if(duplicate_self)
- var/duplicated_access = usr.get_item_by_slot(ITEM_SLOT_ID)
- if(duplicated_access && istype(duplicated_access, /obj/item/card/id))
- var/obj/item/card/id/duplicated_id = duplicated_access
- W.access = duplicated_id.access
- W.icon_state = duplicated_id.icon_state
- else
- W.access += ACCESS_MAINT_TUNNELS
- W.icon_state = "centcom"
- else
- W.access += ACCESS_MAINT_TUNNELS
- W.icon_state = "centcom"
- W.assignment = "Multiverse Traveller"
- W.registered_name = M.real_name
- W.update_label(M.real_name)
- W.SetOwnerInfo(M)
- M.equip_to_slot_or_del(W, ITEM_SLOT_ID)
-
- if(isvox(M))
- M.dna.species.after_equip_job(null, M) //Nitrogen tanks
- if(isplasmaman(M))
- M.dna.species.after_equip_job(null, M) //No fireballs from other dimensions.
-
- M.update_icons()
-
-/obj/item/multisword/pure_evil
- probability_evil = 100
-
-/// If We are to be used and spent, let it be for a noble purpose.
-/obj/item/multisword/pike
- name = "phantom pike"
- desc = "A fishing pike that appears to be imbued with a peculiar energy."
- icon = 'icons/obj/weapons/melee.dmi'
- icon_state = "harpoon"
- item_state = "harpoon"
- cooldown_between_uses = 200 //Half the time
- probability_evil = 100
- duplicate_self = 1
- sword_type = /obj/item/multisword/pike
-
-
-/////////////////////////////////////////Necromantic Stone///////////////////
-
-/obj/item/necromantic_stone
- name = "necromantic stone"
- desc = "A shard capable of resurrecting humans as skeleton thralls."
- icon = 'icons/obj/wizard.dmi'
- icon_state = "necrostone"
- item_state = "electronic"
- origin_tech = "bluespace=4;materials=4"
- w_class = WEIGHT_CLASS_TINY
- ///List of mobs transformed into skeletons by the stone
- var/list/active_skeletons = list()
- ///How many skeletons can be converted by the stone at a time
- var/max_skeletons = 3
- ///If the stone can convert infinite skeletons, bypassing max_skeletons
- var/unlimited = FALSE
- ///If the stone converts into anime instead of skeletons
- var/heresy = FALSE
- ///how long the additional_thralls_cooldown is
- var/above_cap_cooldown = 1 MINUTES
- ///Cooldown between uses when living skeletons above max skeletons
- COOLDOWN_DECLARE(additional_thralls_cooldown)
-
-/obj/item/necromantic_stone/Destroy()
- . = ..()
- active_skeletons = null
-
-/obj/item/necromantic_stone/examine(mob/user)
- . = ..()
- var/skele_count = length(active_skeletons)
- if(skele_count)
- . += "[skele_count] skeleton thrall[skele_count > 1 ? "s have" : " has"] been risen by [src]."
- if(unlimited || skele_count < max_skeletons)
- return
- var/cooldown_time_left = COOLDOWN_TIMELEFT(src, additional_thralls_cooldown)
- if(cooldown_time_left)
- . += "[src] is being strained by the amount of risen skeletons thralls. It cannot be used to rise another skeleton thrall for [cooldown_time_left / 10] seconds."
-
-/obj/item/necromantic_stone/attack__legacy__attackchain(mob/living/carbon/human/victim, mob/living/carbon/human/necromancer)
- if(!istype(victim) || !istype(necromancer))
- return ..()
-
-
- if(victim.stat != DEAD)
- to_chat(necromancer, "This artifact can only affect the dead!")
- return
-
- if((!victim.mind || !victim.client) && !victim.grab_ghost())
- to_chat(necromancer, "There is no soul connected to this body...")
- return
-
- if(!check_skeletons()) //If above the cap, there is a cooldown on additional skeletons
- to_chat(necromancer, "The amount of skeleton thralls risen by [src] strains its power.")
- if(!COOLDOWN_FINISHED(src, additional_thralls_cooldown))
- to_chat(necromancer, "[src] cannot rise another thrall for [DisplayTimeText(COOLDOWN_TIMELEFT(src, additional_thralls_cooldown))].")
- return
- COOLDOWN_START(src, additional_thralls_cooldown, above_cap_cooldown)
-
- convert_victim(victim, necromancer)
-
-///Mindslave and equip the victim
-/obj/item/necromantic_stone/proc/convert_victim(mob/living/carbon/human/victim, mob/living/carbon/human/necromancer)
- active_skeletons |= victim
- var/greet_text = "You have been revived by [necromancer.real_name]!\n[necromancer.p_theyre(TRUE)] your master now, assist them even if it costs you your new life!"
- if(!victim.mind.has_antag_datum(/datum/antagonist/mindslave/necromancy))
- victim.mind.add_antag_datum(new /datum/antagonist/mindslave/necromancy(necromancer.mind, greet_text))
-
- if(heresy)
- equip_heresy(victim)//oh god why
- return
-
- victim.visible_message("A massive amount of flesh sloughs off [victim] and a skeleton rises up!")
- equip_skeleton(victim)
-
-///Clean the list of active skeletons and check if more can be summoned easily
-/obj/item/necromantic_stone/proc/check_skeletons()
- . = FALSE
- if(unlimited)
- return TRUE
-
- listclearnulls(active_skeletons)
- var/living_skeletons = 0
- for(var/mob/living/carbon/human/skeleton as anything in active_skeletons)
- if(!ishuman(skeleton))
- active_skeletons.Remove(skeleton)
- continue
- if(skeleton.stat != DEAD)
- living_skeletons++
-
- if(living_skeletons < max_skeletons)
- return TRUE
-
-//Funny gimmick, skeletons always seem to wear roman/ancient armour
-//Voodoo Zombie Pirates added for paradise
-///Udate the mobs species and gear
-/obj/item/necromantic_stone/proc/equip_skeleton(mob/living/carbon/human/victim)
- victim.set_species(/datum/species/skeleton) // OP skellybones
- victim.grab_ghost() // yoinks the ghost if its not in the body
- victim.revive()
-
- for(var/obj/item/item in victim)
- victim.drop_item_to_ground(item)
-
- var/skeleton_type = pick("roman", "pirate", "yand", "clown")
-
- switch(skeleton_type)
- if("roman")
- var/hat = pick(/obj/item/clothing/head/helmet/roman, /obj/item/clothing/head/helmet/roman/legionaire)
- victim.equip_to_slot_or_del(new hat(victim), ITEM_SLOT_HEAD)
- victim.equip_to_slot_or_del(new /obj/item/clothing/under/costume/roman(victim), ITEM_SLOT_JUMPSUIT)
- victim.equip_to_slot_or_del(new /obj/item/clothing/shoes/roman(victim), ITEM_SLOT_SHOES)
- victim.equip_to_slot_or_del(new /obj/item/shield/riot/roman(victim), ITEM_SLOT_LEFT_HAND)
- victim.equip_to_slot_or_del(new /obj/item/claymore(victim), ITEM_SLOT_RIGHT_HAND)
- victim.equip_to_slot_or_del(new /obj/item/spear(victim), ITEM_SLOT_BACK)
- if("pirate")
- victim.equip_to_slot_or_del(new /obj/item/clothing/under/costume/pirate(victim), ITEM_SLOT_JUMPSUIT)
- victim.equip_to_slot_or_del(new /obj/item/clothing/suit/pirate_brown(victim), ITEM_SLOT_OUTER_SUIT)
- victim.equip_to_slot_or_del(new /obj/item/clothing/head/bandana(victim), ITEM_SLOT_HEAD)
- victim.equip_to_slot_or_del(new /obj/item/clothing/shoes/sandal(victim), ITEM_SLOT_SHOES)
- victim.equip_to_slot_or_del(new /obj/item/clothing/glasses/eyepatch(victim), ITEM_SLOT_EYES)
- victim.equip_to_slot_or_del(new /obj/item/claymore(victim), ITEM_SLOT_RIGHT_HAND)
- victim.equip_to_slot_or_del(new /obj/item/spear(victim), ITEM_SLOT_BACK)
- victim.equip_to_slot_or_del(new /obj/item/shield/riot/roman(victim), ITEM_SLOT_LEFT_HAND)
- if("yand")//mine is an evil laugh
- victim.equip_to_slot_or_del(new /obj/item/clothing/shoes/sandal(victim), ITEM_SLOT_SHOES)
- victim.equip_to_slot_or_del(new /obj/item/clothing/head/kitty(victim), ITEM_SLOT_HEAD)
- victim.equip_to_slot_or_del(new /obj/item/clothing/under/dress/schoolgirl(victim), ITEM_SLOT_JUMPSUIT)
- victim.equip_to_slot_or_del(new /obj/item/clothing/suit/armor/vest(victim), ITEM_SLOT_OUTER_SUIT)
- victim.equip_to_slot_or_del(new /obj/item/katana(victim), ITEM_SLOT_RIGHT_HAND)
- victim.equip_to_slot_or_del(new /obj/item/shield/riot/roman(victim), ITEM_SLOT_LEFT_HAND)
- victim.equip_to_slot_or_del(new /obj/item/spear(victim), ITEM_SLOT_BACK)
- if("clown")
- victim.equip_to_slot_or_del(new /obj/item/clothing/under/rank/civilian/clown(victim), ITEM_SLOT_JUMPSUIT)
- victim.equip_to_slot_or_del(new /obj/item/clothing/shoes/clown_shoes(victim), ITEM_SLOT_SHOES)
- victim.equip_to_slot_or_del(new /obj/item/clothing/mask/gas/clown_hat(victim), ITEM_SLOT_MASK)
- victim.equip_to_slot_or_del(new /obj/item/clothing/head/stalhelm(victim), ITEM_SLOT_HEAD)
- victim.equip_to_slot_or_del(new /obj/item/bikehorn(victim), ITEM_SLOT_LEFT_POCKET)
- victim.equip_to_slot_or_del(new /obj/item/claymore(victim), ITEM_SLOT_RIGHT_HAND)
- victim.equip_to_slot_or_del(new /obj/item/shield/riot/roman(victim), ITEM_SLOT_LEFT_HAND)
- victim.equip_to_slot_or_del(new /obj/item/spear(victim), ITEM_SLOT_BACK)
-
-///Updates the mobs species and gear to anime
-/obj/item/necromantic_stone/proc/equip_heresy(mob/living/carbon/human/victim)
- victim.set_species(/datum/species/human)
- if(victim.gender == MALE)
- victim.change_gender(FEMALE)
-
- var/list/anime_hair =list("Odango", "Kusanagi Hair", "Pigtails", "Hime Cut", "Floorlength Braid", "Ombre", "Twincurls", "Twincurls 2")
- victim.change_hair(pick(anime_hair))
-
- var/list/anime_hair_colours = list(list(216, 192, 120),
- list(140,170,74),list(0,0,0))
-
- var/list/chosen_colour = pick(anime_hair_colours)
- victim.change_hair_color(chosen_colour[1], chosen_colour[2], chosen_colour[3])
-
- victim.update_dna()
- victim.update_body()
- victim.grab_ghost()
- victim.revive()
-
- victim.equip_to_slot_or_del(new /obj/item/clothing/shoes/sandal(victim), ITEM_SLOT_SHOES)
- victim.equip_to_slot_or_del(new /obj/item/clothing/head/kitty(victim), ITEM_SLOT_HEAD)
- victim.equip_to_slot_or_del(new /obj/item/clothing/under/dress/schoolgirl(victim), ITEM_SLOT_JUMPSUIT)
- victim.equip_to_slot_or_del(new /obj/item/clothing/suit/armor/vest(victim), ITEM_SLOT_OUTER_SUIT)
- victim.equip_to_slot_or_del(new /obj/item/katana(victim), ITEM_SLOT_RIGHT_HAND)
- victim.equip_to_slot_or_del(new /obj/item/shield/riot/roman(victim), ITEM_SLOT_LEFT_HAND)
- victim.equip_to_slot_or_del(new /obj/item/spear(victim), ITEM_SLOT_BACK)
-
- if(!victim.real_name || victim.real_name == "unknown")
- victim.real_name = "Neko-chan"
- else
- victim.real_name = "[victim.name]-chan"
-
- victim.mind.assigned_role = SPECIAL_ROLE_WIZARD
- victim.mind.special_role = SPECIAL_ROLE_WIZARD
-
- victim.say("NYA!~")
-
-/obj/item/necromantic_stone/unlimited
- unlimited = TRUE
-
-/obj/item/necromantic_stone/nya
- name = "nya-cromantic stone"
- desc = "A shard capable of resurrecting humans as creatures of Vile Heresy. Even the Wizard Federation fears it.."
- icon = 'icons/obj/wizard.dmi'
- icon_state = "nyacrostone"
- item_state = "electronic"
- origin_tech = "bluespace=4;materials=4"
- heresy = TRUE
- unlimited = TRUE
-
-/obj/item/organ/internal/heart/cursed/wizard
- max_shocks_allowed = 3
- pump_delay = 60
- heal_brute = 25
- heal_burn = 25
- heal_oxy = 25
-
-/obj/item/reagent_containers/drinks/everfull
- name = "everfull mug"
- desc = "An enchanted mug which can be filled with any of various liquids on command."
- icon_state = "evermug"
-
-/obj/item/reagent_containers/drinks/everfull/attack_self__legacy__attackchain(mob/user)
- var/static/list/options = list("Omnizine" = image(icon = 'icons/obj/storage.dmi', icon_state = "firstaid"),
- "Ale" = image(icon = 'icons/obj/drinks.dmi', icon_state = "alebottle"),
- "Wine" = image(icon = 'icons/obj/drinks.dmi', icon_state = "wineglass"),
- "Holy Water" = image(icon = 'icons/obj/drinks.dmi', icon_state = "holyflask"),
- "Welder Fuel" = image(icon = 'icons/obj/objects.dmi', icon_state = "fuel"),
- "Vomit" = image(icon = 'icons/effects/blood.dmi', icon_state = "vomit_1"))
- var/static/list/options_to_reagent = list("Omnizine" = "omnizine",
- "Ale" = "ale",
- "Wine" = "wine",
- "Holy Water" = "holywater",
- "Welder Fuel" = "fuel",
- "Vomit" = "vomit")
- var/static/list/options_to_descriptions = list("Omnizine" = "a strange pink-white liquid",
- "Ale" = "foamy amber ale",
- "Wine" = "deep red wine",
- "Holy Water" = "sparkling clear water",
- "Welder Fuel" = "a dark, pungent, oily substance",
- "Vomit" = "warm chunky vomit")
-
- var/choice = show_radial_menu(user, src, options, require_near = TRUE)
- if(!choice || user.stat || !in_range(user, src) || QDELETED(src))
- return
- to_chat(user, "The [name] fills to brimming with [options_to_descriptions[choice]].")
- magic_fill(options_to_reagent[choice])
-
-/obj/item/reagent_containers/drinks/everfull/proc/magic_fill(reagent_choice)
- reagents.clear_reagents()
- reagents.add_reagent(reagent_choice, volume)
-
-//Oblivion Enforcer clothing (the halberd and gloves are defined elsewhere)
-
-/obj/item/clothing/head/hooded/oblivion
- name = "Oblivion Enforcer's hood"
- desc = "A hood worn by an Oblivion Enforcer."
- icon_state = "oblivionhood"
- flags = BLOCKHAIR
- flags_inv = HIDEFACE
- flags_cover = HEADCOVERSEYES
- armor = list(MELEE = 20, BULLET = 5, LASER = 5, ENERGY = 5, BOMB = 0, RAD = INFINITY, FIRE = 5, ACID = 5)
- flags_2 = RAD_PROTECT_CONTENTS_2
- cold_protection = HEAD
- min_cold_protection_temperature = SPACE_HELM_MIN_TEMP_PROTECT
- magical = TRUE
- sprite_sheets = list(
- "Vox" = 'icons/mob/clothing/species/vox/head.dmi',
- "Kidan" = 'icons/mob/clothing/species/kidan/head.dmi',
- "Grey" = 'icons/mob/clothing/species/grey/head.dmi',
- "Drask" = 'icons/mob/clothing/species/drask/head.dmi',
- "Tajaran" = 'icons/mob/clothing/species/tajaran/head.dmi',
- "Unathi" = 'icons/mob/clothing/species/unathi/head.dmi',
- "Vulpkanin" = 'icons/mob/clothing/species/vulpkanin/head.dmi'
- )
-
-/obj/item/clothing/suit/hooded/oblivion
- name = "Oblivion Enforcer's robes"
- desc = "A set of armored, radiation-proof robes worn by Oblivion Enforcers."
- icon_state = "oblivionarmor"
- item_state = "oblivionarmor"
- body_parts_covered = UPPER_TORSO | LOWER_TORSO | LEGS | FEET | ARMS | HANDS
- hoodtype = /obj/item/clothing/head/hooded/oblivion
- allowed = list(/obj/item/supermatter_halberd, /obj/item/nuke_core/supermatter_sliver)
- armor = list(MELEE = 35, BULLET = 20, LASER = 35, ENERGY = 10, BOMB = 15, RAD = INFINITY, FIRE = 5, ACID = 5)
- flags_inv = HIDEJUMPSUIT | HIDESHOES | HIDETAIL | HIDESHOES
- flags = THICKMATERIAL
- flags_2 = RAD_PROTECT_CONTENTS_2
- magical = TRUE
- sprite_sheets = list(
- "Vox" = 'icons/mob/clothing/species/vox/suit.dmi',
- "Kidan" = 'icons/mob/clothing/species/kidan/suit.dmi',
- "Grey" = 'icons/mob/clothing/species/grey/suit.dmi',
- "Drask" = 'icons/mob/clothing/species/drask/suit.dmi',
- "Tajaran" = 'icons/mob/clothing/species/tajaran/suit.dmi',
- "Unathi" = 'icons/mob/clothing/species/unathi/suit.dmi',
- "Vulpkanin" = 'icons/mob/clothing/species/vulpkanin/suit.dmi'
- )
-
-/obj/item/clothing/suit/hooded/oblivion/Initialize(mapload)
- . = ..()
- ADD_TRAIT(src, TRAIT_SUPERMATTER_IMMUNE, ROUNDSTART_TRAIT)
-
-/obj/item/clothing/mask/gas/voice_modulator/oblivion
- name = "Oblivion Enforcer's mask"
- desc = "The mask of an Oblivion Enforcer. Don't forget to turn it on before giving your one-liners!"
- icon_state = "oblivionmask"
- item_state = "oblivionmask"
- sprite_sheets = list(
- "Vox" = 'icons/mob/clothing/species/vox/mask.dmi',
- "Kidan" = 'icons/mob/clothing/species/kidan/mask.dmi',
- "Grey" = 'icons/mob/clothing/species/grey/mask.dmi',
- "Drask" = 'icons/mob/clothing/species/drask/mask.dmi',
- "Tajaran" = 'icons/mob/clothing/species/tajaran/mask.dmi',
- "Unathi" = 'icons/mob/clothing/species/unathi/mask.dmi',
- "Vulpkanin" = 'icons/mob/clothing/species/vulpkanin/mask.dmi'
- )
-
-/obj/item/clothing/shoes/white/enforcer
- name = "hypernobilium weave shoes"
- desc = "They're surprisingly comfortable and designed to fit under an Oblivion Enforcer's robes."
- magical = TRUE
-
-/obj/item/clothing/shoes/white/enforcer/Initialize(mapload)
- . = ..()
- ADD_TRAIT(src, TRAIT_SUPERMATTER_IMMUNE, ROUNDSTART_TRAIT)
-
-/obj/item/clothing/under/color/white/enforcer
- name = "hypernobilium weave jumpsuit"
- desc = "A close-fitting, breathable jumpsuit, tailored for the dirty work of an Oblivion Enforcer."
- has_sensor = FALSE
- magical = TRUE
-
-/obj/item/clothing/under/color/white/enforcer/Initialize(mapload)
- . = ..()
- ADD_TRAIT(src, TRAIT_SUPERMATTER_IMMUNE, ROUNDSTART_TRAIT)
diff --git a/code/game/gamemodes/wizard/godhand.dm b/code/game/gamemodes/wizard/godhand.dm
deleted file mode 100644
index f75c5a692b833..0000000000000
--- a/code/game/gamemodes/wizard/godhand.dm
+++ /dev/null
@@ -1,162 +0,0 @@
-/obj/item/melee/touch_attack
- name = "outstretched hand"
- desc = "High Five?"
- var/catchphrase = "High Five!"
- var/on_use_sound = null
- var/datum/spell/touch/attached_spell
- icon = 'icons/obj/weapons/magical_weapons.dmi'
- icon_state = "disintegrate"
- lefthand_file = 'icons/mob/inhands/items_lefthand.dmi'
- righthand_file = 'icons/mob/inhands/items_righthand.dmi'
- item_state = null
- flags = ABSTRACT | NODROP | DROPDEL
- w_class = WEIGHT_CLASS_HUGE
- force = 0
- throwforce = 0
- throw_range = 0
- throw_speed = 0
- new_attack_chain = TRUE
- /// Has it been blocked by antimagic? If so, abort.
- var/blocked_by_antimagic = FALSE
-
-/obj/item/melee/touch_attack/New(spell)
- attached_spell = spell
- ..()
-
-/obj/item/melee/touch_attack/Destroy()
- if(attached_spell)
- attached_spell.attached_hand = null
- attached_spell.UnregisterSignal(attached_spell.action.owner, COMSIG_MOB_WILLINGLY_DROP)
- return ..()
-
-/obj/item/melee/touch_attack/customised_abstract_text(mob/living/carbon/owner)
- return "[owner.p_their(TRUE)] [owner.l_hand == src ? "left hand" : "right hand"] is burning in magic fire."
-
-/obj/item/melee/touch_attack/attack(mob/living/target, mob/living/carbon/human/user)
- if(..() || !iscarbon(user)) //Look ma, no hands
- return FINISH_ATTACK
- if(HAS_TRAIT(user, TRAIT_HANDS_BLOCKED))
- to_chat(user, "You can't reach out!")
- return FINISH_ATTACK
-
-/obj/item/melee/touch_attack/after_attack(atom/target, mob/user, proximity_flag, click_parameters)
- . = ..()
- var/mob/mob_victim = target
- if(istype(mob_victim) && mob_victim.can_block_magic(attached_spell.antimagic_flags))
- to_chat(user, "[mob_victim] absorbs your spell!")
- blocked_by_antimagic = TRUE
- if(attached_spell && attached_spell.cooldown_handler)
- attached_spell.cooldown_handler.start_recharge(attached_spell.cooldown_handler.recharge_duration * 0.5)
- qdel(src)
- return
-
-/obj/item/melee/touch_attack/proc/handle_delete(mob/user)
- if(catchphrase)
- user.say(catchphrase)
- playsound(get_turf(user), on_use_sound, 50, 1)
- if(attached_spell)
- attached_spell.perform(list())
- qdel(src)
-
-/obj/item/melee/touch_attack/disintegrate
- name = "disintegrating touch"
- desc = "This hand of mine glows with an awesome power!"
- catchphrase = "EI NATH!!"
- on_use_sound = 'sound/magic/disintegrate.ogg'
- icon_state = "disintegrate"
- item_state = "disintegrate"
-
-
-/obj/item/melee/touch_attack/disintegrate/after_attack(atom/target, mob/user, proximity_flag, click_parameters)
- . = ..()
- if(!proximity_flag || target == user || blocked_by_antimagic || !ismob(target) || !iscarbon(user) || HAS_TRAIT(user, TRAIT_HANDS_BLOCKED)) //exploding after touching yourself would be bad
- return
- var/mob/M = target
- do_sparks(4, 0, M.loc) //no idea what the 0 is
- M.gib()
- handle_delete(user)
-
-/obj/item/melee/touch_attack/fleshtostone
- name = "petrifying touch"
- desc = "That's the bottom line, because flesh to stone said so!"
- catchphrase = "STAUN EI!!"
- on_use_sound = 'sound/magic/fleshtostone.ogg'
- icon_state = "fleshtostone"
- item_state = "fleshtostone"
-
-/obj/item/melee/touch_attack/fleshtostone/after_attack(atom/target, mob/user, proximity_flag, click_parameters)
- . = ..()
-
- if(!proximity_flag || target == user || blocked_by_antimagic || !isliving(target) || !iscarbon(user) || HAS_TRAIT(user, TRAIT_HANDS_BLOCKED)) //getting hard after touching yourself would also be bad
- return
- var/mob/living/L = target
- L.Stun(4 SECONDS)
- new /obj/structure/closet/statue(L.loc, L)
- handle_delete(user)
-
-/obj/item/melee/touch_attack/plushify
- name = "fabric touch"
- desc = "The power to sew your foes into a doom cut from the fabric of fate."
- catchphrase = "MAHR-XET 'ABL"
- on_use_sound = 'sound/magic/smoke.ogg'
- icon_state = "disintegrate"
- item_state = "disintegrate"
- color = COLOR_PURPLE
-
-/obj/item/melee/touch_attack/plushify/after_attack(atom/target, mob/user, proximity_flag, click_parameters)
- . = ..()
-
- if(!proximity_flag || target == user || blocked_by_antimagic || !isliving(target) || !iscarbon(user) || HAS_TRAIT(user, TRAIT_HANDS_BLOCKED)) //There are better ways to get a good nights sleep in a bed.
- return
- var/mob/living/L = target
- L.plushify()
- handle_delete(user)
-
-
-/obj/item/melee/touch_attack/fake_disintegrate
- name = "toy plastic hand"
- desc = "This hand of mine glows with an awesome power! Ok, maybe just batteries."
- catchphrase = "EI NATH!!"
- on_use_sound = 'sound/magic/disintegrate.ogg'
- icon_state = "disintegrate"
- item_state = "disintegrate"
- needs_permit = FALSE
-
-/obj/item/melee/touch_attack/fake_disintegrate/after_attack(atom/target, mob/user, proximity_flag, click_parameters)
- . = ..()
-
- if(!proximity_flag || target == user || blocked_by_antimagic || !ismob(target) || !iscarbon(user) || HAS_TRAIT(user, TRAIT_HANDS_BLOCKED)) //not exploding after touching yourself would be bad
- return
- do_sparks(4, 0, target.loc)
- playsound(target.loc, 'sound/goonstation/effects/gib.ogg', 50, 1)
- handle_delete(user)
-
-/obj/item/melee/touch_attack/cluwne
- name = "cluwne touch"
- desc = "It's time to start clowning around."
- catchphrase = "NWOLC EGNEVER"
- on_use_sound = 'sound/misc/sadtrombone.ogg'
- icon_state = "cluwnecurse"
- item_state = "cluwnecurse"
-
-/obj/item/melee/touch_attack/cluwne/after_attack(atom/target, mob/user, proximity_flag, click_parameters)
- . = ..()
-
- if(!proximity_flag || target == user || blocked_by_antimagic || !ishuman(target) || !iscarbon(user) || HAS_TRAIT(user, TRAIT_HANDS_BLOCKED)) //clowning around after touching yourself would unsurprisingly, be bad
- return
-
- if(iswizard(target))
- to_chat(user, "The spell has no effect on [target].")
- return
-
- var/datum/effect_system/smoke_spread/s = new
- s.set_up(5, FALSE, target)
- s.start()
-
- var/mob/living/carbon/human/H = target
- if(H.mind)
- if(H.mind.assigned_role != "Cluwne")
- H.makeCluwne()
- else
- H.makeAntiCluwne()
- handle_delete(user)
diff --git a/code/game/gamemodes/wizard/magic_tarot.dm b/code/game/gamemodes/wizard/magic_tarot.dm
deleted file mode 100644
index 4bd0c9ec4fe79..0000000000000
--- a/code/game/gamemodes/wizard/magic_tarot.dm
+++ /dev/null
@@ -1,1023 +0,0 @@
-/obj/item/tarot_generator
- name = "enchanted tarot card deck"
- desc = "This tarot card box has quite the array of runes and artwork on it."
- icon = 'icons/obj/playing_cards.dmi'
- icon_state = "tarot_box"
- w_class = WEIGHT_CLASS_SMALL
- /// What is the maximum number of cards the tarot generator can have in the world at a time?
- var/maximum_cards = 3
- /// List of cards we have created, to check against maximum, and so we can purge them from the pack.
- var/list/our_card_list = list()
- ///How long the cooldown is each time we draw a card before we can draw another?
- var/our_card_cooldown_time = 25 SECONDS
- COOLDOWN_DECLARE(card_cooldown)
-
-/obj/item/tarot_generator/wizard
- maximum_cards = 5
- our_card_cooldown_time = 12 SECONDS // A minute for a full hand of 5 cards
-
-/obj/item/tarot_generator/attack_self__legacy__attackchain(mob/user)
- if(!COOLDOWN_FINISHED(src, card_cooldown))
- to_chat(user, "[src]'s magic is still recovering from the last card, wait [round(COOLDOWN_TIMELEFT(src, card_cooldown) / 10)] more second\s!")
- return
- if(length(our_card_list) >= maximum_cards)
- to_chat(user, "[src]'s magic can only support up to [maximum_cards] in the world at once, use or destroy some!")
- return
- var/obj/item/magic_tarot_card/MTC = new /obj/item/magic_tarot_card(get_turf(src), src)
- our_card_list += MTC
- user.put_in_hands(MTC)
- to_chat(user, "You draw [MTC.name]... [MTC.card_desc]") //No period on purpose.
- COOLDOWN_START(src, card_cooldown, our_card_cooldown_time)
-
-/obj/item/tarot_generator/examine(mob/user)
- . = ..()
- . += "Alt-Shift-Click to destroy all cards it has produced."
- . += "It has [length(our_card_list)] card\s in the world right now."
- if(!COOLDOWN_FINISHED(src, card_cooldown))
- . += "You may draw another card again in [round(COOLDOWN_TIMELEFT(src, card_cooldown) / 10)] second\s."
-
-/obj/item/tarot_generator/AltShiftClick(mob/user)
- for(var/obj/item/magic_tarot_card/MTC in our_card_list)
- MTC.dust()
- to_chat(user, "You dispell the cards [src] had created.")
-
-// Booster packs filled with 3, 5, or 7 playing cards! Used by the wizard space ruin, or rarely in lavaland tendril chests.
-/obj/item/tarot_card_pack
- name = "\improper Enchanted Arcana Pack"
- desc = "A pack of 3 Enchanted tarot cards. Collect them all!"
- icon = 'icons/obj/playing_cards.dmi'
- icon_state = "pack"
- ///How many cards in a pack. 3 in base, 5 in jumbo, 7 in mega
- var/cards = 3
-
-/obj/item/tarot_card_pack/attack_self__legacy__attackchain(mob/user)
- user.visible_message("[user] tears open [src].", \
- "You tear open [src]!")
- playsound(loc, 'sound/items/poster_ripped.ogg', 50, TRUE)
- for(var/i in 1 to cards)
- new /obj/item/magic_tarot_card(get_turf(src))
- qdel(src)
-
-/obj/item/tarot_card_pack/jumbo
- name = "\improper Jumbo Arcana Pack"
- desc = "A Jumbo card pack from your friend Jimbo!"
- icon_state = "jumbopack"
- cards = 5
-
-/obj/item/tarot_card_pack/mega
- name = "\improper MEGA Arcana Pack"
- desc = "Sadly, you won't find a Joker for an angel room, or a Soul card in here either."
- icon_state = "megapack"
- cards = 7
-
-// Blank tarot cards. Made by the cult, however also good for space ruins potentially, where one feels a card pack would be too much?
-/obj/item/blank_tarot_card
- name = "blank tarot card"
- desc = "A blank tarot card."
- icon = 'icons/obj/playing_cards.dmi'
- icon_state = "tarot_blank"
- w_class = WEIGHT_CLASS_TINY
- throw_speed = 3
- throw_range = 10
- throwforce = 0
- force = 0
- resistance_flags = FLAMMABLE
- /// If a person can choose what the card produces. No cost if they can choose.
- var/let_people_choose = FALSE
-
-/obj/item/blank_tarot_card/examine(mob/user)
- . = ..()
- if(!let_people_choose)
- . += "With a bit of Ink, a work of art could be created. Will you provide your Ink?"
- else
- . += "We have the Ink... Could you provide your Vision instead?"
-
-/obj/item/blank_tarot_card/attack_self__legacy__attackchain(mob/user)
- if(!ishuman(user))
- return
- if(!let_people_choose)
- var/mob/living/carbon/human/H = user
- if(H.dna && (NO_BLOOD in H.dna.species.species_traits))
- to_chat(user, "No blood to provide?... Then no Ink for the art...")
- return
- if(H.blood_volume <= 100) //Shouldn't happen, they should be dead, but failsafe. Not bleeding as then they could recover the blood with blood rites
- return
- H.blood_volume -= 100
- H.drop_item()
- var/obj/item/magic_tarot_card/MTC = new /obj/item/magic_tarot_card(get_turf(src))
- user.put_in_hands(MTC)
- to_chat(user, "Your blood flows into [src]... And your Ink makes a work of art! [MTC.name]... [MTC.card_desc]") //No period on purpose.
- qdel(src)
- return
- var/tarot_type
- var/tarot_name
- var/list/card_by_name = list()
- for(var/T in subtypesof(/datum/tarot) - /datum/tarot/reversed)
- var/datum/tarot/temp = T
- card_by_name[temp.name] = T
-
- tarot_name = tgui_input_list(user, "Choose the Work of Art to create.", "Art Creation", card_by_name)
- tarot_type = card_by_name[tarot_name]
- if(tarot_type)
- user.drop_item()
- var/obj/item/magic_tarot_card/MTC = new /obj/item/magic_tarot_card(get_turf(src), null, tarot_type)
- user.put_in_hands(MTC)
- to_chat(user, "You put your Vision into [src], and your Vision makes a work of Art! [MTC.name]... [MTC.card_desc]") //No period on purpose.
- qdel(src)
-
-/obj/item/blank_tarot_card/choose //For admins mainly, to spawn a specific tarot card. Not recommended for ruins.
- let_people_choose = TRUE
-
-/obj/item/magic_tarot_card
- name = "XXII - The Unknown"
- desc = "A beautiful tarot card. However, it feels like... more?"
- icon = 'icons/obj/playing_cards.dmi'
- icon_state = "tarot_the_unknown"
- w_class = WEIGHT_CLASS_TINY
- throw_speed = 3
- throw_range = 10
- throwforce = 0
- force = 0
- resistance_flags = FLAMMABLE
- /// The deck that created us. Notifies it we have been deleted on use.
- var/obj/item/tarot_generator/creator_deck
- /// Our magic tarot card datum that lets the tarot card do stuff on use, or hitting someone
- var/datum/tarot/our_tarot
- /// Our fancy description given to use by the tarot datum.
- var/card_desc = "Untold answers... wait what? This is a bug, report this as an issue on github!"
- /// Is the card face down? Shows the card back, hides the examine / name.
- var/face_down = FALSE
- /// Will this card automatically disappear if thrown at a non-mob?
- var/needs_mob_target = TRUE
- /// Has the card been activated? If it has, don't activate it again
- var/has_been_activated = FALSE
-
-/obj/item/magic_tarot_card/Initialize(mapload, obj/item/tarot_generator/source, datum/tarot/chosen_tarot)
- . = ..()
- if(source)
- creator_deck = source
- if(chosen_tarot)
- our_tarot = new chosen_tarot
- if(!istype(our_tarot))
- var/tarotpath = pick(subtypesof(/datum/tarot) - /datum/tarot/reversed)
- our_tarot = new tarotpath
- name = our_tarot.name
- card_desc = our_tarot.desc
- icon_state = "tarot_[our_tarot.card_icon]"
-
-/obj/item/magic_tarot_card/Destroy()
- if(creator_deck)
- creator_deck.our_card_list -= src
- return ..()
-
-/obj/item/magic_tarot_card/examine(mob/user)
- . = ..()
- if(!face_down)
- . += "[card_desc]"
- . += "Alt-Shift-Click to flip the card over."
-
-/obj/item/magic_tarot_card/examine_more(mob/user)
- . = ..()
- if(!face_down)
- . += "[src] [our_tarot.extended_desc]"
-
-/obj/item/magic_tarot_card/attack_self__legacy__attackchain(mob/user)
- poof()
- if(has_been_activated)
- return
- if(face_down)
- flip()
- if(our_tarot)
- user.drop_item()
- pre_activate(user, user)
- return
- qdel(src)
-
-/obj/item/magic_tarot_card/throw_at(atom/target, range, speed, mob/thrower, spin, diagonals_first, datum/callback/callback, force, dodgeable)
- if(face_down)
- flip()
- . = ..()
-
-/obj/item/magic_tarot_card/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum)
- . = ..()
- if(needs_mob_target && !isliving(hit_atom))
- return
- poof()
- if(has_been_activated)
- return
- if(isliving(hit_atom) && our_tarot)
- pre_activate(hit_atom, locateUID(throwingdatum.thrower_uid))
- return
- qdel(src)
-
-/obj/item/magic_tarot_card/AltShiftClick(mob/user)
- flip()
-
-/obj/item/magic_tarot_card/proc/flip()
- if(!face_down)
- icon_state = "cardback[our_tarot.reversed ? "?" : ""]"
- name = "Enchanted tarot card"
- face_down = TRUE
- else
- name = our_tarot.name
- icon_state = "tarot_[our_tarot.card_icon]"
- face_down = FALSE
-
-/obj/item/magic_tarot_card/proc/poof()
- new /obj/effect/temp_visual/revenant(get_turf(src))
-
-/obj/item/magic_tarot_card/proc/dust()
- visible_message("[src] disintegrates into dust!")
- new /obj/effect/temp_visual/revenant(get_turf(src))
- qdel(src)
-
-/obj/item/magic_tarot_card/proc/pre_activate(mob/user, atom/movable/thrower)
- if(user != thrower) //Ignore antimagic stuff if the user is the thrower (aka self activation)
- if(user.can_block_magic(our_tarot.antimagic_flags, 1))
- visible_message("[src] burns up in a flash on contact with [user]!")
- qdel(src)
- return
- has_been_activated = TRUE
- forceMove(user)
- var/obj/effect/temp_visual/card_preview/tarot/draft = new(user, "tarot_[our_tarot.card_icon]")
- user.vis_contents += draft
- user.visible_message("[user] holds up [src]!")
- addtimer(CALLBACK(our_tarot, TYPE_PROC_REF(/datum/tarot, activate), user), 0.5 SECONDS)
- if(ismob(thrower) && our_tarot)
- add_attack_logs(thrower, user, "[thrower] has activated [our_tarot.name] on [user]", ATKLOG_FEW)
- QDEL_IN(src, 0.6 SECONDS)
-
-/obj/effect/temp_visual/card_preview
- name = "a card"
- icon = 'icons/obj/playing_cards.dmi'
- icon_state = "tarot_the_unknown"
- pixel_y = 20
- duration = 1.5 SECONDS
-
-/obj/effect/temp_visual/card_preview/Initialize(mapload, new_icon_state)
- . = ..()
- if(new_icon_state)
- icon_state = new_icon_state
-
- flourish()
-
-/obj/effect/temp_visual/card_preview/proc/flourish()
- var/new_filter = isnull(get_filter("ray"))
- ray_filter_helper(1, 40, "#fcf3dc", 6, 20)
- if(new_filter)
- animate(get_filter("ray"), alpha = 0, offset = 10, time = duration, loop = -1)
- animate(offset = 0, time = duration)
-
-/obj/effect/temp_visual/card_preview/tarot
- name = "a tarot card"
- icon = 'icons/obj/playing_cards.dmi'
- icon_state = "tarot_the_unknown"
- pixel_y = 20
- duration = 1.5 SECONDS
-
-/obj/effect/temp_visual/card_preview/tarot/flourish()
- var/new_filter = isnull(get_filter("ray"))
- ray_filter_helper(1, 40,"#fcf3dc", 6, 20)
- if(new_filter)
- animate(get_filter("ray"), alpha = 0, offset = 10, time = duration, loop = -1)
- animate(offset = 0, time = duration)
-
-/datum/tarot
- /// Name used for the card
- var/name = "XXII - The Unknown."
- /// Desc used for the card description of the card
- var/desc = "Untold answers... wait what? This is a bug, report this as an issue on github!"
- /// Extended desc for the cards. For what they do
- var/extended_desc = "asks you to report this as a bug on GitHub!"
- /// What icon is used for the card?
- var/card_icon = "the_unknown"
- /// Are we reversed? Used for the card back.
- var/reversed = FALSE
- /// What antimagic flags do we have?
- var/antimagic_flags = MAGIC_RESISTANCE
-
-/datum/tarot/proc/activate(mob/living/target)
- stack_trace("A bugged tarot card was spawned and used. Please make an issue report! Type was [src.type]")
-
-/datum/tarot/reversed
- name = "XXII - The Unknown?"
- desc = "Untold answers... wait what? This is a bug, report this as an issue on github! This one was a reversed arcana!"
- card_icon = "the_unknown?"
- reversed = TRUE
-
-/datum/tarot/the_fool
- name = "0 - The Fool"
- desc = "Where journey begins."
- extended_desc = "returns the affected user to the arrival point of this forsaken journey."
- card_icon = "the_fool"
-
-/datum/tarot/the_fool/activate(mob/living/target)
- if(SEND_SIGNAL(target, COMSIG_MOVABLE_TELEPORTING, get_turf(target)) & COMPONENT_BLOCK_TELEPORT)
- return FALSE
- target.forceMove(pick(GLOB.latejoin))
- to_chat(target, "You are abruptly pulled through space!")
-
-/datum/tarot/the_magician
- name = "I - The Magician"
- desc = "May you never miss your goal."
- extended_desc = "makes the user feel extraordinarily badass for a couple of minutes."
- card_icon = "the_magician"
-
-/datum/tarot/the_magician/activate(mob/living/target)
- target.apply_status_effect(STATUS_EFFECT_BADASS)
- to_chat(target, "You feel badass.")
-
-/datum/tarot/the_high_priestess
- name = "II - The High Priestess"
- desc = "Mother is watching you."
- extended_desc = "alerts bubblegum to the user, who will strike them down. The user will receive heavy damage and will be immobilized."
- card_icon = "the_high_priestess"
-
-/datum/tarot/the_high_priestess/activate(mob/living/target)
- new /obj/effect/abstract/bubblegum_rend_helper(get_turf(target), target, 20)
-
-/obj/effect/abstract/bubblegum_rend_helper
- name = "bubblegum_rend_helper"
-
-/obj/effect/abstract/bubblegum_rend_helper/Initialize(mapload, mob/living/owner, damage)
- . = ..()
- INVOKE_ASYNC(src, PROC_REF(rend), owner, damage)
-
-/obj/effect/abstract/bubblegum_rend_helper/proc/rend(mob/living/owner, damage)
- if(!owner)
- for(var/mob/living/L in shuffle(view(9, src)))
- owner = L
- break
- owner.Immobilize(3 SECONDS)
- for(var/i in 1 to 3)
- var/turf/first_turf = get_turf(owner)
- new /obj/effect/decal/cleanable/blood/bubblegum(first_turf)
- if(prob(50))
- new /obj/effect/temp_visual/bubblegum_hands/rightsmack(first_turf)
- else
- new /obj/effect/temp_visual/bubblegum_hands/leftsmack(first_turf)
- sleep(6)
- var/turf/second_turf = get_turf(owner)
- to_chat(owner, "Something huge rends you!")
- playsound(second_turf, 'sound/misc/demon_attack1.ogg', 100, TRUE, -1)
- owner.adjustBruteLoss(damage)
- qdel(src)
-
-/datum/tarot/the_empress
- name = "III - The Empress"
- desc = "May your rage bring power."
- extended_desc = "gives the user a temporary boost of speed. This includes attack speed."
- card_icon = "the_empress"
-
-/datum/tarot/the_empress/activate(mob/living/target)
- if(ishuman(target))
- var/mob/living/carbon/human/H = target
- H.reagents.add_reagent("mephedrone", 4.5)
- H.reagents.add_reagent("mitocholide", 12)
-
-/datum/tarot/the_emperor
- name = "IV - The Emperor"
- desc = "Challenge me!"
- extended_desc = "warps the user to where command commonly resides. Be ready for a fight."
- card_icon = "the_emperor"
-
-/datum/tarot/the_emperor/activate(mob/living/target)
- var/list/L = list()
- for(var/turf/T in get_area_turfs(/area/station/command/bridge))
- if(is_blocked_turf(T))
- continue
- L.Add(T)
-
- if(!length(L))
- to_chat(target, "Huh. No bridge? Well, that sucks.")
- return
- if(SEND_SIGNAL(target, COMSIG_MOVABLE_TELEPORTING, get_turf(target)) & COMPONENT_BLOCK_TELEPORT)
- return FALSE
- target.forceMove(pick(L))
- to_chat(target, "You are abruptly pulled through space!")
-
-/datum/tarot/the_hierophant
- name = "V - The Hierophant"
- desc = "Two prayers for the lost."
- extended_desc = "enchants the user's suit with magic that's strong enough to negate three attacks."
- card_icon = "the_hierophant"
-
-/datum/tarot/the_hierophant/activate(mob/living/target)
- if(!ishuman(target))
- return
- var/mob/living/carbon/human/H = target
- if(!H.wear_suit)
- return
- H.wear_suit.setup_hierophant_shielding()
- H.update_appearance(UPDATE_ICON)
-
-/datum/tarot/the_lovers
- name = "VI - The Lovers"
- desc = "May you prosper and be in good health."
- extended_desc = "will restore the overall health of the user."
- card_icon = "the_lovers"
-
-/datum/tarot/the_lovers/activate(mob/living/target)
- if(ishuman(target))
- var/mob/living/carbon/human/H = target
- H.adjustBruteLoss(-40, robotic = TRUE)
- H.adjustFireLoss(-40, robotic = TRUE)
- H.blood_volume = min(H.blood_volume + 100, BLOOD_VOLUME_NORMAL)
- else
- target.adjustBruteLoss(-40)
- target.adjustFireLoss(-40)
- target.adjustOxyLoss(-40)
- target.adjustToxLoss(-40)
-
-/datum/tarot/the_chariot
- name = "VII - The Chariot"
- desc = "May nothing stand before you."
- extended_desc = "imbues the user with immense power and speed, rendering them practically immortal for 10 seconds, at the cost of being unable to harm another living thing."
- card_icon = "the_chariot"
-
-/datum/tarot/the_chariot/activate(mob/living/target)
- target.apply_status_effect(STATUS_EFFECT_BLOOD_RUSH)
- target.apply_status_effect(STATUS_EFFECT_BLOODDRUNK_CHARIOT)
-
-/datum/tarot/justice
- name = "VIII - Justice"
- desc = "May your future become balanced."
- extended_desc = "grants the user a medical first aid kit, a magical key that can open a single door, and 100 credits."
- card_icon = "justice"
-
-/datum/tarot/justice/activate(mob/living/target)
- var/turf/target_turf = get_turf(target)
- new /obj/item/storage/firstaid/regular(target_turf)
- new /obj/item/grenade/chem_grenade/waterpotassium(target_turf)
- new /obj/item/card/emag/magic_key(target_turf)
- new /obj/item/stack/spacecash/c100(target_turf)
-
-/datum/tarot/the_hermit
- name = "IX - The Hermit"
- desc = "May you see what life has to offer."
- extended_desc = "teleports the user to a random vending machine within the station."
- card_icon = "the_hermit"
-
-/datum/tarot/the_hermit/activate(mob/living/target)
- var/list/viable_vendors = list()
- for(var/obj/machinery/economy/vending/candidate in GLOB.machines)
- if(!is_station_level(candidate.z))
- continue
- viable_vendors += candidate
-
- if(!length(viable_vendors))
- to_chat(target, "No vending machines? Well, with luck cargo will have something to offer. If you go there yourself.")
- return
- if(SEND_SIGNAL(target, COMSIG_MOVABLE_TELEPORTING, get_turf(target)) & COMPONENT_BLOCK_TELEPORT)
- return FALSE
- target.forceMove(get_turf(pick(viable_vendors)))
- to_chat(target, "You are abruptly pulled through space!")
-
-/datum/tarot/wheel_of_fortune
- name = "X - Wheel of Fortune"
- desc = "Spin the wheel of destiny."
- extended_desc = "summons a random vending machine."
- card_icon = "wheel_of_fortune"
-
-/datum/tarot/wheel_of_fortune/activate(mob/living/target)
- var/list/static/bad_vendors = typesof(/obj/machinery/economy/vending/liberationstation)\
- + typesof(/obj/machinery/economy/vending/toyliberationstation)\
- + typesof(/obj/machinery/economy/vending/wallmed) // Future proofing in case we add more subtypes of disallowed vendors
- var/turf/target_turf = get_turf(target)
- var/vendorpath = pick(subtypesof(/obj/machinery/economy/vending) - bad_vendors)
- new vendorpath(target_turf)
-
-/datum/tarot/strength
- name = "XI - Strength"
- desc = "May your power bring rage."
- extended_desc = "grants the user strength beyond belief, but renders them unable to handle ranged weapons."
- card_icon = "strength"
-
-/datum/tarot/strength/activate(mob/living/target)
- target.apply_status_effect(STATUS_EFFECT_VAMPIRE_GLADIATOR)
- target.apply_status_effect(STATUS_EFFECT_BLOOD_SWELL)
-
-/datum/tarot/the_hanged_man
- name = "XII - The Hanged Man"
- desc = "May you find enlightenment."
- extended_desc = "allows the user to fly for a minute."
- card_icon = "the_hanged_man"
-
-/datum/tarot/the_hanged_man/activate(mob/living/target)
- if(HAS_TRAIT(target, TRAIT_FLYING))
- return
- ADD_TRAIT(target, TRAIT_FLYING, "tarot")
- addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(callback_remove_trait), target, TRAIT_FLYING, "tarot"), 60 SECONDS)
-
-/datum/tarot/death
- name = "XIII - Death"
- desc = "Lay waste to all that oppose you."
- extended_desc = "deals damage to all those the user can see. Aside from themselves, of course."
- card_icon = "death"
-
-/datum/tarot/death/activate(mob/living/target)
- for(var/mob/living/L in oview(9, target))
- L.adjustBruteLoss(20)
- L.adjustFireLoss(20)
-
-/datum/tarot/temperance
- name = "XIV - Temperance"
- desc = "May you be pure in heart."
- extended_desc = "cures all ailments the user has. Also reinvigorates their organs."
- card_icon = "temperance"
-
-/datum/tarot/temperance/activate(mob/living/target)
- if(!ishuman(target))
- return
- var/mob/living/carbon/human/H = target
- var/obj/item/organ/internal/body_egg/egg = H.get_int_organ(/obj/item/organ/internal/body_egg)
- if(egg)
- egg.remove(H)
- H.vomit()
- egg.forceMove(get_turf(H))
- H.reagents.add_reagent("mutadone", 1)
- for(var/obj/item/organ/internal/I in H.internal_organs)
- I.heal_internal_damage(60)
- H.apply_status_effect(STATUS_EFFECT_PANACEA)
- for(var/thing in H.viruses)
- var/datum/disease/D = thing
- if(D.severity == NONTHREAT)
- continue
- D.cure()
-
-/datum/tarot/the_devil
- name = "XV - The Devil"
- desc = "Revel in the power of darkness."
- extended_desc = "steals the life-force of everyone around the user."
- card_icon = "the_devil"
-
-/datum/tarot/the_devil/activate(mob/living/target)
- target.apply_status_effect(STATUS_EFFECT_SHADOW_MEND_DEVIL)
-
-/datum/tarot/the_tower
- name = "XVI - The Tower"
- desc = "Destruction brings creation."
- extended_desc = "summons a self-replicating bomb."
- card_icon = "the_tower"
-
-/datum/tarot/the_tower/activate(mob/living/target)
- var/obj/item/grenade/clusterbuster/ied/bakoom = new(get_turf(target))
- var/turf/bombturf = get_turf(target)
- target.investigate_log("[key_name(target)] has been activated (either thrown at or used) on [target] at [bombturf.x],[bombturf.y],[bombturf.z]", INVESTIGATE_BOMB) // Yes, this is an atom proc. Suffering
- bakoom.prime()
-
-/// I'm sorry matt, this is very funny.
-/datum/tarot/the_stars
- name = "XVII - The Stars"
- desc = "May you find what you desire."
- extended_desc = "teleports the user to the station's evidence room, and opens a single locker within."
- card_icon = "the_stars"
-
-/datum/tarot/the_stars/activate(mob/living/target)
- var/list/L = list()
- for(var/turf/T in get_area_turfs(/area/station/security/evidence))
- if(is_blocked_turf(T))
- continue
- L.Add(T)
-
- if(!length(L))
- to_chat(target, "Huh. No evidence? Well, that means they can't charge you with a crime, right?")
- return
- if(SEND_SIGNAL(target, COMSIG_MOVABLE_TELEPORTING, get_turf(target)) & COMPONENT_BLOCK_TELEPORT)
- return FALSE
- target.forceMove(pick(L))
- to_chat(target, "You are abruptly pulled through space!")
- for(var/obj/structure/closet/C in shuffle(view(9, target)))
- if(istype(C, /obj/structure/closet/secure_closet))
- var/obj/structure/closet/secure_closet/SC = C
- SC.locked = FALSE
- C.open()
- break //Only open one locker
-
-/datum/tarot/the_moon
- name = "XVIII - The Moon"
- desc = "May you find all you have lost."
- extended_desc = "teleports the user to a random place of interest, starting with the sector the user is in first."
- card_icon = "the_moon"
-
-/datum/tarot/the_moon/activate(mob/living/target)
- var/list/funny_ruin_list = list()
- var/turf/target_turf = get_turf(target)
- if(SEND_SIGNAL(target, COMSIG_MOVABLE_TELEPORTING, get_turf(target)) & COMPONENT_BLOCK_TELEPORT)
- return FALSE
- for(var/I in GLOB.ruin_landmarks)
- var/obj/effect/landmark/ruin/ruin_landmark = I
- if(ruin_landmark.z == target_turf.z)
- funny_ruin_list += ruin_landmark
-
- if(length(funny_ruin_list))
- var/turf/T = get_turf(pick(funny_ruin_list))
- target.forceMove(T)
- to_chat(target, "You are abruptly pulled through space!")
- T.ChangeTurf(/turf/simulated/floor/plating) //we give them plating so they are not trapped in a wall, and a pickaxe to avoid being trapped in a wall
- new /obj/item/pickaxe/emergency(T)
- target.update_parallax_contents()
- return
- //We did not find a ruin on the same level. Well. I hope you have a space suit, but we'll go space ruins as they are mostly sorta kinda safer.
- for(var/I in GLOB.ruin_landmarks)
- var/obj/effect/landmark/ruin/ruin_landmark = I
- if(!is_mining_level(ruin_landmark.z))
- funny_ruin_list += ruin_landmark
-
- if(!length(funny_ruin_list))
- to_chat(target, "Huh. No space ruins? Well, this card is RUINED!")
-
- var/turf/T = get_turf(pick(funny_ruin_list))
- target.forceMove(T)
- to_chat(target, "You are abruptly pulled through space!")
- T.ChangeTurf(/turf/simulated/floor/plating) //we give them plating so they are not trapped in a wall, and a pickaxe to avoid being trapped in a wall
- new /obj/item/pickaxe/emergency(T)
- target.update_parallax_contents()
- return
-
-/datum/tarot/the_sun
- name = "XIX - The Sun"
- desc = "May the light heal and enlighten you."
- extended_desc = "fully rejuvenates the user back to their peak strength."
- card_icon = "the_sun"
-
-/datum/tarot/the_sun/activate(mob/living/target)
- target.revive()
-
-/datum/tarot/judgement
- name = "XX - Judgement"
- desc = "Judge lest ye be judged."
- extended_desc = "alerts the denizens of the afterlife to the user's existence. Prepare to be judged."
- card_icon = "judgement"
-
-/datum/tarot/judgement/activate(mob/living/target)
- notify_ghosts("[target] has used a judgment card. Judge them. Or not, up to you.", enter_link = "(Click to judge)", source = target, action = NOTIFY_FOLLOW)
-
-/datum/tarot/the_world
- name = "XXI - The World"
- desc = "Open your eyes and see."
- extended_desc = "bellows out smoke and grants the user full x-ray vision for two minutes."
- card_icon = "the_world"
-
-/datum/tarot/the_world/activate(mob/living/target)
- var/datum/effect_system/smoke_spread/bad/smoke = new()
- smoke.set_up(10, FALSE, target)
- smoke.start()
- target.apply_status_effect(STATUS_EFFECT_XRAY)
-
-////////////////////////////////
-////////REVERSED ARCANA/////////
-////////////////////////////////
-
-/datum/tarot/reversed/the_fool
- name = "0 - The Fool?"
- desc = "Let go and move on."
- extended_desc = "removes all items from the user, leaving them completely naked."
- card_icon = "the_fool?"
-
-/datum/tarot/reversed/the_fool/activate(mob/living/target)
- if(!ishuman(target))
- return
- var/mob/living/carbon/human/H = target
- for(var/obj/item/I in H)
- if(istype(I, /obj/item/bio_chip))
- continue
- H.drop_item_to_ground(I)
-
-/datum/tarot/reversed/the_magician
- name = "I - The Magician?"
- desc = "May no harm come to you."
- extended_desc = "will repulse everything away from the user."
- card_icon = "the_magician?"
-
-/datum/tarot/reversed/the_magician/activate(mob/living/target)
- var/list/thrown_atoms = list()
- var/sparkle_path = /obj/effect/temp_visual/gravpush
- for(var/turf/T in range(5, target)) //Done this way so things don't get thrown all around hilariously.
- for(var/atom/movable/AM in T)
- if(ismob(AM))
- var/mob/victim_mob = AM
- if(victim_mob.can_block_magic(antimagic_flags))
- continue
- thrown_atoms += AM
- for(var/atom/movable/AM as anything in thrown_atoms)
- if(AM == target || AM.anchored || (ismob(AM) && !isliving(AM)))
- continue
-
- var/throw_target = get_edge_target_turf(target, get_dir(target, get_step_away(AM, target)))
- var/dist_from_user = get_dist(target, AM)
- if(dist_from_user == 0)
- if(isliving(AM))
- var/mob/living/M = AM
- M.Weaken(6 SECONDS)
- M.adjustBruteLoss(10)
- to_chat(M, "You're slammed into the floor by [name]!")
- add_attack_logs(target, M, "[M] was thrown by [target]'s [name]", ATKLOG_ALMOSTALL)
- else
- new sparkle_path(get_turf(AM), get_dir(target, AM))
- if(isliving(AM))
- var/mob/living/M = AM
- to_chat(M, "You're thrown back by [name]!")
- add_attack_logs(target, M, "[M] was thrown by [target]'s [name]", ATKLOG_ALMOSTALL)
- INVOKE_ASYNC(AM, TYPE_PROC_REF(/atom/movable, throw_at), throw_target, ((clamp((3 - (clamp(dist_from_user - 2, 0, dist_from_user))), 3, 3))), 1) //So stuff gets tossed around at the same time.
-
-/datum/tarot/reversed/the_high_priestess
- name = "II - The High Priestess?"
- desc = "Run."
- extended_desc = "summons Bubblegum to tear portals open around the user that will grab and damage everyone nearby."
- card_icon = "the_high_priestess?"
-
-/datum/tarot/reversed/the_high_priestess/activate(mob/living/target)
- target.visible_message("WHO DARES TO TRY TO USE MY POWER IN A CARD?")
- target.apply_status_effect(STATUS_EFFECT_REVERSED_HIGH_PRIESTESS)
-
-/datum/tarot/reversed/the_empress
- name = "III - The Empress?"
- desc = "May your love bring protection."
- extended_desc = "pacifies everyone in range, except for the user, for 40 seconds."
- card_icon = "the_empress?"
-
-/datum/tarot/reversed/the_empress/activate(mob/living/target)
- for(var/mob/living/L in oview(9, target))
- if(L.can_block_magic(antimagic_flags))
- to_chat(L, "You feel calm for a second, but it quickly passes.")
- continue
- L.apply_status_effect(STATUS_EFFECT_PACIFIED)
-
-/datum/tarot/reversed/the_emperor
- name = "IV - The Emperor?"
- desc = "May you find a worthy opponent."
- extended_desc = "teleports the user to a random head of staff."
- card_icon = "the_emperor?"
-
-/datum/tarot/reversed/the_emperor/activate(mob/living/target)
- var/list/L = list()
- var/list/heads = SSticker.mode.get_all_heads()
- for(var/datum/mind/head in heads)
- if(ishuman(head.current))
- L.Add(head.current)
-
- if(!length(L))
- to_chat(target, "Huh. No command members? I hope you didn't kill them all already...")
- return
- if(SEND_SIGNAL(target, COMSIG_MOVABLE_TELEPORTING, get_turf(target)) & COMPONENT_BLOCK_TELEPORT)
- return FALSE
- target.forceMove(get_turf(pick(L)))
- to_chat(target, "You are abruptly pulled through space!")
-
-/datum/tarot/reversed/the_hierophant
- name = "V - The Hierophant?"
- desc = "Two prayers for the forgotten."
- extended_desc = "makes the Hierophant attack two random mobs in range."
- card_icon = "the_hierophant?"
-
-/datum/tarot/reversed/the_hierophant/activate(mob/living/target)
- var/active_chasers = 0
- for(var/mob/living/M in shuffle(orange(7, target)))
- if(M.stat == DEAD) //Let us not have dead mobs be used to make a disco inferno.
- continue
- if(M.can_block_magic(antimagic_flags)) //Be spared!
- continue
- if(active_chasers >= 2)
- return
- var/obj/effect/temp_visual/hierophant/chaser/C = new(get_turf(target), target, M, 1, FALSE)
- C.moving = 2
- C.standard_moving_before_recalc = 2
- C.moving_dir = text2dir(pick("NORTH", "SOUTH", "EAST", "WEST"))
- active_chasers++
-
-/datum/tarot/reversed/the_lovers
- name = "VI - The Lovers?"
- desc = "May your heart shatter to pieces."
- extended_desc = "causes the user of this card to experience true heartbreak - leaving their chest broken and battered."
- card_icon = "the_lovers?"
-
-/datum/tarot/reversed/the_lovers/activate(mob/living/target)
- if(!ishuman(target))
- return
- var/mob/living/carbon/human/H = target
- H.apply_damage(20, BRUTE, BODY_ZONE_CHEST)
- H.bleed(120)
- var/obj/item/organ/external/chest = H.get_organ(BODY_ZONE_CHEST)
- chest.fracture()
- var/datum/organ/heart/datum_heart = H.get_int_organ_datum(ORGAN_DATUM_HEART)
- var/obj/item/organ/internal/our_heart = datum_heart.linked_organ
- our_heart.receive_damage(20, TRUE)
-
-/datum/tarot/reversed/the_chariot
- name = "VII - The Chariot?"
- desc = "May nothing walk past you."
- extended_desc = "will petrify the user for two minutes, rendering them completely indestructible."
- card_icon = "the_chariot?"
-
-/datum/tarot/reversed/the_chariot/activate(mob/living/target)
- target.Stun(4 SECONDS)
- new /obj/structure/closet/statue/indestructible(get_turf(target), target)
-
-/datum/tarot/reversed/justice
- name = "VIII - Justice?"
- desc = "May your sins come back to torment you."
- extended_desc = "creates a random orderable crate. This can include crates Supply would otherwise not have access to at the time."
- card_icon = "justice?"
-
-/datum/tarot/reversed/justice/activate(mob/living/target)
- var/list/static/ignored_supply_pack_types = list(
- /datum/supply_packs/abstract,
- /datum/supply_packs/abstract/shuttle
- )
- var/chosen = pick(SSeconomy.supply_packs - ignored_supply_pack_types)
- var/datum/supply_packs/the_pack = new chosen()
- var/spawn_location = get_turf(target)
- var/obj/structure/closet/crate/crate = the_pack.create_package(spawn_location)
- crate.name = "magic [crate.name]"
- qdel(the_pack)
-
-/datum/tarot/reversed/the_hermit
- name = "IX - The Hermit?"
- desc = "May you see the value of all things in life."
- extended_desc = "will sell all loose guns, grenades, batons, and armor around the user, transforming them directly into cash."
- card_icon = "the_hermit?"
-
-/datum/tarot/reversed/the_hermit/activate(mob/living/target) //Someone can improve this in the future (hopefully comment will not be here in 10 years.)
- for(var/obj/item/I in view(7, target))
- if(istype(I, /obj/item/gun))
- new /obj/item/stack/spacecash/c200(get_turf(I))
- qdel(I)
- continue
- if(istype(I, /obj/item/grenade))
- new /obj/item/stack/spacecash/c50(get_turf(I))
- qdel(I)
- if(istype(I, /obj/item/clothing/suit/armor))
- new /obj/item/stack/spacecash/c100(get_turf(I))
- qdel(I)
- if(istype(I, /obj/item/melee/baton))
- new /obj/item/stack/spacecash/c100(get_turf(I))
- qdel(I)
-
-/datum/tarot/reversed/wheel_of_fortune
- name = "X - Wheel of Fortune?"
- desc = "Throw the dice of fate."
- extended_desc = "forces the user to roll for a powerful magical artifact. The outcome can be highly positive or highly negative; it is up to fate."
- card_icon = "wheel_of_fortune?"
-
-/datum/tarot/reversed/wheel_of_fortune/activate(mob/living/target)
- var/obj/item/dice/d20/fate/one_use/gonna_roll_a_one = new /obj/item/dice/d20/fate/one_use(get_turf(target))
- gonna_roll_a_one.diceroll(target)
-
-/datum/tarot/reversed/strength
- name = "XI - Strength?"
- desc = "May you break their resolve."
- extended_desc = "breaks the minds of those around the user, dealing heavy brain damage, and causing two minutes of hallucinations."
- card_icon = "strength?"
-
-/datum/tarot/reversed/strength/activate(mob/living/target)
- for(var/mob/living/M in oview(9, target))
- M.Hallucinate(2 MINUTES)
- new /obj/effect/hallucination/delusion(get_turf(M), M)
- M.adjustBrainLoss(30)
-
-/datum/tarot/reversed/the_hanged_man
- name = "XII - The Hanged Man?"
- desc = "May your greed know no bounds."
- extended_desc = "forces the user to spin a cursed slot machine."
- card_icon = "the_hanged_man?"
-
-/datum/tarot/reversed/the_hanged_man/activate(mob/living/target)
- var/obj/structure/cursed_slot_machine/pull_the_lever_kronk = new /obj/structure/cursed_slot_machine(get_turf(target))
- if(ishuman(target))
- var/mob/living/carbon/human/WRONG_LEVER = target
- pull_the_lever_kronk.attack_hand(WRONG_LEVER)
-
-/datum/tarot/reversed/death
- name = "XIII - Death?"
- desc = "May life spring forth from the fallen."
- extended_desc = "grants the user a soulstone and a construct to freely use on the dead."
- card_icon = "death?"
-
-/datum/tarot/reversed/death/activate(mob/living/target)
- new /obj/structure/constructshell(get_turf(target))
- new /obj/item/soulstone/anybody(get_turf(target))
-
-/datum/tarot/reversed/temperance
- name = "XIV - Temperance?"
- desc = "May your hunger be satiated."
- extended_desc = "forces the user to eat five pills containing random reagents."
- card_icon = "temperance?"
-
-/datum/tarot/reversed/temperance/activate(mob/living/target)
- if(!ishuman(target))
- return
- var/mob/living/carbon/human/H = target
- for(var/i in 1 to 5)
- var/datum/reagents/R = new /datum/reagents(10)
- R.add_reagent(get_unrestricted_random_reagent_id(), 10)
- R.reaction(H, REAGENT_INGEST)
- R.trans_to(H, 10)
- target.visible_message("[target] consumes 5 pills rapidly!")
-
-/datum/tarot/reversed/the_devil
- name = "XV - The Devil?"
- desc = "Bask in the light of your mercy."
- extended_desc = "summons a primed cluster flashbang at the user's feet."
- card_icon = "the_devil?"
-
-/datum/tarot/reversed/the_devil/activate(mob/living/target)
- var/obj/item/grenade/clusterbuster/i_hate_nians = new(get_turf(target))
- i_hate_nians.prime()
-
-/datum/tarot/reversed/the_tower
- name = "XVI - The Tower?"
- desc = "Creation brings destruction."
- extended_desc = "will create large stone walls that erupt from the ground around the user."
- card_icon = "the_tower?"
-
-/datum/tarot/reversed/the_tower/activate(mob/living/target)
- for(var/turf/T in RANGE_TURFS(9, target))
- if(locate(/mob/living) in T)
- continue
- if(istype(T, /turf/simulated/wall/indestructible))
- continue
- if(prob(66))
- continue
- T.ChangeTurf(/turf/simulated/mineral/random/labormineral)
-
-/datum/tarot/reversed/the_stars
- name = "XVII - The Stars?"
- desc = "May your loss bring fortune."
- extended_desc = "will cause a large amount of genetic decomposition to the user, as well as hurting a limb. However, it will reward the user with two additional cards."
- card_icon = "the_stars?"
-
-/datum/tarot/reversed/the_stars/activate(mob/living/target) //Heavy clone damage hit, but gain 2 cards. Not teathered to the card producer. Could lead to card stacking, but would require the sun to fix easily
- if(!ishuman(target))
- return
- var/mob/living/carbon/human/H = target
- H.adjustCloneLoss(50)
- for(var/obj/item/organ/external/E in shuffle(H.bodyparts))
- switch(rand(1,3))
- if(1)
- E.fracture()
- if(2)
- E.cause_internal_bleeding()
- if(3)
- E.cause_burn_wound()
- break // I forgot the break the first time. Very funny.
-
- H.drop_l_hand()
- H.drop_r_hand()
- var/obj/item/magic_tarot_card/MTC = new /obj/item/magic_tarot_card(get_turf(src))
- var/obj/item/magic_tarot_card/MPC = new /obj/item/magic_tarot_card(get_turf(src))
- H.put_in_hands(MTC)
- H.put_in_hands(MPC)
-
-/datum/tarot/reversed/the_moon
- name = "XVIII - The Moon?"
- desc = "May you remember lost memories."
- extended_desc = "will reveal the memories of everyone in range to the user."
- card_icon = "the_moon?"
- antimagic_flags = MAGIC_RESISTANCE|MAGIC_RESISTANCE_MIND
-
-/datum/tarot/reversed/the_moon/activate(mob/living/target)
- for(var/mob/living/L in view(5, target)) //Shorter range as this kinda can give away antagonists, though that is also funny.
- target.mind.show_memory(L, 0) //Safe code? Bank accounts? PDA codes? It's yours my friend, as long as you have enough tarots
-
-/datum/tarot/reversed/the_sun
- name = "XIX - The Sun?"
- desc = "May the darkness swallow all around you."
- extended_desc = "makes the user emit darkness, freezing anyone nearby. They will also become nearsighted for the duration, however."
- card_icon = "the_sun?"
-
-/datum/tarot/reversed/the_sun/activate(mob/living/target)
- target.apply_status_effect(STATUS_EFFECT_REVERSED_SUN)
-
-/datum/tarot/reversed/judgement
- name = "XX - Judgement?"
- desc = "May you redeem those found wanting." //Who wants more, but ghosts for something interesting
- extended_desc = "nudges the future events of this shift to be more... interesting."
- card_icon = "judgement?"
-
-/datum/tarot/reversed/judgement/activate(mob/living/target)
- var/datum/event_container/EC = SSevents.event_containers[EVENT_LEVEL_MODERATE]
- var/decrease = 5 MINUTES
- EC.next_event_time -= decrease
- log_and_message_admins("decreased timer for [GLOB.severity_to_string[EC.severity]] events by 5 minutes by use of a [src].")
-
-/datum/tarot/reversed/the_world
- name = "XXI - The World?"
- desc = "Step into the abyss."
- extended_desc = "teleports the user to the mining outpost."
- card_icon = "the_world?"
-
-/datum/tarot/reversed/the_world/activate(mob/living/target)
- var/list/L = list()
- for(var/turf/T in get_area_turfs(/area/mine/outpost)) //Lavaland is the abyss, but also too hot to send people too. Mining base should be fair!
- if(is_blocked_turf(T))
- continue
- L.Add(T)
-
- if(!length(L))
- to_chat(target, "Hmm. No base? A miner issue.")
- return
- if(SEND_SIGNAL(target, COMSIG_MOVABLE_TELEPORTING, get_turf(target)) & COMPONENT_BLOCK_TELEPORT)
- return FALSE
- target.forceMove(pick(L))
- to_chat(target, "You are abruptly pulled through space!")
diff --git a/code/game/gamemodes/wizard/rightandwrong.dm b/code/game/gamemodes/wizard/rightandwrong.dm
deleted file mode 100644
index 4c6ffbd43a064..0000000000000
--- a/code/game/gamemodes/wizard/rightandwrong.dm
+++ /dev/null
@@ -1,186 +0,0 @@
-//In this file: Summon Magic/Summon Guns/Summon Events
-
-// 1 in 50 chance of getting something really special.
-#define SPECIALIST_MAGIC_PROB 2
-
-GLOBAL_LIST_INIT(summoned_guns, list(
- /obj/item/gun/energy/disabler,
- /obj/item/gun/energy/gun,
- /obj/item/gun/energy/gun/advtaser,
- /obj/item/gun/energy/laser,
- /obj/item/gun/projectile/revolver,
- /obj/item/gun/energy/detective,
- /obj/item/gun/projectile/automatic/pistol/deagle/camo,
- /obj/item/gun/projectile/automatic/gyropistol,
- /obj/item/gun/energy/pulse,
- /obj/item/gun/projectile/automatic/pistol,
- /obj/item/gun/projectile/revolver/doublebarrel,
- /obj/item/gun/projectile/shotgun,
- /obj/item/gun/projectile/shotgun/automatic/combat,
- /obj/item/gun/projectile/automatic/ar,
- /obj/item/gun/projectile/revolver/mateba,
- /obj/item/gun/projectile/shotgun/boltaction,
- /obj/item/gun/projectile/automatic/mini_uzi,
- /obj/item/gun/energy/lasercannon,
- /obj/item/gun/energy/kinetic_accelerator/crossbow/large,
- /obj/item/gun/energy/gun/nuclear,
- /obj/item/gun/projectile/automatic/proto,
- /obj/item/gun/projectile/automatic/c20r,
- /obj/item/gun/projectile/automatic/l6_saw,
- /obj/item/gun/projectile/automatic/m90,
- /obj/item/gun/energy/alien,
- /obj/item/gun/energy/pulse/carbine,
- /obj/item/gun/energy/decloner,
- /obj/item/gun/energy/mindflayer,
- /obj/item/gun/energy/kinetic_accelerator,
- /obj/item/gun/energy/plasmacutter/adv,
- /obj/item/gun/energy/wormhole_projector,
- /obj/item/gun/projectile/automatic/wt550,
- /obj/item/gun/projectile/automatic/shotgun/bulldog,
- /obj/item/gun/projectile/revolver/grenadelauncher,
- /obj/item/gun/projectile/revolver/golden,
- /obj/item/gun/projectile/automatic/sniper_rifle,
- /obj/item/gun/medbeam,
- /obj/item/gun/energy/laser/scatter,
- /obj/item/gun/projectile/automatic/c20r/toy/riot,
- /obj/item/gun/projectile/shotgun/automatic/dual_tube,
- /obj/item/gun/energy/kinetic_accelerator/experimental, // even with atmos, this thing can get scary
- /obj/item/gun/energy/emitter,
- /obj/item/gun/energy/spikethrower,
- /obj/item/gun/energy/bsg/prebuilt,
- /obj/item/gun/energy/xray,
- /obj/item/gun/energy/plasma_pistol,
- /obj/item/gun/projectile/automatic/pistol/aps, // whyyy is this capitalized
- /obj/item/gun/projectile/revolver/overgrown,
- /obj/item/gun/energy/gun/blueshield/pdw9,
- /obj/item/gun/energy/disabler/silencer,
- /obj/item/gun/energy/lwap,
- /obj/item/gun/energy/arc_revolver,
- /obj/item/gun/projectile/automatic/ak814))
-
-//if you add anything that isn't covered by the typepaths below, add it to summon_magic_objective_types
-GLOBAL_LIST_INIT(summoned_magic, list(
- /obj/item/spellbook/oneuse/fireball,
- /obj/item/spellbook/oneuse/smoke,
- /obj/item/spellbook/oneuse/blind,
- /obj/item/spellbook/oneuse/mindswap,
- /obj/item/spellbook/oneuse/forcewall,
- /obj/item/spellbook/oneuse/knock,
- /obj/item/spellbook/oneuse/horsemask,
- /obj/item/spellbook/oneuse/charge,
- /obj/item/spellbook/oneuse/summonitem,
- /obj/item/gun/magic/wand,
- /obj/item/gun/magic/wand/resurrection,
- /obj/item/gun/magic/wand/teleport,
- /obj/item/gun/magic/wand/door,
- /obj/item/gun/magic/wand/fireball,
- /obj/item/gun/magic/staff/healing,
- /obj/item/gun/magic/staff/door,
- /obj/item/scrying,
- /obj/item/clothing/suit/space/hardsuit/wizard,
- /obj/item/immortality_talisman,
- /obj/item/melee/ghost_sword,
- /obj/item/tarot_card_pack,
- /obj/item/tarot_card_pack/jumbo))
-
-GLOBAL_LIST_INIT(summoned_special_magic, list(
- /obj/item/gun/magic/staff/animate,
- /obj/item/storage/belt/wands/full,
- /obj/item/contract,
- /obj/item/gun/magic/staff/chaos,
- /obj/item/necromantic_stone,
- /obj/item/blood_contract,
- /obj/item/tarot_generator))
-
-//everything above except for single use spellbooks, because they are counted separately (and are for basic bitches anyways)
-GLOBAL_LIST_INIT(summoned_magic_objectives, list(
- /obj/item/contract,
- /obj/item/blood_contract,
- /obj/item/clothing/suit/space/hardsuit/wizard,
- /obj/item/gun/magic,
- /obj/item/immortality_talisman,
- /obj/item/melee/ghost_sword,
- /obj/item/necromantic_stone,
- /obj/item/scrying,
- /obj/item/spellbook,
- /obj/item/storage/belt/wands/full,
- /obj/item/tarot_generator))
-
-// If true, it's the probability of triggering "survivor" antag.
-GLOBAL_VAR_INIT(summon_guns_triggered, FALSE)
-GLOBAL_VAR_INIT(summon_magic_triggered, FALSE)
-
-/proc/give_guns(mob/living/carbon/human/H)
- if(H.stat == DEAD || !(H.client))
- return
- if(H.mind)
- if(iswizard(H) || H.mind.offstation_role)
- return
-
- if(prob(GLOB.summon_guns_triggered) && !(H.mind in SSticker.mode.traitors))
- SSticker.mode.traitors += H.mind
-
- H.mind.add_antag_datum(/datum/antagonist/survivalist/guns)
- H.create_attack_log("was made into a survivalist, and trusts no one!")
- H.create_log(CONVERSION_LOG, "was made into a survivalist")
-
- var/gun_type = pick(GLOB.summoned_guns)
- var/obj/item/gun/G = new gun_type(get_turf(H))
- playsound(get_turf(H),'sound/magic/summon_guns.ogg', 50, TRUE)
-
- var/in_hand = H.put_in_hands(G) // not always successful
-
- to_chat(H, "\A [G] appears [in_hand ? "in your hand" : "at your feet"]!")
-
-/proc/give_magic(mob/living/carbon/human/H)
- if(H.stat == DEAD || !(H.client))
- return
- if(H.mind)
- if(iswizard(H) || H.mind.offstation_role)
- return
-
- if(prob(GLOB.summon_magic_triggered) && !(H.mind in SSticker.mode.traitors) && !jobban_isbanned(H, ROLE_SYNDICATE))
- SSticker.mode.traitors += H.mind
-
- H.mind.add_antag_datum(/datum/antagonist/survivalist/magic)
- H.create_attack_log("was made into a survivalist, and trusts no one!")
- H.create_log(CONVERSION_LOG, "was made into a survivalist")
-
- var/magic_type = pick(GLOB.summoned_magic)
- var/lucky = FALSE
- if(prob(SPECIALIST_MAGIC_PROB))
- magic_type = pick(GLOB.summoned_special_magic)
- lucky = TRUE
-
- var/obj/item/M = new magic_type(get_turf(H))
- playsound(get_turf(H),'sound/magic/summon_magic.ogg', 50, TRUE)
-
- var/in_hand = H.put_in_hands(M)
-
- to_chat(H, "\A [M] appears [in_hand ? "in your hand" : "at your feet"]!")
- if(lucky)
- to_chat(H, "You feel incredibly lucky.")
-
-/proc/rightandwrong(summon_type, mob/user, survivor_probability)
- if(user) //in this case either someone holding a spellbook or a badmin
- to_chat(user, "You summoned [summon_type]!")
- message_admins("[ADMIN_LOOKUPFLW(user)] summoned [summon_type]!")
- log_game("[key_name(user)] summoned [summon_type]!")
-
- if(summon_type == SUMMON_MAGIC)
- GLOB.summon_magic_triggered = survivor_probability
- else if(summon_type == SUMMON_GUNS)
- GLOB.summon_guns_triggered = survivor_probability
- else
- CRASH("Bad summon_type given: [summon_type]")
-
- for(var/mob/living/carbon/human/H in GLOB.player_list)
- var/turf/T = get_turf(H)
- if(T && is_away_level(T.z))
- continue
- if(summon_type == SUMMON_MAGIC)
- give_magic(H)
- else
- give_guns(H)
-
-#undef SPECIALIST_MAGIC_PROB
diff --git a/code/game/gamemodes/wizard/spellbook.dm b/code/game/gamemodes/wizard/spellbook.dm
deleted file mode 100644
index 087734dee174b..0000000000000
--- a/code/game/gamemodes/wizard/spellbook.dm
+++ /dev/null
@@ -1,1164 +0,0 @@
-/datum/spellbook_entry
- var/name = "Entry Name"
- var/is_ragin_restricted = FALSE // FALSE if this is buyable on ragin mages, TRUE if it's not.
- var/spell_type = null
- var/desc = ""
- var/category = "Offensive"
- var/cost = 2
- var/refundable = TRUE
- var/datum/spell/S = null //Since spellbooks can be used by only one person anyway we can track the actual spell
- var/buy_word = "Learn"
- var/limit //used to prevent a spellbook_entry from being bought more than X times with one wizard spellbook
-
-/datum/spellbook_entry/proc/CanBuy(mob/living/carbon/human/user, obj/item/spellbook/book) // Specific circumstances
- if(book.uses < cost || limit == 0)
- return FALSE
- return TRUE
-
-/datum/spellbook_entry/proc/Buy(mob/living/carbon/human/user, obj/item/spellbook/book) //return TRUE on success
- if(!S)
- S = new spell_type()
-
- return LearnSpell(user, book, S)
-
-/datum/spellbook_entry/proc/LearnSpell(mob/living/carbon/human/user, obj/item/spellbook/book, datum/spell/newspell)
- for(var/datum/spell/aspell in user.mind.spell_list)
- if(initial(newspell.name) == initial(aspell.name)) // Not using directly in case it was learned from one spellbook then upgraded in another
- if(aspell.spell_level >= aspell.level_max)
- to_chat(user, "This spell cannot be improved further.")
- return FALSE
- else
- aspell.name = initial(aspell.name)
- aspell.spell_level++
- aspell.cooldown_handler.recharge_duration = round(aspell.base_cooldown - aspell.spell_level * (aspell.base_cooldown - aspell.cooldown_min) / aspell.level_max)
- switch(aspell.spell_level)
- if(1)
- to_chat(user, "You have improved [aspell.name] into Efficient [aspell.name].")
- aspell.name = "Efficient [aspell.name]"
- if(2)
- to_chat(user, "You have further improved [aspell.name] into Quickened [aspell.name].")
- aspell.name = "Quickened [aspell.name]"
- if(3)
- to_chat(user, "You have further improved [aspell.name] into Free [aspell.name].")
- aspell.name = "Free [aspell.name]"
- if(4)
- to_chat(user, "You have further improved [aspell.name] into Instant [aspell.name].")
- aspell.name = "Instant [aspell.name]"
- if(aspell.spell_level >= aspell.level_max)
- to_chat(user, "This spell cannot be strengthened any further.")
- aspell.on_purchase_upgrade()
- return TRUE
- //No same spell found - just learn it
- SSblackbox.record_feedback("tally", "wizard_spell_learned", 1, name)
- user.mind.AddSpell(newspell)
- to_chat(user, "You have learned [newspell.name].")
- return TRUE
-
-/datum/spellbook_entry/proc/CanRefund(mob/living/carbon/human/user, obj/item/spellbook/book)
- if(!refundable)
- return FALSE
- if(!S)
- S = new spell_type()
- for(var/datum/spell/aspell in user.mind.spell_list)
- if(initial(S.name) == initial(aspell.name))
- return TRUE
- return FALSE
-
-/datum/spellbook_entry/proc/Refund(mob/living/carbon/human/user, obj/item/spellbook/book) //return point value or -1 for failure
- if(!istype(get_area(user), /area/wizard_station))
- to_chat(user, "You can only refund spells at the wizard lair.")
- return -1
- if(!S) //This happens when the spell's source is from another spellbook, from loadouts, or adminery, this create a new template temporary spell
- S = new spell_type()
- var/spell_levels = 0
- for(var/datum/spell/aspell in user.mind.spell_list)
- if(initial(S.name) == initial(aspell.name))
- spell_levels = aspell.spell_level
- user.mind.spell_list.Remove(aspell)
- qdel(aspell)
- if(S) //If we created a temporary spell above, delete it now.
- QDEL_NULL(S)
- return cost * (spell_levels + 1)
- return -1
-
-/datum/spellbook_entry/proc/GetInfo()
- if(!S)
- S = new spell_type()
- var/dat =""
- dat += "[name]"
- dat += " Cooldown:[S.base_cooldown/10]"
- dat += " Cost:[cost] "
- dat += "[S.desc][desc] "
- dat += "[S.clothes_req?"Needs wizard garb":"Can be cast without wizard garb"] "
- return dat
-
-//Main category - Spells
-//Offensive
-/datum/spellbook_entry/blind
- name = "Blind"
- spell_type = /datum/spell/blind
- category = "Offensive"
- cost = 1
-
-/datum/spellbook_entry/lightningbolt
- name = "Lightning Bolt"
- spell_type = /datum/spell/charge_up/bounce/lightning
- category = "Offensive"
- cost = 1
-
-/datum/spellbook_entry/cluwne
- name = "Curse of the Cluwne"
- spell_type = /datum/spell/touch/cluwne
- category = "Offensive"
-
-/datum/spellbook_entry/banana_touch
- name = "Banana Touch"
- spell_type = /datum/spell/touch/banana
- cost = 1
-
-/datum/spellbook_entry/mime_malaise
- name = "Mime Malaise"
- spell_type = /datum/spell/touch/mime_malaise
- cost = 1
-
-/datum/spellbook_entry/horseman
- name = "Curse of the Horseman"
- spell_type = /datum/spell/horsemask
- category = "Offensive"
-
-/datum/spellbook_entry/disintegrate
- name = "Disintegrate"
- spell_type = /datum/spell/touch/disintegrate
- category = "Offensive"
-
-/datum/spellbook_entry/corpse_explosion
- name = "Corpse Explosion"
- spell_type = /datum/spell/corpse_explosion
- category = "Offensive"
-
-/datum/spellbook_entry/fireball
- name = "Fireball"
- spell_type = /datum/spell/fireball
- category = "Offensive"
-
-/datum/spellbook_entry/summon_toolbox
- name = "Homing Toolbox"
- spell_type = /datum/spell/fireball/toolbox
- category = "Offensive"
- cost = 1
-
-/datum/spellbook_entry/fleshtostone
- name = "Flesh to Stone"
- spell_type = /datum/spell/touch/flesh_to_stone
- category = "Offensive"
-
-/datum/spellbook_entry/plushify
- name = "Plushify"
- spell_type = /datum/spell/touch/plushify
- category = "Offensive"
-
-/datum/spellbook_entry/mutate
- name = "Mutate"
- spell_type = /datum/spell/genetic/mutate
- category = "Offensive"
-
-/datum/spellbook_entry/rod_form
- name = "Rod Form"
- spell_type = /datum/spell/rod_form
- category = "Offensive"
-
-/datum/spellbook_entry/infinite_guns
- name = "Lesser Summon Guns"
- spell_type = /datum/spell/infinite_guns
- category = "Offensive"
-
-//Defensive
-/datum/spellbook_entry/disabletech
- name = "Disable Tech"
- spell_type = /datum/spell/emplosion/disable_tech
- category = "Defensive"
- cost = 1
-
-/datum/spellbook_entry/forcewall
- name = "Force Wall"
- spell_type = /datum/spell/forcewall
- category = "Defensive"
- cost = 1
-
-/datum/spellbook_entry/rathens
- name = "Rathen's Secret"
- spell_type = /datum/spell/rathens
- category = "Defensive"
- cost = 2
-
-/datum/spellbook_entry/repulse
- name = "Repulse"
- spell_type = /datum/spell/aoe/repulse
- category = "Defensive"
- cost = 1
-
-/datum/spellbook_entry/smoke
- name = "Smoke"
- spell_type = /datum/spell/smoke
- category = "Defensive"
- cost = 1
-
-/datum/spellbook_entry/lichdom
- name = "Bind Soul"
- spell_type = /datum/spell/lichdom
- category = "Defensive"
- is_ragin_restricted = TRUE
-
-/datum/spellbook_entry/magicm
- name = "Magic Missile"
- spell_type = /datum/spell/projectile/magic_missile
- category = "Defensive"
-
-/datum/spellbook_entry/timestop
- name = "Time Stop"
- spell_type = /datum/spell/aoe/conjure/timestop
- category = "Defensive"
-
-/datum/spellbook_entry/sacred_flame
- name = "Sacred Flame and Fire Immunity"
- spell_type = /datum/spell/sacred_flame
- cost = 1
- category = "Defensive"
-
-/datum/spellbook_entry/sacred_flame/LearnSpell(mob/living/carbon/human/user, obj/item/spellbook/book, datum/spell/newspell)
- to_chat(user, "You feel fireproof.")
- ADD_TRAIT(user, TRAIT_RESISTHEAT, MAGIC_TRAIT)
- ADD_TRAIT(user, TRAIT_RESISTHIGHPRESSURE, MAGIC_TRAIT)
- return ..()
-
-/datum/spellbook_entry/sacred_flame/Refund(mob/living/carbon/human/user, obj/item/spellbook/book)
- to_chat(user, "You no longer feel fireproof.")
- REMOVE_TRAIT(user, TRAIT_RESISTHEAT, MAGIC_TRAIT)
- REMOVE_TRAIT(user, TRAIT_RESISTHIGHPRESSURE, MAGIC_TRAIT)
- return ..()
-
-/datum/spellbook_entry/summon_supermatter
- name = "Summon Supermatter Crystal"
- spell_type = /datum/spell/aoe/conjure/summon_supermatter
- cost = 3
- category = "Defensive"
-
-/datum/spellbook_entry/summon_supermatter/LearnSpell(mob/living/carbon/human/user, obj/item/spellbook/book, datum/spell/newspell)
- to_chat(user, "You feel a little bit of supermatter enter your body.")
- ADD_TRAIT(user, TRAIT_RADIMMUNE, MAGIC_TRAIT)
- ADD_TRAIT(user, SM_HALLUCINATION_IMMUNE, MAGIC_TRAIT)
- return ..()
-
-/datum/spellbook_entry/summon_supermatter/Refund(mob/living/carbon/human/user, obj/item/spellbook/book)
- to_chat(user, "A little bit of supermatter leaves your body. So does that metallic taste in your mouth.")
- REMOVE_TRAIT(user, TRAIT_RADIMMUNE, MAGIC_TRAIT)
- REMOVE_TRAIT(user, SM_HALLUCINATION_IMMUNE, MAGIC_TRAIT)
- return ..()
-
-//Mobility
-/datum/spellbook_entry/knock
- name = "Knock"
- spell_type = /datum/spell/aoe/knock
- category = "Mobility"
- cost = 1
-
-/datum/spellbook_entry/blink
- name = "Blink"
- spell_type = /datum/spell/turf_teleport/blink
- category = "Mobility"
-
-/datum/spellbook_entry/jaunt
- name = "Ethereal Jaunt"
- spell_type = /datum/spell/ethereal_jaunt
- category = "Mobility"
-
-/datum/spellbook_entry/spacetime_dist
- name = "Spacetime Distortion"
- spell_type = /datum/spell/spacetime_dist
- cost = 1 //Better defence than greater forcewall (maybe) but good luck hitting anyone, so 1 point.
- category = "Mobility"
-
-/datum/spellbook_entry/greaterknock
- name = "Greater Knock"
- spell_type = /datum/spell/aoe/knock/greater
- category = "Mobility"
- refundable = 0 //global effect on cast
-
-/datum/spellbook_entry/mindswap
- name = "Mindswap"
- spell_type = /datum/spell/mind_transfer
- category = "Mobility"
-
-/datum/spellbook_entry/teleport
- name = "Teleport"
- spell_type = /datum/spell/area_teleport/teleport
- category = "Mobility"
-
-//Assistance
-/datum/spellbook_entry/charge
- name = "Charge"
- spell_type = /datum/spell/charge
- category = "Assistance"
- cost = 1
-
-/datum/spellbook_entry/summonitem
- name = "Summon Item"
- spell_type = /datum/spell/summonitem
- category = "Assistance"
- cost = 1
-
-/datum/spellbook_entry/disguiseself
- name = "Disguise Self"
- spell_type = /datum/spell/disguise_self
- category = "Assistance"
- cost = 1
-
-/datum/spellbook_entry/noclothes
- name = "Remove Clothes Requirement"
- spell_type = /datum/spell/noclothes
- category = "Assistance"
- cost = 1
-
-//Rituals
-/datum/spellbook_entry/summon
- name = "Summon Stuff"
- category = "Rituals"
- refundable = FALSE
- buy_word = "Cast"
- var/active = FALSE
-
-/datum/spellbook_entry/summon/CanBuy(mob/living/carbon/human/user, obj/item/spellbook/book)
- return ..() && !active
-
-/datum/spellbook_entry/summon/GetInfo()
- var/dat =""
- dat += "[name]"
- if(cost == 0)
- dat += " No Cost "
- else
- dat += " Cost:[cost] "
- dat += "[desc] "
- if(active)
- dat += "Already cast! "
- return dat
-
-/datum/spellbook_entry/summon/ghosts
- name = "Summon Ghosts"
- desc = "Spook the crew out by making them see dead people. Be warned, ghosts are capricious and occasionally vindicative, and some will use their incredibly minor abilities to frustrate you."
- cost = 0
- is_ragin_restricted = TRUE
-
-/datum/spellbook_entry/summon/ghosts/Buy(mob/living/carbon/human/user, obj/item/spellbook/book)
- new /datum/event/wizard/ghost()
- active = TRUE
- to_chat(user, "You have cast summon ghosts!")
- playsound(get_turf(user), 'sound/effects/ghost2.ogg', 50, 1)
- return TRUE
-
-/datum/spellbook_entry/summon/slience_ghosts
- name = "Silence Ghosts"
- desc = "Tired of people talking behind your back, and spooking you? Why not silence them, and make the dead deader."
- cost = 2
- is_ragin_restricted = TRUE //Salt needs to flow here, to be honest
-
-/datum/spellbook_entry/summon/slience_ghosts/Buy(mob/living/carbon/human/user, obj/item/spellbook/book)
- new /datum/event/wizard/ghost_mute()
- active = TRUE
- to_chat(user, "You have silenced all ghosts!")
- playsound(get_turf(user), 'sound/effects/ghost.ogg', 50, 1)
- message_admins("[key_name_admin(usr)] silenced all ghosts as a wizard! (Deadchat is now DISABLED)")
- return TRUE
-
-/datum/spellbook_entry/summon/guns
- name = "Summon Guns"
- desc = "Nothing could possibly go wrong with arming a crew of lunatics just itching for an excuse to kill you. There is a good chance that they will shoot each other first. Hopefully. Gives you 2 extra spell points on purchase."
- cost = -2
- is_ragin_restricted = TRUE
-
-/datum/spellbook_entry/summon/guns/Buy(mob/living/carbon/human/user, obj/item/spellbook/book)
- SSblackbox.record_feedback("tally", "wizard_spell_learned", 1, name)
- rightandwrong(SUMMON_GUNS, user, 10)
- active = TRUE
- playsound(get_turf(user), 'sound/magic/castsummon.ogg', 50, TRUE)
- to_chat(user, "You have cast summon guns!")
- return TRUE
-
-/datum/spellbook_entry/summon/magic
- name = "Summon Magic"
- desc = "Share the wonders of magic with the crew and show them why they aren't to be trusted with it at the same time. Gives you 2 extra spell points on purchase."
- cost = -2
- is_ragin_restricted = TRUE
-
-/datum/spellbook_entry/summon/magic/Buy(mob/living/carbon/human/user, obj/item/spellbook/book)
- SSblackbox.record_feedback("tally", "wizard_spell_learned", 1, name)
- rightandwrong(SUMMON_MAGIC, user, 10)
- active = TRUE
- playsound(get_turf(user), 'sound/magic/castsummon.ogg', 50, TRUE)
- to_chat(user, "You have cast summon magic!")
- return TRUE
-
-//Main category - Magical Items
-/datum/spellbook_entry/item
- name = "Buy Item"
- refundable = 0
- buy_word = "Summon"
- var/spawn_on_floor = FALSE
- var/item_path = null
-
-/datum/spellbook_entry/item/Buy(mob/living/carbon/human/user, obj/item/spellbook/book)
- if(!spawn_on_floor)
- user.put_in_hands(new item_path)
- else
- new item_path(user.loc)
- SSblackbox.record_feedback("tally", "wizard_spell_learned", 1, name)
- return 1
-
-/datum/spellbook_entry/item/GetInfo()
- var/dat =""
- dat += "[name]"
- dat += " Cost:[cost] "
- dat += "[desc] "
- return dat
-
-//Artefacts
-/datum/spellbook_entry/item/necrostone
- name = "A Necromantic Stone"
- desc = "A Necromantic stone is able to resurrect three dead individuals as skeletal thralls for you to command."
- item_path = /obj/item/necromantic_stone
- category = "Artefacts"
-
-/datum/spellbook_entry/item/scryingorb
- name = "Scrying Orb"
- desc = "An incandescent orb of crackling energy, using it will allow you to ghost while alive, allowing you to spy upon the station with ease. In addition, buying it will permanently grant you x-ray vision. It will also work as an excellent throwing weapon, and will return to your hand after throwing."
- item_path = /obj/item/scrying
- category = "Artefacts"
-
-/datum/spellbook_entry/item/soulstones
- name = "Six Soul Stone Shards and the spell Artificer"
- desc = "Soul Stone Shards are ancient tools capable of capturing and harnessing the spirits of the dead and dying. The spell Artificer allows you to create arcane machines for the captured souls to pilot."
- item_path = /obj/item/storage/belt/soulstone/full
- category = "Artefacts"
-
-/datum/spellbook_entry/item/soulstones/Buy(mob/living/carbon/human/user, obj/item/spellbook/book)
- . = ..()
- if(.)
- user.mind.AddSpell(new /datum/spell/aoe/conjure/construct(null))
-
-/datum/spellbook_entry/item/wands
- name = "Wand Assortment"
- desc = "A collection of wands that allow for a wide variety of utility. Wands do not recharge, so be conservative in use. Comes in a handy belt."
- item_path = /obj/item/storage/belt/wands/full
- category = "Artefacts"
-
-/datum/spellbook_entry/item/magic_nanny_bag
- name = "Magic Nanny Bag"
- desc = "A magical bottomless bag that comes filled with many random goodies, and sticks well in your hand. Will have a melee weapon, a staff, a wand, an artifact, and a special food treat! Can't fit on your back."
- item_path = /obj/item/storage/backpack/duffel/magic_nanny_bag
- cost = 4
- spawn_on_floor = TRUE // it isn't happy if it has to remake itself in hand
- is_ragin_restricted = TRUE //No blocked magic items on raging, sorry!
- category = "Artefacts"
-
-/datum/spellbook_entry/item/cursed_heart
- name = "Cursed Heart"
- desc = "A heart that has been empowered with magic to heal the user. The user must ensure the heart is manually beaten or their blood circulation will suffer, but every beat heals their injuries. It must beat every 6 seconds. Not reccomended for first time wizards."
- item_path = /obj/item/organ/internal/heart/cursed/wizard
- cost = 1
- category = "Artefacts"
-
-/datum/spellbook_entry/item/voice_of_god
- name = "Voice of God"
- desc = "A magical vocal cord that can be used to yell out with the voice of a god, be it to harm, help, or confuse the target."
- item_path = /obj/item/organ/internal/vocal_cords/colossus/wizard
- category = "Artefacts"
-
-/datum/spellbook_entry/item/warp_cubes
- name = "Warp Cubes"
- desc = "Two magic cubes, that when they are twisted in hand, teleports the user to the location of the other cube instantly. Great for silently teleporting to a fixed location, or teleporting you to an apprentice, or vice versa. Do not leave on the wizard den, it will not work."
- item_path = /obj/item/warp_cube/red
- cost = 1
- spawn_on_floor = TRUE // breaks if spawned in hand
- category = "Artefacts"
-
-/datum/spellbook_entry/item/everfull_mug
- name = "Everfull Mug"
- desc = "A magical mug that can be filled with omnizine at will, though beware of addiction! It can also produce alchohol and other less useful substances."
- item_path = /obj/item/reagent_containers/drinks/everfull
- cost = 1
- category = "Artefacts"
-
-/datum/spellbook_entry/item/tarot_generator
- name = "Enchanted tarot card deck"
- desc = "An magic tarot card deck, enchanted with special Ink. \
- Capable of producing magic tarot cards of the 22 major arcana, both normal and reversed. Each card has a different effect. \
- Throw the card at someone to use it on them, or use it in hand to apply it to yourself. Unlimited uses, 12 second cooldown, can have up to 5 cards in the world."
- item_path = /obj/item/tarot_generator/wizard
- cost = 2
- category = "Artefacts"
-
-//Weapons and Armors
-/datum/spellbook_entry/item/battlemage
- name = "Battlemage Armor"
- desc = "An ensorceled spaceproof suit of protective yet light armor, protected by a powerful shield. The shield can completely negate 15 attacks before permanently failing. \
- This armor grants you full protection from magical attacks, and allows you to cast magic despite that. However, this means it will also block wands or staffs of \
- healing from working on you, and should be removed before application."
- item_path = /obj/item/storage/box/wizard/hardsuit
- limit = 1
- category = "Weapons and Armors"
-
-/datum/spellbook_entry/item/mjolnir
- name = "Mjolnir"
- desc = "A mighty hammer on loan from Thor, God of Thunder. It crackles with barely contained power."
- item_path = /obj/item/mjollnir
- category = "Weapons and Armors"
-
-/datum/spellbook_entry/item/singularity_hammer
- name = "Singularity Hammer"
- desc = "A hammer that creates an intensely powerful field of gravity where it strikes, pulling everything nearby to the point of impact."
- item_path = /obj/item/singularityhammer
- category = "Weapons and Armors"
-
-/datum/spellbook_entry/item/cursed_katana
- name = "Cursed Katana"
- desc = "A cursed artefact, used to seal a horrible being inside the katana, which has now reformed. Can be used to make multiple powerful combos, examine it to see them. Can not be dropped. On death, you will dust."
- item_path = /obj/item/organ/internal/cyberimp/arm/katana
- cost = 1
- category = "Weapons and Armors"
-
-/datum/spellbook_entry/item/spell_blade
- name = "Spellblade"
- desc = "A magical sword that can be enchanted by using it in hand to have a unique on-hit effect. Lighting: arcs electricity between nearby targets, stunning and damaging them. Fire: creates a massive ball of fire on hit, and makes the wielder immune to fire. Bluespace: allows you to strike people from a range, teleporting you to them. Forceshield: on hit, makes you stun immune for 3 seconds and reduces damage by half. Spacetime: will slice faster but weaker and will curse the target, slashing them a few seconds after they have not been swinged at for each hit"
- item_path = /obj/item/melee/spellblade
- category = "Weapons and Armors"
-
-/datum/spellbook_entry/item/meat_hook
- name = "Meat hook"
- desc = "An enchanted hook, that can be used to hook people, hurt them, and bring them right to you. Quite bulky, works well as a belt though."
- item_path = /obj/item/gun/magic/hook
- cost = 1
- category = "Weapons and Armors"
-
-//Staves
-/datum/spellbook_entry/item/staffdoor
- name = "Staff of Door Creation"
- desc = "A particular staff that can mold solid metal into ornate wooden doors. Useful for getting around in the absence of other transportation. Does not work on glass."
- item_path = /obj/item/gun/magic/staff/door
- category = "Staves"
- cost = 1
-
-/datum/spellbook_entry/item/staffhealing
- name = "Staff of Healing"
- desc = "An altruistic staff that can heal the lame and raise the dead."
- item_path = /obj/item/gun/magic/staff/healing
- category = "Staves"
- cost = 1
-
-/datum/spellbook_entry/item/staffslipping
- name = "Staff of Slipping"
- desc = "A staff that shoots magical bananas. These bananas will either slip or stun the target when hit. Surprisingly reliable!"
- item_path = /obj/item/gun/magic/staff/slipping
- category = "Staves"
- cost = 1
-
-/datum/spellbook_entry/item/staffanimation
- name = "Staff of Animation"
- desc = "An arcane staff capable of shooting bolts of eldritch energy which cause inanimate objects to come to life. This magic doesn't affect machines."
- item_path = /obj/item/gun/magic/staff/animate
- category = "Staves"
-
-/datum/spellbook_entry/item/staffchange
- name = "Staff of Change"
- desc = "An artefact that spits bolts of coruscating energy which cause the target's very form to reshape itself."
- item_path = /obj/item/gun/magic/staff/change
- category = "Staves"
- is_ragin_restricted = TRUE
-
-/datum/spellbook_entry/item/staffchaos
- name = "Staff of Chaos"
- desc = "A curious staff firing bolts of chaotic energy. Any life struck will be the victim of a random effect, usually harming them. No effect on dead targets."
- item_path = /obj/item/gun/magic/staff/chaos
- category = "Staves"
-
-//Summons
-/datum/spellbook_entry/item/oozebottle
- name = "Bottle of Ooze"
- desc = "A bottle of magically infused ooze, which will awake an all-consuming Morph, capable of cunningly disguising itself as any object it comes in contact with and even casting some very basic spells. Be careful though, as Morph diet includes Wizards."
- item_path = /obj/item/antag_spawner/morph
- category = "Summons"
- limit = 3
- cost = 1
- is_ragin_restricted = TRUE
-
-/datum/spellbook_entry/item/hugbottle
- name = "Bottle of Tickles"
- desc = "A bottle of magically infused fun, the smell of which will \
- attract adorable extradimensional beings when broken. These beings \
- are similar to slaughter demons, but are a little weaker and they do not permanently \
- kill their victims, instead putting them in an extradimensional hugspace, \
- to be released on the demon's death. Chaotic, but not ultimately \
- damaging. The crew's reaction to the other hand could be very \
- destructive."
- item_path = /obj/item/antag_spawner/slaughter_demon/laughter
- category = "Summons"
- limit = 3
- cost = 1 // Non-destructive; it's just a jape, sibling!
-
-/datum/spellbook_entry/item/bloodbottle
- name = "Bottle of Blood"
- desc = "A bottle of magically infused blood, the smell of which will attract extradimensional beings when broken. Be careful though, the kinds of creatures summoned by blood magic are indiscriminate in their killing, and you yourself may become a victim."
- item_path = /obj/item/antag_spawner/slaughter_demon
- category = "Summons"
- limit = 3
- is_ragin_restricted = TRUE
-
-/datum/spellbook_entry/item/shadowbottle
- name = "Bottle of Shadows"
- desc = "A bottle of pure darkness, the smell of which will attract extradimensional beings when broken. Be careful though, the kinds of creatures summoned from the shadows are indiscriminate in their killing, and you yourself may become a victim."
- item_path = /obj/item/antag_spawner/slaughter_demon/shadow
- category = "Summons"
- limit = 3
- cost = 1 //Unless you blackout the station this ain't going to do much, wizard doesn't get NV, still dies easily to a group of 2 and it doesn't eat bodies.
-
-/datum/spellbook_entry/item/revenantbottle
- name = "Bottle of Ectoplasm"
- desc = "A magically infused bottle of ectoplasm, effectively pure salt from the spectral realm. Be careful though, these salty spirits are indiscriminate in their harvesting, and you yourself may become a victim."
- item_path = /obj/item/antag_spawner/revenant
- category = "Summons"
- limit = 3
- cost = 1 //Needs essence to live. Needs crew to die for essence, doubt xenobio will be making many monkeys. As such, weaker. Also can hardstun the wizard.
-
-/datum/spellbook_entry/item/pulsedemonbottle
- name = "Living Lightbulb"
- desc = "A magically sealed lightbulb confining some manner of electricity based creature. Beware, these creatures are indiscriminate in their shocking antics, and you yourself may become a victim. It is *heavily* advised not to summon it in maintenance areas."
- item_path = /obj/item/antag_spawner/pulse_demon
- category = "Summons"
- limit = 3
- cost = 1 // Needs station power to live. Also can kill the wizard trivially in maints (get shock protection).
-
-/datum/spellbook_entry/item/contract
- name = "Contract of Apprenticeship"
- desc = "A magical contract binding an apprentice wizard to your service, using it will summon them to your side."
- item_path = /obj/item/contract
- category = "Summons"
- limit = 1
- is_ragin_restricted = TRUE //We have enough wizards already! Sheesh!
-
-/datum/spellbook_entry/item/tarotdeck
- name = "Guardian Deck"
- desc = "A deck of guardian tarot cards, capable of binding a personal guardian to your body. There are multiple types of guardian available, but all of them will transfer some amount of damage to you. \
- It would be wise to avoid buying these with anything capable of causing you to swap bodies with others."
- item_path = /obj/item/guardiancreator
- category = "Summons"
- limit = 1
-
-//Spell loadouts datum, list of loadouts is in wizloadouts.dm
-/datum/spellbook_entry/loadout
- name = "Standard Loadout"
- cost = 10
- category = "Standard"
- refundable = FALSE
- buy_word = "Summon"
- var/list/items_path = list()
- var/list/spells_path = list()
- var/destroy_spellbook = FALSE //Destroy the spellbook when bought, for loadouts containing non-standard items/spells, otherwise wiz can refund spells
-
-/datum/spellbook_entry/loadout/GetInfo()
- var/dat = ""
- dat += "[name]"
- if(cost > 0)
- dat += " Cost:[cost] "
- else
- dat += " No Cost "
- dat += "[desc] "
- return dat
-
-/datum/spellbook_entry/loadout/Buy(mob/living/carbon/human/user, obj/item/spellbook/book)
- if(destroy_spellbook)
- var/response = tgui_alert(user, "The [src] loadout cannot be refunded once bought. Are you sure this is what you want?", "No refunds!", list("No", "Yes"))
- if(response != "Yes")
- return FALSE
- if(!CanBuy(user, book))
- to_chat(user, "You can't afford that anymore!")
- return FALSE
- to_chat(user, "[book] crumbles to ashes as you acquire its knowledge.")
- qdel(book)
- else if(length(items_path))
- var/response = tgui_alert(user, "The [src] loadout contains items that will not be refundable if bought. Are you sure this is what you want?", "No refunds!", list("No", "Yes"))
- if(response != "Yes")
- return FALSE
- if(!CanBuy(user, book))
- to_chat(user, "You can't afford that anymore!")
- return FALSE
- if(length(items_path))
- var/obj/item/storage/box/wizard/B = new(src)
- for(var/path in items_path)
- new path(B)
- user.put_in_hands(B)
- for(var/path in spells_path)
- var/datum/spell/S = new path()
- LearnSpell(user, book, S)
- OnBuy(user, book)
- return TRUE
-
-/datum/spellbook_entry/loadout/proc/OnBuy(mob/living/carbon/human/user, obj/item/spellbook/book)
- return
-
-/obj/item/spellbook
- name = "spell book"
- desc = "The legendary book of spells of the wizard."
- icon = 'icons/obj/library.dmi'
- icon_state = "spellbook"
- throw_speed = 2
- throw_range = 5
- w_class = WEIGHT_CLASS_TINY
- var/uses = 10
- var/temp = null
- var/op = 1
- var/tab = null
- var/main_tab = null
- var/mob/living/carbon/human/owner
- var/list/datum/spellbook_entry/entries = list()
- var/list/categories = list()
- var/list/main_categories = list("Spells", "Magical Items", "Loadouts")
- var/list/spell_categories = list("Offensive", "Defensive", "Mobility", "Assistance", "Rituals")
- var/list/item_categories = list("Artefacts", "Weapons and Armors", "Staves", "Summons")
- var/list/loadout_categories = list("Standard", "Unique")
-
-/obj/item/spellbook/proc/initialize()
- var/entry_types = subtypesof(/datum/spellbook_entry) - /datum/spellbook_entry/item - /datum/spellbook_entry/summon - /datum/spellbook_entry/loadout
- for(var/T in entry_types)
- var/datum/spellbook_entry/E = new T
- if(GAMEMODE_IS_RAGIN_MAGES && E.is_ragin_restricted)
- qdel(E)
- continue
- entries |= E
- categories |= E.category
-
- main_tab = main_categories[1]
- tab = categories[1]
-
-/obj/item/spellbook/New()
- ..()
- initialize()
-
-/obj/item/spellbook/attackby__legacy__attackchain(obj/item/O as obj, mob/user as mob, params)
- if(istype(O, /obj/item/contract))
- var/obj/item/contract/contract = O
- if(contract.used)
- to_chat(user, "The contract has been used, you can't get your points back now!")
- else
- to_chat(user, "You feed the contract back into the spellbook, refunding your points.")
- uses+=2
- qdel(O)
- return
-
- if(istype(O, /obj/item/antag_spawner/slaughter_demon))
- to_chat(user, "On second thought, maybe summoning a demon is a bad idea. You refund your points.")
- if(istype(O, /obj/item/antag_spawner/slaughter_demon/laughter))
- uses += 1
- for(var/datum/spellbook_entry/item/hugbottle/HB in entries)
- if(!isnull(HB.limit))
- HB.limit++
- else if(istype(O, /obj/item/antag_spawner/slaughter_demon/shadow))
- uses += 1
- for(var/datum/spellbook_entry/item/shadowbottle/SB in entries)
- if(!isnull(SB.limit))
- SB.limit++
- else
- uses += 2
- for(var/datum/spellbook_entry/item/bloodbottle/BB in entries)
- if(!isnull(BB.limit))
- BB.limit++
- qdel(O)
- return
-
- if(istype(O, /obj/item/antag_spawner/morph))
- to_chat(user, "On second thought, maybe awakening a morph is a bad idea. You refund your points.")
- uses += 1
- for(var/datum/spellbook_entry/item/oozebottle/OB in entries)
- if(!isnull(OB.limit))
- OB.limit++
- qdel(O)
- return
-
- if(istype(O, /obj/item/antag_spawner/revenant))
- to_chat(user, "On second thought, maybe the ghosts have been salty enough today. You refund your points.")
- uses += 1
- for(var/datum/spellbook_entry/item/revenantbottle/RB in entries)
- if(!isnull(RB.limit))
- RB.limit++
- qdel(O)
- return
- return ..()
-
-/obj/item/spellbook/proc/GetCategoryHeader(category)
- var/dat = ""
- switch(category)
- if("Offensive")
- dat += "Spells geared towards debilitating and destroying.
"
- dat += "For spells: the number after the spell name is the cooldown time. "
- dat += "You can reduce this number by spending more points on the spell. "
- if("Defensive")
- dat += "Spells geared towards improving your survivability or reducing foes ability to attack.
"
- dat += "For spells: the number after the spell name is the cooldown time. "
- dat += "You can reduce this number by spending more points on the spell. "
- if("Mobility")
- dat += "Spells geared towards improving your ability to move. It is a good idea to take at least one.
"
- dat += "For spells: the number after the spell name is the cooldown time. "
- dat += "You can reduce this number by spending more points on the spell. "
- if("Assistance")
- dat += "Spells geared towards improving your other items and abilities.
"
- dat += "For spells: the number after the spell name is the cooldown time. "
- dat += "You can reduce this number by spending more points on the spell. "
- if("Rituals")
- dat += "These powerful spells are capable of changing the very fabric of reality. Not always in your favour. "
- if("Weapons and Armors")
- dat += "Various weapons and armors to crush your enemies and protect you from harm.
"
- dat += "Items are not bound to you and can be stolen. Additionally they cannot typically be returned once purchased. "
- if("Staves")
- dat += "Various staves granting you their power, which they slowly recharge over time.
"
- dat += "Items are not bound to you and can be stolen. Additionally they cannot typically be returned once purchased. "
- if("Artefacts")
- dat += "Various magical artefacts to aid you.
"
- dat += "Items are not bound to you and can be stolen. Additionally they cannot typically be returned once purchased. "
- if("Summons")
- dat += "Magical items geared towards bringing in outside forces to aid you.
"
- dat += "Items are not bound to you and can be stolen. Additionally they cannot typically be returned once purchased. "
- if("Standard")
- dat += "These battle-tested spell sets are easy to use and provide good balance between offense and defense.
"
- dat += "They all cost, and are worth, 10 spell points. You are able to refund any of the spells included as long as you stay in the wizard den. "
- if("Unique")
- dat += "These esoteric loadouts usually contain spells or items that cannot be bought elsewhere in this spellbook.
"
- dat += "Recommended for experienced wizards looking for something new. No refunds once purchased! "
- return dat
-
-/obj/item/spellbook/proc/wrap(content)
- var/dat = ""
- dat +="Spellbook"
- dat += {"
-
-
-
- "}
- dat += {"[content] |