diff --git a/Build.sh b/Build.sh
index 6fd691921e..f36de6e9d0 100755
--- a/Build.sh
+++ b/Build.sh
@@ -1,4 +1,4 @@
#!/bin/bash
cd "$(dirname "$(readlink -f "$0")")"
cd ./tools/build
-sudo bash ./build.sh
+sudo bash ./build
diff --git a/_maps/RandomZLevels/away_mission/jungleresort.dmm b/_maps/RandomZLevels/away_mission/jungleresort.dmm
index 9ff92b619a..4207080ac4 100644
--- a/_maps/RandomZLevels/away_mission/jungleresort.dmm
+++ b/_maps/RandomZLevels/away_mission/jungleresort.dmm
@@ -203,7 +203,7 @@
/area/awaymission/jungleresort)
"cK" = (
/obj/structure/closet/crate,
-/obj/item/clothing/head/collectable/petehat/gang,
+/obj/item/clothing/head/collectable/petehat,
/turf/open/floor/plating/rust,
/area/awaymission/jungleresort)
"cT" = (
diff --git a/code/__DEFINES/_flags/obj_flags.dm b/code/__DEFINES/_flags/obj_flags.dm
index ac772c6e8e..5f5ea12aaa 100644
--- a/code/__DEFINES/_flags/obj_flags.dm
+++ b/code/__DEFINES/_flags/obj_flags.dm
@@ -16,6 +16,7 @@
#define SHOVABLE_ONTO (1<<13)//called on turf.shove_act() to consider whether an object should have a niche effect (defined in their own shove_act()) when someone is pushed onto it, or do a sanity CanPass() check.
#define EXAMINE_SKIP (1<<14) /// Makes the Examine proc not read out this item.
#define IN_STORAGE (1<<15) //is this item in the storage item, such as backpack? used for tooltips
+#define HAND_ITEM (1<<16) // If an item is just your hand (circled hand, slapper) and shouldn't block things like riding
/// Integrity defines for clothing (not flags but close enough)
#define CLOTHING_PRISTINE 0 // We have no damage on the clothing
diff --git a/code/__DEFINES/antagonists.dm b/code/__DEFINES/antagonists.dm
index 11189cf1af..20b4f03d71 100644
--- a/code/__DEFINES/antagonists.dm
+++ b/code/__DEFINES/antagonists.dm
@@ -120,3 +120,6 @@ GLOBAL_LIST_EMPTY(living_heart_cache) //A list of all living hearts in existance
/// TC to charge someone if they get a free implant through choice or
/// because they have nothing else that supports an implant.
#define UPLINK_IMPLANT_TELECRYSTAL_COST 4
+
+/// The dimensions of the antagonist preview icon. Will be scaled to this size.
+#define ANTAGONIST_PREVIEW_ICON_SIZE 96
diff --git a/code/__DEFINES/color/color_priority.dm b/code/__DEFINES/color/color_priority.dm
new file mode 100644
index 0000000000..040ff5003a
--- /dev/null
+++ b/code/__DEFINES/color/color_priority.dm
@@ -0,0 +1,6 @@
+//different types of atom colorations
+#define ADMIN_COLOUR_PRIORITY 1 //only used by rare effects like greentext coloring mobs and when admins varedit color
+#define TEMPORARY_COLOUR_PRIORITY 2 //e.g. purple effect of the revenant on a mob, black effect when mob electrocuted
+#define WASHABLE_COLOUR_PRIORITY 3 //color splashed onto an atom (e.g. paint on turf)
+#define FIXED_COLOUR_PRIORITY 4 //color inherent to the atom (e.g. blob color)
+#define COLOUR_PRIORITY_AMOUNT 4 //how many priority levels there are.
diff --git a/code/__DEFINES/colors.dm b/code/__DEFINES/color/colors.dm
similarity index 100%
rename from code/__DEFINES/colors.dm
rename to code/__DEFINES/color/colors.dm
diff --git a/code/__DEFINES/color/lum.dm b/code/__DEFINES/color/lum.dm
new file mode 100644
index 0000000000..942462b573
--- /dev/null
+++ b/code/__DEFINES/color/lum.dm
@@ -0,0 +1,4 @@
+//Luma coefficients suggested for HDTVs. If you change these, make sure they add up to 1.
+#define LUMA_R 0.213
+#define LUMA_G 0.715
+#define LUMA_B 0.072
diff --git a/code/__DEFINES/dcs/signals.dm b/code/__DEFINES/dcs/signals.dm
index ea527d19ab..fdded58308 100644
--- a/code/__DEFINES/dcs/signals.dm
+++ b/code/__DEFINES/dcs/signals.dm
@@ -472,6 +472,14 @@
#define COMSIG_MINE_TRIGGERED "minegoboom" ///from [/obj/effect/mine/proc/triggermine]:
// Uncovered information
#define COMPONENT_DEEPSCAN_UNCOVERED_INFORMATION 1
+///Called when an item is being offered, from [/obj/item/proc/on_offered(mob/living/carbon/offerer)]
+#define COMSIG_ITEM_OFFERING "item_offering"
+ ///Interrupts the offer proc
+ #define COMPONENT_OFFER_INTERRUPT (1<<0)
+///Called when an someone tries accepting an offered item, from [/obj/item/proc/on_offer_taken(mob/living/carbon/offer, mob/living/carbon/taker)]
+#define COMSIG_ITEM_OFFER_TAKEN "item_offer_taken"
+ ///Interrupts the offer acceptance
+ #define COMPONENT_OFFER_TAKE_INTERRUPT (1<<0)
///from [/obj/structure/closet/supplypod/proc/endlaunch]:
#define COMSIG_SUPPLYPOD_LANDED "supplypodgoboom"
diff --git a/code/__DEFINES/layers_planes.dm b/code/__DEFINES/layers_planes.dm
index 531629c7cd..2550084207 100644
--- a/code/__DEFINES/layers_planes.dm
+++ b/code/__DEFINES/layers_planes.dm
@@ -5,9 +5,10 @@
#define CLICKCATCHER_PLANE -99
-#define PLANE_SPACE -95
+#define PLANE_SPACE -98
#define PLANE_SPACE_RENDER_TARGET "PLANE_SPACE"
-#define PLANE_SPACE_PARALLAX -90
+
+#define PLANE_SPACE_PARALLAX -95
#define PLANE_SPACE_PARALLAX_RENDER_TARGET "PLANE_SPACE_PARALLAX"
#define OPENSPACE_LAYER 17 //Openspace layer over all
diff --git a/code/__DEFINES/misc.dm b/code/__DEFINES/misc.dm
index 1410a5d99a..a88f40a19f 100644
--- a/code/__DEFINES/misc.dm
+++ b/code/__DEFINES/misc.dm
@@ -345,18 +345,6 @@ GLOBAL_LIST_INIT(pda_reskins, list(PDA_SKIN_CLASSIC = 'icons/obj/pda.dmi', PDA_S
//TODO Move to a pref
#define STATION_GOAL_BUDGET 1
-//Luma coefficients suggested for HDTVs. If you change these, make sure they add up to 1.
-#define LUMA_R 0.213
-#define LUMA_G 0.715
-#define LUMA_B 0.072
-
-//different types of atom colorations
-#define ADMIN_COLOUR_PRIORITY 1 //only used by rare effects like greentext coloring mobs and when admins varedit color
-#define TEMPORARY_COLOUR_PRIORITY 2 //e.g. purple effect of the revenant on a mob, black effect when mob electrocuted
-#define WASHABLE_COLOUR_PRIORITY 3 //color splashed onto an atom (e.g. paint on turf)
-#define FIXED_COLOUR_PRIORITY 4 //color inherent to the atom (e.g. blob color)
-#define COLOUR_PRIORITY_AMOUNT 4 //how many priority levels there are.
-
//Endgame Results
#define NUKE_NEAR_MISS 1
#define NUKE_MISS_STATION 2
@@ -380,7 +368,8 @@ GLOBAL_LIST_INIT(pda_reskins, list(PDA_SKIN_CLASSIC = 'icons/obj/pda.dmi', PDA_S
#define CLOCK_SILICONS 22
#define CLOCK_PROSELYTIZATION 23
#define SHUTTLE_HIJACK 24
-#define GANG_VICTORY 25
+#define GANG_DESTROYED 25
+#define GANG_OPERATING 26
#define FIELD_TURF 1
#define FIELD_EDGE 2
@@ -568,3 +557,8 @@ GLOBAL_LIST_INIT(pda_reskins, list(PDA_SKIN_CLASSIC = 'icons/obj/pda.dmi', PDA_S
#define SHOES_KNOTTED 2
#define WANTED_FILE "wanted_message.json"
+
+// Notification action types
+#define NOTIFY_JUMP "jump"
+#define NOTIFY_ATTACK "attack"
+#define NOTIFY_ORBIT "orbit"
diff --git a/code/__DEFINES/preferences.dm b/code/__DEFINES/preferences.dm
index cd13510366..cb95aab8c0 100644
--- a/code/__DEFINES/preferences.dm
+++ b/code/__DEFINES/preferences.dm
@@ -34,12 +34,6 @@
#define TOGGLES_DEFAULT_CHAT (CHAT_OOC|CHAT_DEAD|CHAT_GHOSTEARS|CHAT_GHOSTSIGHT|CHAT_PRAYER|CHAT_RADIO|CHAT_PULLR|CHAT_GHOSTWHISPER|CHAT_GHOSTPDA|CHAT_GHOSTRADIO|CHAT_LOOC|CHAT_BANKCARD)
-#define PARALLAX_INSANE -1 //for show offs
-#define PARALLAX_HIGH 0 //default.
-#define PARALLAX_MED 1
-#define PARALLAX_LOW 2
-#define PARALLAX_DISABLE 3 //this option must be the highest number
-
#define PIXEL_SCALING_AUTO 0
#define PIXEL_SCALING_1X 1
#define PIXEL_SCALING_1_2X 1.5
@@ -50,10 +44,6 @@
#define SCALING_METHOD_DISTORT "distort"
#define SCALING_METHOD_BLUR "blur"
-#define PARALLAX_DELAY_DEFAULT world.tick_lag
-#define PARALLAX_DELAY_MED 1
-#define PARALLAX_DELAY_LOW 2
-
#define SEC_DEPT_NONE "None"
#define SEC_DEPT_RANDOM "Random"
#define SEC_DEPT_ENGINEERING "Engineering"
diff --git a/code/__DEFINES/atom_hud.dm b/code/__DEFINES/rendering/atom_hud.dm
similarity index 94%
rename from code/__DEFINES/atom_hud.dm
rename to code/__DEFINES/rendering/atom_hud.dm
index 386473a3ae..067c3f801c 100644
--- a/code/__DEFINES/atom_hud.dm
+++ b/code/__DEFINES/rendering/atom_hud.dm
@@ -60,11 +60,8 @@
#define ANTAG_HUD_BROTHER 23
#define ANTAG_HUD_BLOODSUCKER 24
#define ANTAG_HUD_FUGITIVE 25
-#define ANTAG_HUD_HERETIC 26
-
-// Notification action types
-#define NOTIFY_JUMP "jump"
-#define NOTIFY_ATTACK "attack"
-#define NOTIFY_ORBIT "orbit"
+#define ANTAG_HUD_HERETIC 26
+#define ANTAG_HUD_SPACECOP 27
+#define ANTAG_HUD_GANGSTER 28
#define ADD_HUD_TO_COOLDOWN 20 //cooldown for being shown the images for any particular data hud
diff --git a/code/__DEFINES/rendering/parallax.dm b/code/__DEFINES/rendering/parallax.dm
new file mode 100644
index 0000000000..8170b4190d
--- /dev/null
+++ b/code/__DEFINES/rendering/parallax.dm
@@ -0,0 +1,13 @@
+#define PARALLAX_DELAY_DEFAULT world.tick_lag
+#define PARALLAX_DELAY_MED 1
+#define PARALLAX_DELAY_LOW 2
+
+// WARNING - client.prefs uses this, if you change these make sure to update the code in preferences!
+#define PARALLAX_DISABLE 0
+#define PARALLAX_LOW 1
+#define PARALLAX_MED 2
+#define PARALLAX_HIGH 3
+#define PARALLAX_INSANE 4 // default
+
+// keep this false until we can fix it being a seizure hazard/ugly as sin
+#define PARALLAX_ROTATION_ANIMATIONS FALSE
diff --git a/code/__DEFINES/role_preferences.dm b/code/__DEFINES/role_preferences.dm
index 19ccf5fdc1..3204a39b89 100644
--- a/code/__DEFINES/role_preferences.dm
+++ b/code/__DEFINES/role_preferences.dm
@@ -36,7 +36,8 @@
#define ROLE_DEATHSQUAD "deathsquad"
#define ROLE_LAVALAND "lavaland"
#define ROLE_INTERNAL_AFFAIRS "internal affairs agent"
-#define ROLE_GANG "gangster"
+#define ROLE_FAMILIES "family boss"
+#define ROLE_FAMILY_HEAD_ASPIRANT "family head aspirant"
#define ROLE_BLOODSUCKER "bloodsucker"
#define ROLE_SPACE_DRAGON "Space Dragon"
//#define ROLE_MONSTERHUNTER "monster hunter" Disabled for now
@@ -72,9 +73,9 @@ GLOBAL_LIST_INIT(special_roles, list(
ROLE_OVERTHROW = /datum/game_mode/overthrow,
ROLE_INTERNAL_AFFAIRS = /datum/game_mode/traitor/internal_affairs,
ROLE_SENTIENCE,
- ROLE_GANG = /datum/game_mode/gang,
ROLE_HERETIC = /datum/game_mode/heretics,
ROLE_BLOODSUCKER = /datum/game_mode/bloodsucker,
+ ROLE_FAMILIES = /datum/game_mode/gang,
ROLE_SPACE_DRAGON
//ROLE_MONSTERHUNTER Disabled for now
))
diff --git a/code/__DEFINES/shuttles.dm b/code/__DEFINES/shuttles.dm
index a188bf8363..862cc27f04 100644
--- a/code/__DEFINES/shuttles.dm
+++ b/code/__DEFINES/shuttles.dm
@@ -44,7 +44,6 @@
#define SHUTTLE_TRANSIT_BORDER 10
-#define PARALLAX_LOOP_TIME 25
#define HYPERSPACE_END_TIME 5
#define HYPERSPACE_WARMUP 1
diff --git a/code/__DEFINES/spans.dm b/code/__DEFINES/spans.dm
index 45eae3ce76..50fc40d5ca 100644
--- a/code/__DEFINES/spans.dm
+++ b/code/__DEFINES/spans.dm
@@ -44,6 +44,7 @@
#define span_greenannounce(str) ("" + str + "")
#define span_greenteamradio(str) ("" + str + "")
#define span_greentext(str) ("" + str + "")
+#define span_gangradio(str) ("" + str + "")
#define span_hear(str) ("" + str + "")
#define span_hidden(str) ("" + str + "")
#define span_hierophant(str) ("" + str + "")
diff --git a/code/__DEFINES/status_effects.dm b/code/__DEFINES/status_effects.dm
index 669bf6326b..e3c928a9a9 100644
--- a/code/__DEFINES/status_effects.dm
+++ b/code/__DEFINES/status_effects.dm
@@ -136,6 +136,10 @@
#define STATUS_EFFECT_INLOVE /datum/status_effect/in_love //Displays you as being in love with someone else, and makes hearts appear around them.
+#define STATUS_EFFECT_OFFERING /datum/status_effect/offering // you are offering up an item to people
+
+#define STATUS_EFFECT_HANDSHAKE /datum/status_effect/offering/secret_handshake // you are attempting to perform a secret Family handshake
+
/////////////
// SLIME //
/////////////
diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm
index 30806bfc52..e1154b305f 100644
--- a/code/__DEFINES/traits.dm
+++ b/code/__DEFINES/traits.dm
@@ -202,6 +202,7 @@
#define TRAIT_FRIENDLY "friendly"
#define TRAIT_SNOB "snob"
#define TRAIT_MULTILINGUAL "multilingual"
+#define TRAIT_HEARING_SENSITIVE "hearing_sensitive"
#define TRAIT_CULT_EYES "cult_eyes"
#define TRAIT_AUTO_CATCH_ITEM "auto_catch_item"
#define TRAIT_CLOWN_MENTALITY "clown_mentality" // The future is now, clownman.
diff --git a/code/__HELPERS/_lists.dm b/code/__HELPERS/_lists.dm
index f045b5e9aa..2a50ab3a39 100644
--- a/code/__HELPERS/_lists.dm
+++ b/code/__HELPERS/_lists.dm
@@ -21,7 +21,9 @@
#define LAZYACCESS(L, I) (L ? (isnum(I) ? (I > 0 && I <= length(L) ? L[I] : null) : L[I]) : null)
#define LAZYSET(L, K, V) if(!L) { L = list(); } L[K] = V;
#define LAZYLEN(L) length(L)
-///Sets a list to null
+///This is used to add onto lazy assoc list when the value you're adding is a /list/. This one has extra safety over lazyaddassoc because the value could be null (and thus cant be used to += objects)
+#define LAZYADDASSOCLIST(L, K, V) if(!L) { L = list(); } L[K] += list(V);
+//Sets a list to null
#define LAZYNULL(L) L = null
#define LAZYADDASSOC_TG(L, K, V) if(!L) { L = list(); } L[K] += V;
///This is used to add onto lazy assoc list when the value you're adding is a /list/. This one has extra safety over lazyaddassoc because the value could be null (and thus cant be used to += objects)
diff --git a/code/__HELPERS/matrices.dm b/code/__HELPERS/matrices/color_matrix.dm
similarity index 59%
rename from code/__HELPERS/matrices.dm
rename to code/__HELPERS/matrices/color_matrix.dm
index 59fbb4ffc0..e4a4a9bb48 100644
--- a/code/__HELPERS/matrices.dm
+++ b/code/__HELPERS/matrices/color_matrix.dm
@@ -1,85 +1,3 @@
-/matrix/proc/TurnTo(old_angle, new_angle)
- . = new_angle - old_angle
- Turn(.) //BYOND handles cases such as -270, 360, 540 etc. DOES NOT HANDLE 180 TURNS WELL, THEY TWEEN AND LOOK LIKE SHIT
-
-/atom/proc/SpinAnimation(speed = 10, loops = -1, clockwise = 1, segments = 3, parallel = TRUE)
- if(!segments)
- return
- var/segment = 360/segments
- if(!clockwise)
- segment = -segment
- var/list/matrices = list()
- for(var/i in 1 to segments-1)
- var/matrix/M = matrix(transform)
- M.Turn(segment*i)
- matrices += M
- var/matrix/last = matrix(transform)
- matrices += last
-
- speed /= segments
-
- if(parallel)
- animate(src, transform = matrices[1], time = speed, loops , flags = ANIMATION_PARALLEL)
- else
- animate(src, transform = matrices[1], time = speed, loops)
-
- for(var/i in 2 to segments) //2 because 1 is covered above
- animate(transform = matrices[i], time = speed)
- //doesn't have an object argument because this is "Stacking" with the animate call above
- //3 billion% intentional
-
-//Dumps the matrix data in format a-f
-/matrix/proc/tolist()
- . = list()
- . += a
- . += b
- . += c
- . += d
- . += e
- . += f
-
-//Dumps the matrix data in a matrix-grid format
-/*
- a d 0
- b e 0
- c f 1
-*/
-/matrix/proc/togrid()
- . = list()
- . += a
- . += d
- . += 0
- . += b
- . += e
- . += 0
- . += c
- . += f
- . += 1
-
-//The X pixel offset of this matrix
-/matrix/proc/get_x_shift()
- . = c
-
-//The Y pixel offset of this matrix
-/matrix/proc/get_y_shift()
- . = f
-
-/matrix/proc/get_x_skew()
- . = b
-
-/matrix/proc/get_y_skew()
- . = d
-
-//Skews a matrix in a particular direction
-//Missing arguments are treated as no skew in that direction
-
-//As Rotation is defined as a scale+skew, these procs will break any existing rotation
-//Unless the result is multiplied against the current matrix
-/matrix/proc/set_skew(x = 0, y = 0)
- b = x
- d = y
-
-
/////////////////////
// COLOUR MATRICES //
/////////////////////
@@ -114,12 +32,64 @@ list(0.393,0.349,0.272,0, 0.769,0.686,0.534,0, 0.189,0.168,0.131,0, 0,0,0,1, 0,0
return list(R + value,R,R,0, G,G + value,G,0, B,B,B + value,0, 0,0,0,1, 0,0,0,0)
+/**
+ * Exxagerates or removes colors
+ */
+/proc/color_matrix_saturation_percent(percent)
+ if(percent == 0)
+ return color_matrix_identity()
+ percent = clamp(percent, -100, 100)
+ if(percent > 0)
+ percent *= 3
+ var/x = 1 + percent / 100
+ var/inv = 1 - x
+ var/R = LUMA_R * inv
+ var/G = LUMA_G * inv
+ var/B = LUMA_B * inv
+
+ return list(R + x,R,R, G,G + x,G, B,B,B + x)
+
//Changes distance colors have from rgb(127,127,127) grey
//1 is identity. 0 makes everything grey >1 blows out colors and greys
/proc/color_matrix_contrast(value)
var/add = (1 - value) / 2
return list(value,0,0,0, 0,value,0,0, 0,0,value,0, 0,0,0,1, add,add,add,0)
+/**
+ * Exxagerates or removes brightness
+ */
+/proc/color_matrix_contrast_percent(percent)
+ var/static/list/delta_index = list(
+ 0, 0.01, 0.02, 0.04, 0.05, 0.06, 0.07, 0.08, 0.1, 0.11,
+ 0.12, 0.14, 0.15, 0.16, 0.17, 0.18, 0.20, 0.21, 0.22, 0.24,
+ 0.25, 0.27, 0.28, 0.30, 0.32, 0.34, 0.36, 0.38, 0.40, 0.42,
+ 0.44, 0.46, 0.48, 0.5, 0.53, 0.56, 0.59, 0.62, 0.65, 0.68,
+ 0.71, 0.74, 0.77, 0.80, 0.83, 0.86, 0.89, 0.92, 0.95, 0.98,
+ 1.0, 1.06, 1.12, 1.18, 1.24, 1.30, 1.36, 1.42, 1.48, 1.54,
+ 1.60, 1.66, 1.72, 1.78, 1.84, 1.90, 1.96, 2.0, 2.12, 2.25,
+ 2.37, 2.50, 2.62, 2.75, 2.87, 3.0, 3.2, 3.4, 3.6, 3.8,
+ 4.0, 4.3, 4.7, 4.9, 5.0, 5.5, 6.0, 6.5, 6.8, 7.0,
+ 7.3, 7.5, 7.8, 8.0, 8.4, 8.7, 9.0, 9.4, 9.6, 9.8,
+ 10.0)
+ percent = clamp(percent, -100, 100)
+ if(percent == 0)
+ return color_matrix_identity()
+
+ var/x = 0
+ if (percent < 0)
+ x = 127 + percent / 100 * 127;
+ else
+ x = percent % 1
+ if(x == 0)
+ x = delta_index[percent]
+ else
+ x = delta_index[percent] * (1-x) + delta_index[percent+1] * x//use linear interpolation for more granularity.
+ x = x * 127 + 127
+
+ var/mult = x / 127
+ var/add = 0.5 * (127-x) / 255
+ return list(mult,0,0, 0,mult,0, 0,0,mult, add,add,add)
+
//Moves all colors angle degrees around the color wheel while maintaining intensity of the color and not affecting greys
//0 is identity, 120 moves reds to greens, 240 moves reds to blues
/proc/color_matrix_rotate_hue(angle)
@@ -134,6 +104,26 @@ round(cos_inv_third+sqrt3_sin, 0.001), round(cos_inv_third-sqrt3_sin, 0.001), ro
0,0,0,1,
0,0,0,0)
+/**
+ * Moves all colors angle degrees around the color wheel while maintaining intensity of the color and not affecting whites
+ * TODO: Need a version that only affects one color (ie shift red to blue but leave greens and blues alone)
+ */
+/proc/color_matrix_rotation(angle)
+ if(angle == 0)
+ return color_matrix_identity()
+ angle = clamp(angle, -180, 180)
+ var/cos = cos(angle)
+ var/sin = sin(angle)
+
+ var/constA = 0.143
+ var/constB = 0.140
+ var/constC = -0.283
+ return list(
+ LUMA_R + cos * (1-LUMA_R) + sin * -LUMA_R, LUMA_R + cos * -LUMA_R + sin * constA, LUMA_R + cos * -LUMA_R + sin * -(1-LUMA_R),
+ LUMA_G + cos * -LUMA_G + sin * -LUMA_G, LUMA_G + cos * (1-LUMA_G) + sin * constB, LUMA_G + cos * -LUMA_G + sin * LUMA_G,
+ LUMA_B + cos * -LUMA_B + sin * (1-LUMA_B), LUMA_B + cos * -LUMA_B + sin * constC, LUMA_B + cos * (1-LUMA_B) + sin * LUMA_B
+ )
+
//These next three rotate values about one axis only
//x is the red axis, y is the green axis, z is the blue axis.
/proc/color_matrix_rotate_x(angle)
@@ -183,3 +173,9 @@ round(cos_inv_third+sqrt3_sin, 0.001), round(cos_inv_third-sqrt3_sin, 0.001), ro
*/
/proc/rgb_construct_color_matrix(rr = 1, rg, rb, gr, gg = 1, gb, br, bg, bb = 1, cr, cg, cb)
return list(rr, rg, rb, gr, gg, gb, br, bg, bb, cr, cg, cb)
+
+/**
+ * Assembles a color matrix, defaulting to identity
+ */
+/proc/rgba_construct_color_matrix(rr = 1, rg, rb, ra, gr, gg = 1, gb, ga, br, bg, bb = 1, ba, ar, ag, ab, aa = 1, cr, cg, cb, ca)
+ return list(rr, rg, rb, ra, gr, gg, gb, ga, br, bg, bb, ba, ar, ag, ab, aa, cr, cg, cb, ca)
diff --git a/code/__HELPERS/matrices/transform_matrix.dm b/code/__HELPERS/matrices/transform_matrix.dm
new file mode 100644
index 0000000000..251ef8151f
--- /dev/null
+++ b/code/__HELPERS/matrices/transform_matrix.dm
@@ -0,0 +1,86 @@
+/matrix/proc/TurnTo(old_angle, new_angle)
+ . = new_angle - old_angle
+ Turn(.) //BYOND handles cases such as -270, 360, 540 etc. DOES NOT HANDLE 180 TURNS WELL, THEY TWEEN AND LOOK LIKE SHIT
+
+/atom/proc/SpinAnimation(speed = 10, loops = -1, clockwise = 1, segments = 3, parallel = TRUE)
+ if(!segments)
+ return
+ var/segment = 360/segments
+ if(!clockwise)
+ segment = -segment
+ var/list/matrices = list()
+ for(var/i in 1 to segments-1)
+ var/matrix/M = matrix(transform)
+ M.Turn(segment*i)
+ matrices += M
+ var/matrix/last = matrix(transform)
+ matrices += last
+
+ speed /= segments
+
+ if(parallel)
+ animate(src, transform = matrices[1], time = speed, loops , flags = ANIMATION_PARALLEL)
+ else
+ animate(src, transform = matrices[1], time = speed, loops)
+
+ for(var/i in 2 to segments) //2 because 1 is covered above
+ animate(transform = matrices[i], time = speed)
+ //doesn't have an object argument because this is "Stacking" with the animate call above
+ //3 billion% intentional
+
+//Dumps the matrix data in format a-f
+/matrix/proc/tolist()
+ . = list()
+ . += a
+ . += b
+ . += c
+ . += d
+ . += e
+ . += f
+
+//Dumps the matrix data in a matrix-grid format
+/*
+ a d 0
+ b e 0
+ c f 1
+*/
+/matrix/proc/togrid()
+ . = list()
+ . += a
+ . += d
+ . += 0
+ . += b
+ . += e
+ . += 0
+ . += c
+ . += f
+ . += 1
+
+//The X pixel offset of this matrix
+/matrix/proc/get_x_shift()
+ . = c
+
+//The Y pixel offset of this matrix
+/matrix/proc/get_y_shift()
+ . = f
+
+/matrix/proc/get_x_skew()
+ . = b
+
+/matrix/proc/get_y_skew()
+ . = d
+
+//Skews a matrix in a particular direction
+//Missing arguments are treated as no skew in that direction
+
+//As Rotation is defined as a scale+skew, these procs will break any existing rotation
+//Unless the result is multiplied against the current matrix
+/matrix/proc/set_skew(x = 0, y = 0)
+ b = x
+ d = y
+
+/**
+ * constructs a transform matrix, defaulting to identity
+ */
+/proc/transform_matrix_construct(a = 1, b, c, d = 1, e, f)
+ return matrix(a, b, c, d, e, f)
diff --git a/code/__HELPERS/mobs.dm b/code/__HELPERS/mobs.dm
index a26905f7b3..178fa5b8a4 100644
--- a/code/__HELPERS/mobs.dm
+++ b/code/__HELPERS/mobs.dm
@@ -473,3 +473,6 @@ GLOBAL_LIST_EMPTY(species_datums)
/// Gets the client of the mob, allowing for mocking of the client.
/// You only need to use this if you know you're going to be mocking clients somewhere else.
#define GET_CLIENT(mob) (##mob.client || ##mob.mock_client)
+
+//check if the person is dead, not sure where to put this
+#define IS_DEAD_OR_INCAP(source) (source.incapacitated() || source.stat)
diff --git a/code/__HELPERS/priority_announce.dm b/code/__HELPERS/priority_announce.dm
index 1aaebc5f32..1609c5f208 100644
--- a/code/__HELPERS/priority_announce.dm
+++ b/code/__HELPERS/priority_announce.dm
@@ -152,7 +152,7 @@
continue
to_chat(mob_to_teleport, announcement)
SEND_SOUND(mob_to_teleport, meeting_sound) //no preferences here, you must hear the funny sound
- mob_to_teleport.overlay_fullscreen("emergency_meeting", /atom/movable/screen/fullscreen/emergency_meeting, 1)
+ mob_to_teleport.overlay_fullscreen("emergency_meeting", /atom/movable/screen/fullscreen/scaled/emergency_meeting, 1)
addtimer(CALLBACK(mob_to_teleport, /mob/.proc/clear_fullscreen, "emergency_meeting"), 3 SECONDS)
if (is_station_level(mob_to_teleport.z)) //teleport the mob to the crew meeting
diff --git a/code/__HELPERS/roundend.dm b/code/__HELPERS/roundend.dm
index edc9541abf..b0719bfdfa 100644
--- a/code/__HELPERS/roundend.dm
+++ b/code/__HELPERS/roundend.dm
@@ -590,8 +590,8 @@
var/list/all_teams = list()
var/list/all_antagonists = list()
- // for(var/datum/team/A in GLOB.antagonist_teams)
- // all_teams |= A
+ for(var/datum/team/A in GLOB.antagonist_teams)
+ all_teams |= A
for(var/datum/antagonist/A in GLOB.antagonists)
if(!A.owner)
diff --git a/code/_globalvars/lists/mobs.dm b/code/_globalvars/lists/mobs.dm
index 53448a284a..5cbfd9f212 100644
--- a/code/_globalvars/lists/mobs.dm
+++ b/code/_globalvars/lists/mobs.dm
@@ -52,6 +52,8 @@ GLOBAL_LIST_EMPTY(current_observers_list)
//Dynamic Port
GLOBAL_LIST_EMPTY(new_player_list) //all /mob/dead/new_player, in theory all should have clients and those that don't are in the process of spawning and get deleted when done.
+//Family Port
+GLOBAL_LIST_EMPTY(pre_setup_antags) //minds that have been picked as antag by the gamemode. removed as antag datums are set.
/proc/update_config_movespeed_type_lookup(update_mobs = TRUE)
// NOTE: This is entirely based on the fact that byond typesof/subtypesof gets longer/deeper paths before shallower ones.
diff --git a/code/_onclick/click.dm b/code/_onclick/click.dm
index 1b72cc71b1..773adc061f 100644
--- a/code/_onclick/click.dm
+++ b/code/_onclick/click.dm
@@ -470,51 +470,6 @@
else
setDir(WEST, ismousemovement)
-//debug
-/atom/movable/screen/proc/scale_to(x1,y1)
- if(!y1)
- y1 = x1
- var/matrix/M = new
- M.Scale(x1,y1)
- transform = M
-
-/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"
-
-#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/T = params2turf(modifiers["screen-loc"], get_turf(usr.client ? usr.client.eye : usr), usr.client)
- params += "&catcher=1"
- if(T)
- T.Click(location, control, params)
- . = 1
-
/* MouseWheelOn */
/mob/proc/MouseWheelOn(atom/A, delta_x, delta_y, params)
diff --git a/code/_onclick/hud/_defines.dm b/code/_onclick/hud/_defines.dm
index d1fff25dae..f8dfc703f0 100644
--- a/code/_onclick/hud/_defines.dm
+++ b/code/_onclick/hud/_defines.dm
@@ -176,9 +176,6 @@
#define ui_ghost_mafia "SOUTH: 6, CENTER+2:24"
#define ui_ghost_spawners "SOUTH: 6, CENTER+1:24" // LEGACY. SAME LOC AS PAI
-// #define ui_wanted_lvl "NORTH,11"
-
-
//UI position overrides for 1:1 screen layout. (default is 7:5)
#define ui_stamina "EAST-1:28,CENTER:17" // replacing internals button
#define ui_overridden_resist "EAST-3:24,SOUTH+1:7"
@@ -190,3 +187,5 @@
#define ui_boxarea "EAST-4:6,SOUTH+1:6"
#define ui_boxlang "EAST-5:22,SOUTH+1:6"
#define ui_boxvore "EAST-5:22,SOUTH+1:6"
+
+#define ui_wanted_lvl "NORTH,11"
diff --git a/code/_onclick/hud/alert.dm b/code/_onclick/hud/alert.dm
index ed4c91dd9b..f74d380b8a 100644
--- a/code/_onclick/hud/alert.dm
+++ b/code/_onclick/hud/alert.dm
@@ -2,9 +2,6 @@
//PUBLIC - call these wherever you want
-
-/mob/proc/throw_alert(category, type, severity, obj/new_master, override = FALSE)
-
/* Proc to create or update an alert. Returns the alert if the alert is new or updated, 0 if it was thrown already
category is a text string. Each mob may only have one alert per category; the previous one will be replaced
path is a type path of the actual alert type to throw
@@ -14,6 +11,7 @@
Clicks are forwarded to master
Override makes it so the alert is not replaced until cleared by a clear_alert with clear_override, and it's used for hallucinations.
*/
+/mob/proc/throw_alert(category, type, severity, obj/new_master, override = FALSE)
if(!category || QDELETED(src))
return
@@ -42,7 +40,7 @@
thealert.override_alerts = override
if(override)
thealert.timeout = null
- thealert.mob_viewer = src
+ thealert.owner = src
if(new_master)
var/old_layer = new_master.layer
@@ -97,8 +95,10 @@
var/severity = 0
var/alerttooltipstyle = ""
var/override_alerts = FALSE //If it is overriding other alerts of the same type
- var/mob/mob_viewer //the mob viewing this alert
+ var/mob/owner //Alert owner
+ /// Boolean. If TRUE, the Click() proc will attempt to Click() on the master first if there is a master.
+ var/click_master = TRUE
/atom/movable/screen/alert/MouseEntered(location,control,params)
if(!QDELETED(src))
@@ -251,6 +251,8 @@ or something covering your eyes."
/atom/movable/screen/alert/mind_control/Click()
var/mob/living/L = usr
+ if(L != owner)
+ return
to_chat(L, "[command]")
/atom/movable/screen/alert/hypnosis
@@ -271,7 +273,7 @@ If you're feeling frisky, examine yourself and click the underlined item to pull
icon_state = "embeddedobject"
/atom/movable/screen/alert/embeddedobject/Click()
- if(isliving(usr))
+ if(isliving(usr) && usr == owner)
var/mob/living/carbon/M = usr
return M.help_shake_act(M)
@@ -300,7 +302,7 @@ or shoot a gun to move around via Newton's 3rd Law of Motion."
/atom/movable/screen/alert/fire/Click()
var/mob/living/L = usr
- if(!istype(L) || !L.can_resist())
+ if(!istype(L) || !L.can_resist() || L != owner)
return
L.MarkResistTime()
if(CHECK_MOBILITY(L, MOBILITY_MOVE))
@@ -308,37 +310,110 @@ or shoot a gun to move around via Newton's 3rd Law of Motion."
/atom/movable/screen/alert/give // information set when the give alert is made
icon_state = "default"
- var/mob/living/carbon/giver
+ var/mob/living/carbon/offerer
var/obj/item/receiving
/**
- * Handles assigning most of the variables for the alert that pops up when an item is offered
- *
- * Handles setting the name, description and icon of the alert and tracking the person giving
- * and the item being offered, also registers a signal that removes the alert from anyone who moves away from the giver
- * Arguments:
- * * taker - The person receiving the alert
- * * giver - The person giving the alert and item
- * * receiving - The item being given by the giver
- */
-/atom/movable/screen/alert/give/proc/setup(mob/living/carbon/taker, mob/living/carbon/giver, obj/item/receiving)
- name = "[giver] is offering [receiving]"
- desc = "[giver] is offering [receiving]. Click this alert to take it."
+ * Handles assigning most of the variables for the alert that pops up when an item is offered
+ *
+ * Handles setting the name, description and icon of the alert and tracking the person giving
+ * and the item being offered, also registers a signal that removes the alert from anyone who moves away from the offerer
+ * Arguments:
+ * * taker - The person receiving the alert
+ * * offerer - The person giving the alert and item
+ * * receiving - The item being given by the offerer
+ */
+/atom/movable/screen/alert/give/proc/setup(mob/living/carbon/taker, mob/living/carbon/offerer, obj/item/receiving)
+ name = "[offerer] is offering [receiving]"
+ desc = "[offerer] is offering [receiving]. Click this alert to take it."
icon_state = "template"
cut_overlays()
add_overlay(receiving)
src.receiving = receiving
- src.giver = giver
- RegisterSignal(taker, COMSIG_MOVABLE_MOVED, .proc/removeAlert)
-
-/atom/movable/screen/alert/give/proc/removeAlert()
- to_chat(usr, "You moved out of range of [giver]!")
- usr.clear_alert("[giver]")
+ src.offerer = offerer
+ RegisterSignal(taker, COMSIG_MOVABLE_MOVED, .proc/check_in_range, override = TRUE) //Override to prevent runtimes when people offer a item multiple times
/atom/movable/screen/alert/give/Click(location, control, params)
. = ..()
- var/mob/living/carbon/C = usr
- C.take(giver, receiving)
+ if(!.)
+ return
+ if(!iscarbon(usr))
+ CRASH("User for [src] is of type \[[usr.type]\]. This should never happen.")
+ handle_transfer()
+
+/// An overrideable proc used simply to hand over the item when claimed, this is a proc so that high-fives can override them since nothing is actually transferred
+/atom/movable/screen/alert/give/proc/handle_transfer()
+ var/mob/living/carbon/taker = owner
+ taker.take(offerer, receiving)
+
+/// Simply checks if the other person is still in range
+/atom/movable/screen/alert/give/proc/check_in_range(atom/taker)
+ SIGNAL_HANDLER
+
+ if(!offerer.CanReach(taker))
+ to_chat(owner, span_warning("You moved out of range of [offerer]!"))
+ owner.clear_alert("[offerer]")
+
+/atom/movable/screen/alert/give/highfive/setup(mob/living/carbon/taker, mob/living/carbon/offerer, obj/item/receiving)
+ . = ..()
+ name = "[offerer] is offering a high-five!"
+ desc = "[offerer] is offering a high-five! Click this alert to slap it."
+ RegisterSignal(offerer, COMSIG_PARENT_EXAMINE_MORE, .proc/check_fake_out)
+
+/atom/movable/screen/alert/give/highfive/handle_transfer()
+ var/mob/living/carbon/taker = owner
+ if(receiving && (receiving in offerer.held_items))
+ receiving.on_offer_taken(offerer, taker)
+ return
+ too_slow_p1()
+
+/// If the person who offered the high five no longer has it when we try to accept it, we get pranked hard
+/atom/movable/screen/alert/give/highfive/proc/too_slow_p1()
+ var/mob/living/carbon/rube = owner
+ if(!rube || !offerer)
+ qdel(src)
+ return
+
+ offerer.visible_message(span_notice("[rube] rushes in to high-five [offerer], but-"), span_nicegreen("[rube] falls for your trick just as planned, lunging for a high-five that no longer exists! Classic!"), ignored_mobs=rube)
+ to_chat(rube, span_nicegreen("You go in for [offerer]'s high-five, but-"))
+ addtimer(CALLBACK(src, .proc/too_slow_p2, offerer, rube), 0.5 SECONDS)
+
+/// Part two of the ultimate prank
+/atom/movable/screen/alert/give/highfive/proc/too_slow_p2()
+ var/mob/living/carbon/rube = owner
+ if(!rube || !offerer)
+ qdel(src)
+ return
+
+ offerer.visible_message(span_danger("[offerer] pulls away from [rube]'s slap at the last second, dodging the high-five entirely!"), span_nicegreen("[rube] fails to make contact with your hand, making an utter fool of [rube.p_them()]self!"), span_hear("You hear a disappointing sound of flesh not hitting flesh!"), ignored_mobs=rube)
+ var/all_caps_for_emphasis = uppertext("NO! [offerer] PULLS [offerer.p_their()] HAND AWAY FROM YOURS! YOU'RE TOO SLOW!")
+ to_chat(rube, span_userdanger("[all_caps_for_emphasis]"))
+ playsound(offerer, 'sound/weapons/thudswoosh.ogg', 100, TRUE, 1)
+ rube.Knockdown(1 SECONDS)
+ SEND_SIGNAL(offerer, COMSIG_ADD_MOOD_EVENT, "high_five", /datum/mood_event/down_low)
+ SEND_SIGNAL(rube, COMSIG_ADD_MOOD_EVENT, "high_five", /datum/mood_event/too_slow)
+ qdel(src)
+
+/// If someone examine_more's the offerer while they're trying to pull a too-slow, it'll tip them off to the offerer's trickster ways
+/atom/movable/screen/alert/give/highfive/proc/check_fake_out(datum/source, mob/user, list/examine_list)
+ SIGNAL_HANDLER
+
+ if(!receiving)
+ examine_list += "[span_warning("[offerer]'s arm appears tensed up, as if [offerer.p_they()] plan on pulling it back suddenly...")]\n"
+
+/// Families handshakes
+/atom/movable/screen/alert/give/secret_handshake
+ icon_state = "default"
+
+/atom/movable/screen/alert/give/secret_handshake/setup(mob/living/carbon/taker, mob/living/carbon/offerer, obj/item/receiving)
+ name = "[offerer] is offering a Handshake"
+ desc = "[offerer] wants to teach you the Secret Handshake for their Family and induct you! Click on this alert to accept."
+ icon_state = "template"
+ cut_overlays()
+ add_overlay(receiving)
+ src.receiving = receiving
+ src.offerer = offerer
+ RegisterSignal(taker, COMSIG_MOVABLE_MOVED, .proc/check_in_range, override = TRUE) //Override to prevent runtimes when people offer a item multiple times
//ALIENS
@@ -393,10 +468,10 @@ or shoot a gun to move around via Newton's 3rd Law of Motion."
/atom/movable/screen/alert/bloodsense/process()
var/atom/blood_target
- if(!mob_viewer.mind)
+ if(!owner.mind)
return
- var/datum/antagonist/cult/antag = mob_viewer.mind.has_antag_datum(/datum/antagonist/cult,TRUE)
+ var/datum/antagonist/cult/antag = owner.mind.has_antag_datum(/datum/antagonist/cult,TRUE)
if(!antag?.cult_team)
return
var/datum/objective/sacrifice/sac_objective = locate() in antag.cult_team.objectives
@@ -433,7 +508,7 @@ or shoot a gun to move around via Newton's 3rd Law of Motion."
add_overlay(narnar)
return
var/turf/P = get_turf(blood_target)
- var/turf/Q = get_turf(mob_viewer)
+ var/turf/Q = get_turf(owner)
if(!P || !Q || (P.z != Q.z)) //The target is on a different Z level, we cannot sense that far.
icon_state = "runed_sense2"
desc = "You can no longer sense your target's presence."
@@ -497,7 +572,7 @@ or shoot a gun to move around via Newton's 3rd Law of Motion."
for(var/mob/living/L in GLOB.alive_mob_list)
if(is_servant_of_ratvar(L))
servants++
- var/datum/antagonist/clockcult/C = mob_viewer.mind.has_antag_datum(/datum/antagonist/clockcult,TRUE)
+ var/datum/antagonist/clockcult/C = owner.mind.has_antag_datum(/datum/antagonist/clockcult,TRUE)
if(C && C.clock_team)
textlist += "[C.clock_team.eminence ? "There is an Eminence." : "There is no Eminence! Get one ASAP!"]
"
textlist += "There are currently [servants] servant[servants > 1 ? "s" : ""] of Ratvar.
"
@@ -595,7 +670,7 @@ so as to remain in compliance with the most up-to-date laws."
var/atom/target = null
/atom/movable/screen/alert/hackingapc/Click()
- if(!usr || !usr.client)
+ if(!usr || !usr.client || usr != owner)
return
if(!target)
return
@@ -621,7 +696,7 @@ so as to remain in compliance with the most up-to-date laws."
timeout = 300
/atom/movable/screen/alert/notify_cloning/Click()
- if(!usr || !usr.client)
+ if(!usr || !usr.client || usr != owner)
return
var/mob/dead/observer/G = usr
G.reenter_corpse()
@@ -635,7 +710,7 @@ so as to remain in compliance with the most up-to-date laws."
var/action = NOTIFY_JUMP
/atom/movable/screen/alert/notify_action/Click()
- if(!usr || !usr.client)
+ if(!usr || !usr.client || usr != owner)
return
if(!target)
return
@@ -669,14 +744,14 @@ so as to remain in compliance with the most up-to-date laws."
/atom/movable/screen/alert/restrained/Click()
var/mob/living/L = usr
- if(!istype(L) || !L.can_resist())
+ if(!istype(L) || !L.can_resist() || L != owner)
return
L.MarkResistTime()
return L.resist_restraints()
/atom/movable/screen/alert/restrained/buckled/Click()
var/mob/living/L = usr
- if(!istype(L) || !L.can_resist())
+ if(!istype(L) || !L.can_resist() || L != owner)
return
L.MarkResistTime()
return L.resist_buckle()
@@ -693,7 +768,7 @@ so as to remain in compliance with the most up-to-date laws."
/atom/movable/screen/alert/shoes/Click()
var/mob/living/carbon/C = usr
- if(!istype(C) || !C.can_resist() || C != mob_viewer || !C.shoes)
+ if(!istype(C) || !C.can_resist() || C != owner || !C.shoes)
return
C.MarkResistTime()
C.shoes.handle_tying(C)
@@ -701,11 +776,14 @@ so as to remain in compliance with the most up-to-date laws."
// PRIVATE = only edit, use, or override these if you're editing the system as a whole
// Re-render all alerts - also called in /datum/hud/show_hud() because it's needed there
-/datum/hud/proc/reorganize_alerts()
+/datum/hud/proc/reorganize_alerts(mob/viewmob)
+ var/mob/screenmob = viewmob || mymob
+ if(!screenmob.client)
+ return
var/list/alerts = mymob.alerts
if(!hud_shown)
for(var/i = 1, i <= alerts.len, i++)
- mymob.client.screen -= alerts[alerts[i]]
+ screenmob.client.screen -= alerts[alerts[i]]
return 1
for(var/i = 1, i <= alerts.len, i++)
var/atom/movable/screen/alert/alert = alerts[alerts[i]]
@@ -725,22 +803,29 @@ so as to remain in compliance with the most up-to-date laws."
else
. = ""
alert.screen_loc = .
- mymob.client.screen |= alert
+ screenmob.client.screen |= alert
+ if(!viewmob)
+ for(var/M in mymob.observers)
+ reorganize_alerts(M)
return 1
/atom/movable/screen/alert/Click(location, control, params)
if(!usr || !usr.client)
- return
+ return FALSE
+ if(usr != owner)
+ return FALSE
var/paramslist = params2list(params)
if(paramslist["shift"]) // screen objects don't do the normal Click() stuff so we'll cheat
to_chat(usr, "[name] - [desc]")
- return
- if(master)
+ return FALSE
+ if(master && click_master)
return usr.client.Click(master, location, control, params)
+
+ return TRUE
/atom/movable/screen/alert/Destroy()
. = ..()
severity = 0
master = null
- mob_viewer = null
+ owner = null
screen_loc = ""
diff --git a/code/_onclick/hud/families.dm b/code/_onclick/hud/families.dm
new file mode 100644
index 0000000000..eb09d53bc1
--- /dev/null
+++ b/code/_onclick/hud/families.dm
@@ -0,0 +1,27 @@
+/atom/movable/screen/wanted
+ name = "Space Police Alertness"
+ desc = "Shows the current level of hostility the space police is planning to rain down on you. Better be careful."
+ icon = 'icons/obj/gang/wanted_160x32.dmi'
+ icon_state = "wanted_0"
+ screen_loc = ui_wanted_lvl
+ /// Wanted level, affects the hud icon. Level 0 is default, and the level 0 icon is blank, so in case of no families gamemode (and thus no wanted level), this HUD element will never appear.
+ level = 2
+ /// Boolean, have the cops arrived? If so, the icon stops changing and remains the same.
+ var/cops_arrived = 0
+
+/atom/movable/screen/wanted/New()
+ return ..()
+
+/atom/movable/screen/wanted/Initialize()
+ . = ..()
+ update_icon()
+
+/atom/movable/screen/wanted/MouseEntered(location,control,params)
+ openToolTip(usr,src,params,title = name,content = desc, theme = "alerttooltipstyle")
+
+/atom/movable/screen/wanted/MouseExited()
+ closeToolTip(usr)
+
+/atom/movable/screen/wanted/update_icon_state()
+ . = ..()
+ icon_state = "wanted_[level][cops_arrived ? "_active" : ""]"
diff --git a/code/_onclick/hud/fullscreen.dm b/code/_onclick/hud/fullscreen.dm
deleted file mode 100644
index 81442b602f..0000000000
--- a/code/_onclick/hud/fullscreen.dm
+++ /dev/null
@@ -1,192 +0,0 @@
-/mob/proc/overlay_fullscreen(category, type, severity)
- var/atom/movable/screen/fullscreen/screen = screens[category]
- if (!screen || screen.type != type)
- // needs to be recreated
- clear_fullscreen(category, FALSE)
- screens[category] = screen = new type()
- else if ((!severity || severity == screen.severity) && (!client || screen.screen_loc != "CENTER-7,CENTER-7" || screen.view == client.view))
- // doesn't need to be updated
- return screen
-
- screen.icon_state = "[initial(screen.icon_state)][severity]"
- screen.severity = severity
- if (client && screen.should_show_to(src))
- screen.update_for_view(client.view)
- client.screen += screen
-
- return screen
-
-/mob/proc/clear_fullscreen(category, animated = 10)
- var/atom/movable/screen/fullscreen/screen = screens[category]
- if(!screen)
- return
-
- screens -= category
-
- if(animated)
- animate(screen, alpha = 0, time = animated)
- addtimer(CALLBACK(src, .proc/clear_fullscreen_after_animate, screen), animated, TIMER_CLIENT_TIME)
- else
- if(client)
- client.screen -= screen
- qdel(screen)
-
-/mob/proc/clear_fullscreen_after_animate(atom/movable/screen/fullscreen/screen)
- if(client)
- client.screen -= screen
- qdel(screen)
-
-/mob/proc/clear_fullscreens()
- for(var/category in screens)
- clear_fullscreen(category)
-
-/mob/proc/hide_fullscreens()
- if(client)
- for(var/category in screens)
- client.screen -= screens[category]
-
-/mob/proc/reload_fullscreen()
- if(client)
- var/atom/movable/screen/fullscreen/screen
- for(var/category in screens)
- screen = screens[category]
- if(screen.should_show_to(src))
- screen.update_for_view(client.view)
- client.screen |= screen
- else
- client.screen -= screen
-
-/atom/movable/screen/fullscreen
- icon = 'icons/mob/screen_full.dmi'
- icon_state = "default"
- screen_loc = "CENTER-7,CENTER-7"
- layer = FULLSCREEN_LAYER
- plane = FULLSCREEN_PLANE
- mouse_opacity = MOUSE_OPACITY_TRANSPARENT
- var/view = 7
- var/severity = 0
- var/show_when_dead = FALSE
-
-/atom/movable/screen/fullscreen/proc/update_for_view(client_view)
- if (screen_loc == "CENTER-7,CENTER-7" && view != client_view)
- var/list/actualview = getviewsize(client_view)
- view = client_view
- transform = matrix(actualview[1]/FULLSCREEN_OVERLAY_RESOLUTION_X, 0, 0, 0, actualview[2]/FULLSCREEN_OVERLAY_RESOLUTION_Y, 0)
-
-/atom/movable/screen/fullscreen/proc/should_show_to(mob/mymob)
- if(!show_when_dead && mymob.stat == DEAD)
- return FALSE
- return TRUE
-
-/atom/movable/screen/fullscreen/Destroy()
- severity = 0
- . = ..()
-
-/atom/movable/screen/fullscreen/emergency_meeting
- icon_state = "emergency_meeting"
- show_when_dead = TRUE
- layer = CURSE_LAYER
- plane = SPLASHSCREEN_PLANE
-
-/atom/movable/screen/fullscreen/brute
- icon_state = "brutedamageoverlay"
- layer = UI_DAMAGE_LAYER
- plane = FULLSCREEN_PLANE
-
-/atom/movable/screen/fullscreen/oxy
- icon_state = "oxydamageoverlay"
- layer = UI_DAMAGE_LAYER
- plane = FULLSCREEN_PLANE
-
-/atom/movable/screen/fullscreen/crit
- icon_state = "passage"
- layer = CRIT_LAYER
- plane = FULLSCREEN_PLANE
-
-/atom/movable/screen/fullscreen/crit/vision
- icon_state = "oxydamageoverlay"
- layer = BLIND_LAYER
-
-/atom/movable/screen/fullscreen/blind
- icon_state = "blackimageoverlay"
- layer = BLIND_LAYER
- plane = FULLSCREEN_PLANE
-
-/atom/movable/screen/fullscreen/curse
- icon_state = "curse"
- layer = CURSE_LAYER
- plane = FULLSCREEN_PLANE
-
-/atom/movable/screen/fullscreen/impaired
- icon_state = "impairedoverlay"
-
-/atom/movable/screen/fullscreen/blurry
- icon = 'icons/mob/screen_gen.dmi'
- screen_loc = "WEST,SOUTH to EAST,NORTH"
- icon_state = "blurry"
-
-/atom/movable/screen/fullscreen/flash
- icon = 'icons/mob/screen_gen.dmi'
- screen_loc = "WEST,SOUTH to EAST,NORTH"
- icon_state = "flash"
-
-/atom/movable/screen/fullscreen/flash/static
- icon = 'icons/mob/screen_gen.dmi'
- screen_loc = "WEST,SOUTH to EAST,NORTH"
- icon_state = "noise"
-
-/atom/movable/screen/fullscreen/high
- icon = 'icons/mob/screen_gen.dmi'
- screen_loc = "WEST,SOUTH to EAST,NORTH"
- icon_state = "druggy"
-
-/atom/movable/screen/fullscreen/color_vision
- icon = 'icons/mob/screen_gen.dmi'
- screen_loc = "WEST,SOUTH to EAST,NORTH"
- icon_state = "flash"
- alpha = 80
-
-/atom/movable/screen/fullscreen/color_vision/green
- color = "#00ff00"
-
-/atom/movable/screen/fullscreen/color_vision/red
- color = "#ff0000"
-
-/atom/movable/screen/fullscreen/color_vision/blue
- color = "#0000ff"
-
-/atom/movable/screen/fullscreen/cinematic_backdrop
- icon = 'icons/mob/screen_gen.dmi'
- screen_loc = "WEST,SOUTH to EAST,NORTH"
- icon_state = "flash"
- plane = SPLASHSCREEN_PLANE
- layer = SPLASHSCREEN_LAYER - 1
- color = "#000000"
- show_when_dead = TRUE
-
-/atom/movable/screen/fullscreen/lighting_backdrop
- icon = 'icons/mob/screen_gen.dmi'
- icon_state = "flash"
- transform = matrix(200, 0, 0, 0, 200, 0)
- plane = LIGHTING_PLANE
- blend_mode = BLEND_OVERLAY
- show_when_dead = TRUE
-
-//Provides darkness to the back of the lighting plane
-/atom/movable/screen/fullscreen/lighting_backdrop/lit
- invisibility = INVISIBILITY_LIGHTING
- layer = BACKGROUND_LAYER+21
- color = "#000"
- show_when_dead = TRUE
-
-//Provides whiteness in case you don't see lights so everything is still visible
-/atom/movable/screen/fullscreen/lighting_backdrop/unlit
- layer = BACKGROUND_LAYER+20
- show_when_dead = TRUE
-
-/atom/movable/screen/fullscreen/see_through_darkness
- icon_state = "nightvision"
- plane = LIGHTING_PLANE
- layer = LIGHTING_LAYER
- blend_mode = BLEND_ADD
- show_when_dead = TRUE
diff --git a/code/_onclick/hud/ghost.dm b/code/_onclick/hud/ghost.dm
index b9514bff46..bb637ed445 100644
--- a/code/_onclick/hud/ghost.dm
+++ b/code/_onclick/hud/ghost.dm
@@ -93,3 +93,10 @@
screenmob.client.screen -= static_inventory
else
screenmob.client.screen += static_inventory
+
+//We should only see observed mob alerts.
+/datum/hud/ghost/reorganize_alerts(mob/viewmob)
+ var/mob/dead/observer/O = mymob
+ if (istype(O) && O.observetarget)
+ return
+ . = ..()
diff --git a/code/_onclick/hud/hud.dm b/code/_onclick/hud/hud.dm
index 144a38e728..a5fe8f4ba8 100644
--- a/code/_onclick/hud/hud.dm
+++ b/code/_onclick/hud/hud.dm
@@ -60,6 +60,7 @@ GLOBAL_LIST_INIT(available_ui_styles, list(
var/atom/movable/screen/healthdoll
var/atom/movable/screen/internals
+ var/atom/movable/screen/wanted/wanted_lvl
// subtypes can override this to force a specific UI style
var/ui_style
@@ -82,6 +83,7 @@ GLOBAL_LIST_INIT(available_ui_styles, list(
plane_masters["[instance.plane]"] = instance
instance.backdrop(mymob)
+
/datum/hud/Destroy()
if(mymob.hud_used == src)
mymob.hud_used = null
@@ -102,6 +104,7 @@ GLOBAL_LIST_INIT(available_ui_styles, list(
healths = null
healthdoll = null
+ wanted_lvl = null
internals = null
lingchemdisplay = null
devilsouldisplay = null
@@ -133,7 +136,7 @@ GLOBAL_LIST_INIT(available_ui_styles, list(
return FALSE
screenmob.client.screen = list()
- screenmob.client.apply_clickcatcher()
+ screenmob.client.update_clickcatcher()
var/display_hud_version = version
if(!display_hud_version) //If 0 or blank, display the next hud version
@@ -193,8 +196,6 @@ GLOBAL_LIST_INIT(available_ui_styles, list(
persistent_inventory_update(screenmob)
screenmob.update_action_buttons(1)
reorganize_alerts()
- screenmob.reload_fullscreen()
- update_parallax_pref(screenmob)
// ensure observers get an accurate and up-to-date view
if (!viewmob)
@@ -204,6 +205,8 @@ GLOBAL_LIST_INIT(available_ui_styles, list(
else if (viewmob.hud_used)
viewmob.hud_used.plane_masters_update()
+ screenmob.reload_rendering()
+
return TRUE
/datum/hud/proc/plane_masters_update()
diff --git a/code/_onclick/hud/human.dm b/code/_onclick/hud/human.dm
index 83b8d7be48..4fa2dd538a 100644
--- a/code/_onclick/hud/human.dm
+++ b/code/_onclick/hud/human.dm
@@ -174,7 +174,7 @@
/datum/hud/human/New(mob/living/carbon/human/owner)
..()
- owner.overlay_fullscreen("see_through_darkness", /atom/movable/screen/fullscreen/see_through_darkness)
+ owner.overlay_fullscreen("see_through_darkness", /atom/movable/screen/fullscreen/special/see_through_darkness)
var/widescreenlayout = FALSE //CIT CHANGE - adds support for different hud layouts depending on widescreen pref
if(owner.client && owner.client.prefs && owner.client.prefs.widescreenpref) //CIT CHANGE - ditto
diff --git a/code/_onclick/hud/parallax.dm b/code/_onclick/hud/parallax.dm
deleted file mode 100755
index a98fec03c4..0000000000
--- a/code/_onclick/hud/parallax.dm
+++ /dev/null
@@ -1,322 +0,0 @@
-
-/datum/hud/proc/create_parallax(mob/viewmob)
- var/mob/screenmob = viewmob || mymob
- var/client/C = screenmob.client
- if (!apply_parallax_pref(viewmob)) //don't want shit computers to crash when specing someone with insane parallax, so use the viewer's pref
- return
-
- if(!length(C.parallax_layers_cached))
- C.parallax_layers_cached = list()
- C.parallax_layers_cached += new /atom/movable/screen/parallax_layer/layer_1(null, C.view)
- C.parallax_layers_cached += new /atom/movable/screen/parallax_layer/layer_2(null, C.view)
- C.parallax_layers_cached += new /atom/movable/screen/parallax_layer/planet(null, C.view)
- if(SSparallax.random_layer)
- C.parallax_layers_cached += new SSparallax.random_layer
- C.parallax_layers_cached += new /atom/movable/screen/parallax_layer/layer_3(null, C.view)
-
- C.parallax_layers = C.parallax_layers_cached.Copy()
-
- if (length(C.parallax_layers) > C.parallax_layers_max)
- C.parallax_layers.len = C.parallax_layers_max
-
- C.screen |= (C.parallax_layers)
- var/atom/movable/screen/plane_master/PM = screenmob.hud_used.plane_masters["[PLANE_SPACE]"]
- if(screenmob != mymob)
- C.screen -= locate(/atom/movable/screen/plane_master/parallax_white) in C.screen
- C.screen += PM
- PM.color = list(
- 0, 0, 0, 0,
- 0, 0, 0, 0,
- 0, 0, 0, 0,
- 1, 1, 1, 1,
- 0, 0, 0, 0
- )
-
-
-/datum/hud/proc/remove_parallax(mob/viewmob)
- var/mob/screenmob = viewmob || mymob
- var/client/C = screenmob.client
- C.screen -= (C.parallax_layers_cached)
- var/atom/movable/screen/plane_master/PM = screenmob.hud_used.plane_masters["[PLANE_SPACE]"]
- if(screenmob != mymob)
- C.screen -= locate(/atom/movable/screen/plane_master/parallax_white) in C.screen
- C.screen += PM
- PM.color = initial(PM.color)
- C.parallax_layers = null
-
-/datum/hud/proc/apply_parallax_pref(mob/viewmob)
- var/mob/screenmob = viewmob || mymob
- var/client/C = screenmob.client
- if(C.prefs)
- var/pref = C.prefs.parallax
- if (isnull(pref))
- pref = PARALLAX_HIGH
- switch(C.prefs.parallax)
- if (PARALLAX_INSANE)
- C.parallax_throttle = FALSE
- C.parallax_layers_max = 5
- return TRUE
-
- if (PARALLAX_MED)
- C.parallax_throttle = PARALLAX_DELAY_MED
- C.parallax_layers_max = 3
- return TRUE
-
- if (PARALLAX_LOW)
- C.parallax_throttle = PARALLAX_DELAY_LOW
- C.parallax_layers_max = 1
- return TRUE
-
- if (PARALLAX_DISABLE)
- return FALSE
-
- //This is high parallax.
- C.parallax_throttle = PARALLAX_DELAY_DEFAULT
- C.parallax_layers_max = 4
- return TRUE
-
-/datum/hud/proc/update_parallax_pref(mob/viewmob)
- remove_parallax(viewmob)
- create_parallax(viewmob)
- update_parallax()
-
-// This sets which way the current shuttle is moving (returns true if the shuttle has stopped moving so the caller can append their animation)
-/datum/hud/proc/set_parallax_movedir(new_parallax_movedir, skip_windups)
- . = FALSE
- var/client/C = mymob.client
- if(new_parallax_movedir == C.parallax_movedir)
- return
- var/animatedir = new_parallax_movedir
- if(new_parallax_movedir == FALSE)
- var/animate_time = 0
- for(var/thing in C.parallax_layers)
- var/atom/movable/screen/parallax_layer/L = thing
- L.icon_state = initial(L.icon_state)
- L.update_o(C.view)
- var/T = PARALLAX_LOOP_TIME / L.speed
- if (T > animate_time)
- animate_time = T
- C.dont_animate_parallax = world.time + min(animate_time, PARALLAX_LOOP_TIME)
- animatedir = C.parallax_movedir
-
- var/matrix/newtransform
- switch(animatedir)
- if(NORTH)
- newtransform = matrix(1, 0, 0, 0, 1, 480)
- if(SOUTH)
- newtransform = matrix(1, 0, 0, 0, 1,-480)
- if(EAST)
- newtransform = matrix(1, 0, 480, 0, 1, 0)
- if(WEST)
- newtransform = matrix(1, 0,-480, 0, 1, 0)
-
- var/shortesttimer
- if(!skip_windups)
- for(var/thing in C.parallax_layers)
- var/atom/movable/screen/parallax_layer/L = thing
-
- var/T = PARALLAX_LOOP_TIME / L.speed
- if (isnull(shortesttimer))
- shortesttimer = T
- if (T < shortesttimer)
- shortesttimer = T
- L.transform = newtransform
- animate(L, transform = matrix(), time = T, easing = QUAD_EASING | (new_parallax_movedir ? EASE_IN : EASE_OUT), flags = ANIMATION_END_NOW)
- if (new_parallax_movedir)
- L.transform = newtransform
- animate(transform = matrix(), time = T) //queue up another animate so lag doesn't create a shutter
-
- C.parallax_movedir = new_parallax_movedir
- if (C.parallax_animate_timer)
- deltimer(C.parallax_animate_timer)
- var/datum/callback/CB = CALLBACK(src, .proc/update_parallax_motionblur, C, animatedir, new_parallax_movedir, newtransform)
- if(skip_windups)
- CB.InvokeAsync()
- else
- C.parallax_animate_timer = addtimer(CB, min(shortesttimer, PARALLAX_LOOP_TIME), TIMER_CLIENT_TIME|TIMER_STOPPABLE)
-
-
-/datum/hud/proc/update_parallax_motionblur(client/C, animatedir, new_parallax_movedir, matrix/newtransform)
- if(!C)
- return
- C.parallax_animate_timer = FALSE
- for(var/thing in C.parallax_layers)
- var/atom/movable/screen/parallax_layer/L = thing
- if (!new_parallax_movedir)
- animate(L)
- continue
-
- var/newstate = initial(L.icon_state)
- var/T = PARALLAX_LOOP_TIME / L.speed
-
- if (newstate in icon_states(L.icon))
- L.icon_state = newstate
- L.update_o(C.view)
-
- L.transform = newtransform
-
- animate(L, transform = matrix(), time = T, loop = -1, flags = ANIMATION_END_NOW)
-
-/datum/hud/proc/update_parallax()
- var/client/C = mymob.client
- var/turf/posobj = get_turf(C.eye)
- if(!posobj)
- return
- var/area/areaobj = posobj.loc
-
- // Update the movement direction of the parallax if necessary (for shuttles)
- set_parallax_movedir(areaobj.parallax_movedir, FALSE)
-
- var/force
- if(!C.previous_turf || (C.previous_turf.z != posobj.z))
- C.previous_turf = posobj
- force = TRUE
-
- if (!force && world.time < C.last_parallax_shift+C.parallax_throttle)
- return
-
- //Doing it this way prevents parallax layers from "jumping" when you change Z-Levels.
- var/offset_x = posobj.x - C.previous_turf.x
- var/offset_y = posobj.y - C.previous_turf.y
-
- if(!offset_x && !offset_y && !force)
- return
-
- var/last_delay = world.time - C.last_parallax_shift
- last_delay = min(last_delay, C.parallax_throttle)
- C.previous_turf = posobj
- C.last_parallax_shift = world.time
-
- for(var/thing in C.parallax_layers)
- var/atom/movable/screen/parallax_layer/L = thing
- L.update_status(mymob)
- if (L.view_sized != C.view)
- L.update_o(C.view)
-
- var/change_x
- var/change_y
-
- if(L.absolute)
- L.offset_x = -(posobj.x - SSparallax.planet_x_offset) * L.speed
- L.offset_y = -(posobj.y - SSparallax.planet_y_offset) * L.speed
- else
- change_x = offset_x * L.speed
- L.offset_x -= change_x
- change_y = offset_y * L.speed
- L.offset_y -= change_y
-
- if(L.offset_x > 240)
- L.offset_x -= 480
- if(L.offset_x < -240)
- L.offset_x += 480
- if(L.offset_y > 240)
- L.offset_y -= 480
- if(L.offset_y < -240)
- L.offset_y += 480
-
-
- if(!areaobj.parallax_movedir && C.dont_animate_parallax <= world.time && (offset_x || offset_y) && abs(offset_x) <= max(C.parallax_throttle/world.tick_lag+1,1) && abs(offset_y) <= max(C.parallax_throttle/world.tick_lag+1,1) && (round(abs(change_x)) > 1 || round(abs(change_y)) > 1))
- L.transform = matrix(1, 0, offset_x*L.speed, 0, 1, offset_y*L.speed)
- animate(L, transform=matrix(), time = last_delay)
-
- L.screen_loc = "CENTER-7:[round(L.offset_x,1)],CENTER-7:[round(L.offset_y,1)]"
-
-/atom/movable/proc/update_parallax_contents()
- if(length(client_mobs_in_contents))
- for(var/thing in client_mobs_in_contents)
- var/mob/M = thing
- if(M?.client && M.hud_used && length(M.client.parallax_layers))
- M.hud_used.update_parallax()
-
-/mob/proc/update_parallax_teleport() //used for arrivals shuttle
- if(client?.eye && hud_used && length(client.parallax_layers))
- var/area/areaobj = get_area(client.eye)
- hud_used.set_parallax_movedir(areaobj.parallax_movedir, TRUE)
-
-/atom/movable/screen/parallax_layer
- icon = 'icons/effects/parallax.dmi'
- var/speed = 1
- var/offset_x = 0
- var/offset_y = 0
- var/view_sized
- var/absolute = FALSE
- blend_mode = BLEND_ADD
- plane = PLANE_SPACE_PARALLAX
- screen_loc = "CENTER-7,CENTER-7"
- mouse_opacity = MOUSE_OPACITY_TRANSPARENT
-
-
-/atom/movable/screen/parallax_layer/Initialize(mapload, view)
- . = ..()
- if (!view)
- view = world.view
- update_o(view)
-
-/atom/movable/screen/parallax_layer/proc/update_o(view)
- if (!view)
- view = world.view
-
- var/list/viewscales = getviewsize(view)
- var/countx = CEILING((viewscales[1]/2)/(480/world.icon_size), 1)+1
- var/county = CEILING((viewscales[2]/2)/(480/world.icon_size), 1)+1
- var/list/new_overlays = new
- for(var/x in -countx to countx)
- for(var/y in -county to county)
- if(x == 0 && y == 0)
- continue
- var/mutable_appearance/texture_overlay = mutable_appearance(icon, icon_state)
- texture_overlay.transform = matrix(1, 0, x*480, 0, 1, y*480)
- new_overlays += texture_overlay
- cut_overlays()
- add_overlay(new_overlays)
- view_sized = view
-
-/atom/movable/screen/parallax_layer/proc/update_status(mob/M)
- return
-
-/atom/movable/screen/parallax_layer/layer_1
- icon_state = "layer1"
- speed = 0.6
- layer = 1
-
-/atom/movable/screen/parallax_layer/layer_2
- icon_state = "layer2"
- speed = 1
- layer = 2
-
-/atom/movable/screen/parallax_layer/layer_3
- icon_state = "layer3"
- speed = 1.4
- layer = 3
-
-/atom/movable/screen/parallax_layer/random
- blend_mode = BLEND_OVERLAY
- speed = 3
- layer = 3
-
-/atom/movable/screen/parallax_layer/random/space_gas
- icon_state = "space_gas"
-
-/atom/movable/screen/parallax_layer/random/space_gas/Initialize(mapload, view)
- . = ..()
- src.add_atom_colour(SSparallax.random_parallax_color, ADMIN_COLOUR_PRIORITY)
-
-/atom/movable/screen/parallax_layer/random/asteroids
- icon_state = "asteroids"
-
-/atom/movable/screen/parallax_layer/planet
- icon_state = "planet"
- blend_mode = BLEND_OVERLAY
- absolute = TRUE //Status of seperation
- speed = 3
- layer = 30
-
-/atom/movable/screen/parallax_layer/planet/update_status(mob/M)
- var/client/C = M.client
- var/turf/posobj = get_turf(C.eye)
- if(!posobj)
- return
- invisibility = is_station_level(posobj.z) ? 0 : INVISIBILITY_ABSTRACT
-
-/atom/movable/screen/parallax_layer/planet/update_o()
- return //Shit won't move
diff --git a/code/_onclick/hud/plane_master.dm b/code/_onclick/hud/plane_master.dm
index bb6a9cd925..d83a81fa45 100644
--- a/code/_onclick/hud/plane_master.dm
+++ b/code/_onclick/hud/plane_master.dm
@@ -124,8 +124,8 @@
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
/atom/movable/screen/plane_master/lighting/backdrop(mob/mymob)
- mymob.overlay_fullscreen("lighting_backdrop_lit", /atom/movable/screen/fullscreen/lighting_backdrop/lit)
- mymob.overlay_fullscreen("lighting_backdrop_unlit", /atom/movable/screen/fullscreen/lighting_backdrop/unlit)
+ mymob.overlay_fullscreen("lighting_backdrop_lit", /atom/movable/screen/fullscreen/special/lighting_backdrop/lit)
+ mymob.overlay_fullscreen("lighting_backdrop_unlit", /atom/movable/screen/fullscreen/special/lighting_backdrop/unlit)
/*!
* This system works by exploiting BYONDs color matrix filter to use layers to handle emissive blockers.
@@ -143,7 +143,6 @@
add_filter("emissives", 1, alpha_mask_filter(render_source = EMISSIVE_RENDER_TARGET, flags = MASK_INVERSE))
add_filter("object_lighting", 2, alpha_mask_filter(render_source = O_LIGHTING_VISUAL_RENDER_TARGET, flags = MASK_INVERSE))
-
/**
* Handles emissive overlays and emissive blockers.
*/
@@ -163,9 +162,10 @@
plane = PLANE_SPACE_PARALLAX
blend_mode = BLEND_MULTIPLY
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
+ render_target = PLANE_SPACE_PARALLAX_RENDER_TARGET
/atom/movable/screen/plane_master/parallax_white
- name = "parallax whitifier plane master"
+ name = "parallax backdrop/space turf plane master"
plane = PLANE_SPACE
/atom/movable/screen/plane_master/camera_static
@@ -174,7 +174,6 @@
appearance_flags = PLANE_MASTER
blend_mode = BLEND_OVERLAY
-
//Reserved to chat messages, so they are still displayed above the field of vision masking.
/atom/movable/screen/plane_master/chat_messages
name = "runechat plane master"
diff --git a/code/_onclick/observer.dm b/code/_onclick/observer.dm
index d431e6d3cd..78fc32480f 100644
--- a/code/_onclick/observer.dm
+++ b/code/_onclick/observer.dm
@@ -11,7 +11,6 @@
// Otherwise jump
else if(A.loc)
forceMove(get_turf(A))
- update_parallax_contents()
/mob/dead/observer/ClickOn(var/atom/A, var/params)
if(check_click_intercept(params,A))
diff --git a/code/game/alternate_appearance.dm b/code/_rendering/atom_huds/alternate_appearance.dm
similarity index 100%
rename from code/game/alternate_appearance.dm
rename to code/_rendering/atom_huds/alternate_appearance.dm
diff --git a/code/datums/hud.dm b/code/_rendering/atom_huds/atom_hud.dm
similarity index 97%
rename from code/datums/hud.dm
rename to code/_rendering/atom_huds/atom_hud.dm
index 1c23d5f2a0..07b403284b 100644
--- a/code/datums/hud.dm
+++ b/code/_rendering/atom_huds/atom_hud.dm
@@ -124,9 +124,3 @@ GLOBAL_LIST_INIT(huds, list(
/mob/dead/new_player/reload_huds()
return
-
-/mob/proc/add_click_catcher()
- client.screen += client.void
-
-/mob/dead/new_player/add_click_catcher()
- return
diff --git a/code/game/data_huds.dm b/code/_rendering/atom_huds/data_huds.dm
similarity index 100%
rename from code/game/data_huds.dm
rename to code/_rendering/atom_huds/data_huds.dm
diff --git a/code/_rendering/clickcatcher/clickcatcher.dm b/code/_rendering/clickcatcher/clickcatcher.dm
new file mode 100644
index 0000000000..524f753e98
--- /dev/null
+++ b/code/_rendering/clickcatcher/clickcatcher.dm
@@ -0,0 +1,74 @@
+/atom/movable/screen/click_catcher
+ icon = 'icons/screen/clickcatcher.dmi'
+ icon_state = "catcher"
+ appearance_flags = TILE_BOUND | NO_CLIENT_COLOR | RESET_TRANSFORM | RESET_COLOR | RESET_ALPHA
+ plane = CLICKCATCHER_PLANE
+ plane = CLICKCATCHER_PLANE
+ mouse_opacity = MOUSE_OPACITY_OPAQUE
+ screen_loc = "CENTER"
+
+/*
+#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/UpdateFill(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
+
+#undef MAX_SAFE_BYOND_ICON_SCALE_TILES
+#undef MAX_SAFE_BYOND_ICON_SCALE_PX
+*/
+
+/atom/movable/screen/click_catcher/proc/UpdateFill(view_size_x, view_size_y)
+ screen_loc = "1,1 to [view_size_x],[view_size_y]"
+
+/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/T = Parse(modifiers["screen-loc"], get_turf(usr.client?.eye || usr), usr.client)
+ params += "&catcher=1"
+ if(T)
+ T.Click(location, control, params)
+ return TRUE
+
+/atom/movable/screen/click_catcher/proc/Parse(scr_loc, turf/origin, client/C)
+ // screen-loc: Pixel coordinates in screen_loc format ("[tile_x]:[pixel_x],[tile_y]:[pixel_y]")
+ if(!scr_loc)
+ return null
+ var/tX = splittext(scr_loc, ",")
+ var/tY = splittext(tX[2], ":")
+ var/tZ = origin.z
+ tY = tY[1]
+ tX = splittext(tX[1], ":")
+ tX = tX[1]
+ var/list/actual_view = getviewsize(C ? C.view : world.view)
+ tX = clamp(origin.x + text2num(tX) - round(actual_view[1] / 2) - 1, 1, world.maxx)
+ tY = clamp(origin.y + text2num(tY) - round(actual_view[2] / 2) - 1, 1, world.maxy)
+ return locate(tX, tY, tZ)
+
+/**
+ * Makes a clickcatcher if necessary, and ensures it's fit to our size.
+ */
+/client/proc/update_clickcatcher(list/view_override)
+ if(!click_catcher)
+ click_catcher = new
+ screen |= click_catcher
+ if(view_override)
+ click_catcher.UpdateFill(view_override[1], view_override[2])
+ else
+ var/list/view_list = getviewsize(view)
+ click_catcher.UpdateFill(view_list[1], view_list[2])
diff --git a/code/_rendering/fullscreen/fullscreen.dm b/code/_rendering/fullscreen/fullscreen.dm
new file mode 100644
index 0000000000..dd8f697891
--- /dev/null
+++ b/code/_rendering/fullscreen/fullscreen.dm
@@ -0,0 +1,234 @@
+/**
+ * Adds a fullscreen overlay
+ *
+ * @params
+ * - category - string - must exist. will overwrite any other screen in this category. defaults to type.
+ * - type - the typepath of the screen
+ * - severity - severity - different screen objects have differing severities
+ */
+/mob/proc/overlay_fullscreen(category, type, severity)
+ ASSERT(type)
+ if(!category)
+ category = type
+ var/atom/movable/screen/fullscreen/screen = fullscreens[category]
+ if (!screen || screen.type != type)
+ // needs to be recreated
+ clear_fullscreen(category, 0)
+ fullscreens[category] = screen = new type
+ screen.SetSeverity(severity)
+ if(client && screen.ShouldShow(src))
+ screen.SetView(client.view)
+ client.screen += screen
+ return screen
+
+/**
+ * Wipes a fullscreen of a certain category
+ *
+ * Second argument is for animation delay.
+ */
+/mob/proc/clear_fullscreen(category, animated = 10)
+ if(!fullscreens)
+ return
+ var/atom/movable/screen/fullscreen/screen = fullscreens[category]
+ fullscreens -= category
+ if(!screen)
+ return
+ if(animated > 0)
+ animate(screen, alpha = 0, time = animated)
+ addtimer(CALLBACK(src, .proc/_remove_fullscreen_direct, screen), animated, TIMER_CLIENT_TIME)
+ else
+ if(client)
+ client.screen -= screen
+ qdel(screen)
+
+/mob/proc/_remove_fullscreen_direct(atom/movable/screen/fullscreen/screen)
+ if(client)
+ client.screen -= screen
+ qdel(screen)
+
+/**
+ * Wipes all fullscreens
+ */
+/mob/proc/wipe_fullscreens()
+ for(var/category in fullscreens)
+ clear_fullscreen(category)
+
+/**
+ * Removes fullscreens from client but not the mob
+ */
+/mob/proc/hide_fullscreens()
+ if(client)
+ for(var/category in fullscreens)
+ client.screen -= fullscreens[category]
+
+/**
+ * Ensures all fullscreens are on client.
+ */
+/mob/proc/reload_fullscreen()
+ if(client)
+ var/atom/movable/screen/fullscreen/screen
+ for(var/category in fullscreens)
+ screen = fullscreens[category]
+ if(screen.ShouldShow(src))
+ screen.SetView(client.view)
+ client.screen |= screen
+ else
+ client.screen -= screen
+
+/atom/movable/screen/fullscreen
+ icon = 'icons/screen/fullscreen_15x15.dmi'
+ icon_state = "default"
+ screen_loc = "CENTER-7,CENTER-7"
+ layer = FULLSCREEN_LAYER
+ plane = FULLSCREEN_PLANE
+ mouse_opacity = MOUSE_OPACITY_TRANSPARENT
+ /// current view we're adapted to
+ var/view_current
+ /// min severity
+ var/severity_min = 0
+ /// max severity
+ var/severity_max = INFINITY
+ /// current severity
+ var/severity = 0
+ /// show this while dead
+ var/show_when_dead = FALSE
+
+/atom/movable/screen/fullscreen/proc/SetSeverity(severity)
+ src.severity = clamp(severity, severity_min, severity_max)
+ icon_state = "[initial(icon_state)][severity]"
+
+/atom/movable/screen/fullscreen/proc/SetView(client_view)
+ view_current = client_view
+
+/atom/movable/screen/fullscreen/proc/ShouldShow(mob/M)
+ if(!show_when_dead && M.stat == DEAD)
+ return FALSE
+ return TRUE
+
+/atom/movable/screen/fullscreen/Destroy()
+ SetSeverity(0)
+ return ..()
+
+/atom/movable/screen/fullscreen/scaled
+ icon = 'icons/screen/fullscreen_15x15.dmi'
+ screen_loc = "CENTER-7,CENTER-7"
+ /// size of sprite in tiles
+ var/size_x = 15
+ /// size of sprite in tiles
+ var/size_y = 15
+
+/atom/movable/screen/fullscreen/scaled/SetView(client_view)
+ if(view_current != client_view)
+ var/list/actualview = getviewsize(client_view)
+ view_current = client_view
+ transform = matrix(actualview[1] / size_x, 0, 0, 0, actualview[2] / size_y, 0)
+ return ..()
+
+/atom/movable/screen/fullscreen/scaled/brute
+ icon_state = "brutedamageoverlay"
+ layer = UI_DAMAGE_LAYER
+ plane = FULLSCREEN_PLANE
+
+/atom/movable/screen/fullscreen/scaled/oxy
+ icon_state = "oxydamageoverlay"
+ layer = UI_DAMAGE_LAYER
+ plane = FULLSCREEN_PLANE
+
+/atom/movable/screen/fullscreen/scaled/crit
+ icon_state = "passage"
+ layer = CRIT_LAYER
+ plane = FULLSCREEN_PLANE
+
+/atom/movable/screen/fullscreen/scaled/crit/vision
+ icon_state = "oxydamageoverlay"
+ layer = BLIND_LAYER
+
+/atom/movable/screen/fullscreen/scaled/blind
+ icon_state = "blackimageoverlay"
+ layer = BLIND_LAYER
+ plane = FULLSCREEN_PLANE
+
+/atom/movable/screen/fullscreen/scaled/curse
+ icon_state = "curse"
+ layer = CURSE_LAYER
+ plane = FULLSCREEN_PLANE
+
+/atom/movable/screen/fullscreen/scaled/impaired
+ icon_state = "impairedoverlay"
+
+/atom/movable/screen/fullscreen/scaled/emergency_meeting
+ icon_state = "emergency_meeting"
+ show_when_dead = TRUE
+ layer = CURSE_LAYER
+ plane = SPLASHSCREEN_PLANE
+
+/atom/movable/screen/fullscreen/tiled/blurry
+ icon = 'icons/mob/screen_gen.dmi'
+ screen_loc = "WEST,SOUTH to EAST,NORTH"
+ icon_state = "cloudy"
+
+/atom/movable/screen/fullscreen/tiled/flash
+ icon = 'icons/mob/screen_gen.dmi'
+ screen_loc = "WEST,SOUTH to EAST,NORTH"
+ icon_state = "flash"
+
+/atom/movable/screen/fullscreen/tiled/flash/static
+ icon = 'icons/mob/screen_gen.dmi'
+ screen_loc = "WEST,SOUTH to EAST,NORTH"
+ icon_state = "noise"
+
+/atom/movable/screen/fullscreen/tiled/high
+ icon = 'icons/mob/screen_gen.dmi'
+ screen_loc = "WEST,SOUTH to EAST,NORTH"
+ icon_state = "druggy"
+
+/atom/movable/screen/fullscreen/tiled/color_vision
+ icon = 'icons/mob/screen_gen.dmi'
+ screen_loc = "WEST,SOUTH to EAST,NORTH"
+ icon_state = "flash"
+ alpha = 80
+
+/atom/movable/screen/fullscreen/tiled/color_vision/green
+ color = "#00ff00"
+
+/atom/movable/screen/fullscreen/tiled/color_vision/red
+ color = "#ff0000"
+
+/atom/movable/screen/fullscreen/tiled/color_vision/blue
+ color = "#0000ff"
+
+/atom/movable/screen/fullscreen/tiled/cinematic_backdrop
+ icon = 'icons/mob/screen_gen.dmi'
+ screen_loc = "WEST,SOUTH to EAST,NORTH"
+ icon_state = "flash"
+ plane = SPLASHSCREEN_PLANE
+ layer = SPLASHSCREEN_LAYER - 1
+ color = "#000000"
+ show_when_dead = TRUE
+
+/atom/movable/screen/fullscreen/special/lighting_backdrop
+ icon = 'icons/mob/screen_gen.dmi'
+ icon_state = "flash"
+ transform = matrix(200, 0, 0, 0, 200, 0)
+ plane = LIGHTING_PLANE
+ blend_mode = BLEND_OVERLAY
+ show_when_dead = TRUE
+
+//Provides darkness to the back of the lighting plane
+/atom/movable/screen/fullscreen/special/lighting_backdrop/lit
+ invisibility = INVISIBILITY_LIGHTING
+ layer = BACKGROUND_LAYER+21
+ color = "#000"
+ show_when_dead = TRUE
+
+//Provides whiteness in case you don't see lights so everything is still visible
+/atom/movable/screen/fullscreen/special/lighting_backdrop/unlit
+ layer = BACKGROUND_LAYER+20
+ show_when_dead = TRUE
+
+/atom/movable/screen/fullscreen/special/see_through_darkness
+ icon_state = "nightvision"
+ plane = LIGHTING_PLANE
+ layer = LIGHTING_LAYER
+ blend_mode = BLEND_ADD
+ show_when_dead = TRUE
diff --git a/code/_rendering/mob.dm b/code/_rendering/mob.dm
new file mode 100644
index 0000000000..49095d0910
--- /dev/null
+++ b/code/_rendering/mob.dm
@@ -0,0 +1,21 @@
+/**
+ * initializes screen rendering. call on mob new
+ */
+/mob/proc/init_rendering()
+
+/**
+ * loads screen rendering. call on mob login
+ */
+/mob/proc/reload_rendering()
+ if(!client.parallax_holder)
+ client.CreateParallax()
+ else
+ client.parallax_holder.Reset(force = TRUE)
+ client.update_clickcatcher()
+ reload_fullscreen()
+
+/**
+ * destroys screen rendering. call on mob del
+ */
+/mob/proc/dispose_rendering()
+ wipe_fullscreens()
diff --git a/code/_rendering/parallax/parallax.dm b/code/_rendering/parallax/parallax.dm
new file mode 100644
index 0000000000..dbd27ae762
--- /dev/null
+++ b/code/_rendering/parallax/parallax.dm
@@ -0,0 +1,28 @@
+/**
+ * Holds parallax information.
+ */
+/datum/parallax
+ /// List of parallax objects - these are cloned to a parallax holder using Clone on each.
+ var/list/atom/movable/screen/parallax_layer/objects
+ /// Parallax layers
+ var/layers = 0
+
+/datum/parallax/New(create_objects = TRUE)
+ if(create_objects)
+ objects = CreateObjects()
+ layers = objects.len
+
+/datum/parallax/Destroy()
+ QDEL_LIST(objects)
+ return ..()
+
+/**
+ * Gets a new version of the objects inside - used when applying to a holder.
+ */
+/datum/parallax/proc/GetObjects()
+ . = list()
+ for(var/atom/movable/screen/parallax_layer/layer in objects)
+ . += layer.Clone()
+
+/datum/parallax/proc/CreateObjects()
+ . = objects = list()
diff --git a/code/_rendering/parallax/parallax_holder.dm b/code/_rendering/parallax/parallax_holder.dm
new file mode 100644
index 0000000000..8a9e4b71a9
--- /dev/null
+++ b/code/_rendering/parallax/parallax_holder.dm
@@ -0,0 +1,311 @@
+/**
+ * # Parallax holders
+ *
+ * Holds all the information about a client's parallax
+ *
+ * Not on mob because parallax is area based, not mob based.
+ *
+ * How parallax works:
+ * - Layers - normal layers, scroll with movement to relative position, can scroll
+ * - Absolute - absolute layers, scroll with movement to absolute position, cannot scroll
+ * - Vis - vis_contents-like model - things in this are directly applied and get no processing whatsoever. Things like overmap ships can use this.
+ */
+/datum/parallax_holder
+ /// Client that owns us
+ var/client/owner
+ /// The parallax object we're currently rendering
+ var/datum/parallax/parallax
+ /// Eye we were last anchored to - used to detect eye changes
+ var/atom/cached_eye
+ /// force this eye as the "real" eye - useful for secondary maps
+ var/atom/forced_eye
+ /// last turf loc
+ var/turf/last
+ /// last area - for parallax scrolling/loop animations
+ var/area/last_area
+ /// Holder object for vis
+ var/atom/movable/screen/parallax_vis/vis_holder
+ /// are we not on the main map? if so, put map id here
+ var/secondary_map
+ /// all layers
+ var/list/atom/movable/screen/parallax_layer/layers
+ /// vis contents
+ var/list/atom/movable/vis
+ /// currently scrolling?
+ var/scrolling = FALSE
+ /// current scroll speed in DS per scroll
+ var/scroll_speed
+ /// current scroll turn - applied after angle. if angle is 0 (picture moving north) and turn is 90, it would be like if you turned your viewport 90 deg clockwise.
+ var/scroll_turn
+ /// override planemaster we manipulate for turning and other effects
+ var/atom/movable/screen/plane_master/parallax/planemaster_override
+
+/datum/parallax_holder/New(client/C, secondary_map, forced_eye, planemaster_override)
+ owner = C
+ if(!owner)
+ CRASH("No client")
+ src.secondary_map = secondary_map
+ src.forced_eye = forced_eye
+ src.planemaster_override = planemaster_override
+ Reset()
+
+/datum/parallax_holder/Destroy()
+ if(owner)
+ if(owner.parallax_holder == src)
+ owner.parallax_holder = null
+ Remove()
+ HardResetAnimations()
+ QDEL_NULL(vis_holder)
+ QDEL_NULL(parallax)
+ layers = null
+ vis = null
+ last = null
+ forced_eye = cached_eye = null
+ owner = null
+ return ..()
+
+/datum/parallax_holder/proc/Reset(auto_z_change, force)
+ if(!(cached_eye = Eye()))
+ // if no eye, tear down
+ last = cached_eye = last_area = null
+ SetParallax(null, null, auto_z_change)
+ return
+ // first, check loc
+ var/turf/T = get_turf(cached_eye)
+ if(!T)
+ // if in nullspace, tear down
+ last = cached_eye = last_area = null
+ SetParallax(null, null, auto_z_change)
+ return
+ // set last loc and eye
+ last = T
+ last_area = T.loc
+ // rebuild parallax
+ SetParallax(SSparallax.get_parallax_datum(T.z), null, auto_z_change, force)
+ // hard reset positions to correct positions
+ for(var/atom/movable/screen/parallax_layer/L in layers)
+ L.ResetPosition(T.x, T.y)
+
+// better updates via client_mobs_in_contents can be created again when important recursive contents is ported!
+/datum/parallax_holder/proc/Update(full)
+ if(!full && !cached_eye || (get_turf(cached_eye) == last))
+ return
+ if(!owner) // why are we here
+ if(!QDELETED(src))
+ qdel(src)
+ return
+ if(cached_eye != Eye())
+ // eye mismatch, reset
+ Reset()
+ return
+ var/turf/T = get_turf(cached_eye)
+ if(!last || T.z != last.z)
+ // z mismatch, reset
+ Reset()
+ return
+ // get rel offsets
+ var/rel_x = T.x - last.x
+ var/rel_y = T.y - last.y
+ // set last
+ last = T
+ // move
+ for(var/atom/movable/screen/parallax_layer/L in layers)
+ L.RelativePosition(T.x, T.y, rel_x, rel_y)
+ // process scrolling/movedir
+ if(last_area != T.loc)
+ last_area = T.loc
+ UpdateMotion()
+
+/**
+ * Gets the eye we should be centered on
+ */
+/datum/parallax_holder/proc/Eye()
+ return forced_eye || owner?.eye
+
+/**
+ * Gets the base parallax planemaster for things like turning
+ */
+/datum/parallax_holder/proc/GetPlaneMaster()
+ return planemaster_override || (owner && (locate(/atom/movable/screen/plane_master/parallax) in owner?.screen))
+
+/**
+ * Syncs us to our parallax objects. Does NOT check if we should have those objects, that's Reset()'s job.
+ *
+ * Doesn't move/update positions/screen locs either.
+ *
+ * Also ensures movedirs are correct for the eye's pos.
+ */
+/datum/parallax_holder/proc/Sync(auto_z_change, force)
+ layers = list()
+ for(var/atom/movable/screen/parallax_layer/L in parallax.objects)
+ layers += L
+ L.map_id = secondary_map
+ if(!istype(vis_holder))
+ vis_holder = new /atom/movable/screen/parallax_vis
+ var/turf/T = get_turf(cached_eye)
+ vis_holder.vis_contents = vis = T? SSparallax.get_parallax_vis_contents(T.z) : list()
+ UpdateMotion(auto_z_change, force)
+
+/**
+ * Updates motion if needed
+ */
+/datum/parallax_holder/proc/UpdateMotion(auto_z_change, force)
+ var/turf/T = get_turf(cached_eye)
+ if(!T)
+ if(scroll_speed || scroll_turn)
+ HardResetAnimations()
+ return
+ var/list/ret = SSparallax.get_parallax_motion(T.z)
+ if(ret)
+ Animation(ret[1], ret[2], auto_z_change? 0 : ret[3], auto_z_change? 0 : ret[4], force)
+ else
+ var/area/A = T.loc
+ Animation(A.parallax_move_speed, A.parallax_move_angle, auto_z_change? 0 : null, auto_z_change? 0 : null, force)
+
+/datum/parallax_holder/proc/Apply(client/C = owner)
+ if(QDELETED(C))
+ return
+ . = list()
+ for(var/atom/movable/screen/parallax_layer/L in layers)
+ if(L.parallax_intensity > owner.prefs.parallax)
+ continue
+ if(!L.ShouldSee(C, last))
+ continue
+ L.SetView(C.view, TRUE)
+ . |= L
+ C.screen |= .
+ if(!secondary_map)
+ var/atom/movable/screen/plane_master/parallax_white/PM = locate() in C.screen
+ if(PM)
+ PM.color = list(
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 1, 1, 1, 1,
+ 0, 0, 0, 0
+ )
+
+/datum/parallax_holder/proc/Remove(client/C = owner)
+ if(QDELETED(C))
+ return
+ C.screen -= layers
+ if(!secondary_map)
+ var/atom/movable/screen/plane_master/parallax_white/PM = locate() in C.screen
+ if(PM)
+ PM.color = initial(PM.color)
+
+/datum/parallax_holder/proc/SetParallaxType(path)
+ if(!ispath(path, /datum/parallax))
+ CRASH("Invalid path")
+ SetParallax(new path)
+
+/datum/parallax_holder/proc/SetParallax(datum/parallax/P, delete_old = TRUE, auto_z_change, force)
+ if(P == parallax)
+ return
+ Remove()
+ if(delete_old && istype(parallax) && !QDELETED(parallax))
+ qdel(parallax)
+ HardResetAnimations()
+ parallax = P
+ if(!parallax)
+ return
+ Sync(auto_z_change, force)
+ Apply()
+
+/**
+ * Runs a modifier to parallax as an animation.
+ *
+ * @params
+ * speed - ds per loop
+ * turn - angle clockwise from north to turn the motion to
+ * windup - ds to spend on windups. 0 for immediate.
+ * turn_speed - ds to spend on turning. 0 for immediate.
+ */
+/datum/parallax_holder/proc/Animation(speed = 25, turn = 0, windup = speed, turn_speed = speed, force)
+ // Parallax doesn't currently use this method of rotating.
+
+ // #if !PARALLAX_ROTATION_ANIMATIONS
+ // turn_speed = 0
+ // #endif
+
+ if(speed == 0)
+ StopScrolling(turn = turn, time = windup)
+ return
+ // if(turn != scroll_turn && GetPlaneMaster())
+ // // first handle turn. we turn the planemaster
+ // var/matrix/turn_transform = matrix()
+ // turn_transform.Turn(turn)
+ // scroll_turn = turn
+ // animate(GetPlaneMaster(), transform = turn_transform, time = turn_speed, easing = QUAD_EASING | EASE_IN, flags = ANIMATION_END_NOW | ANIMATION_LINEAR_TRANSFORM)
+ if(scroll_speed == speed && !force)
+ // we're done
+ return
+ // speed diff?
+ scroll_speed = speed
+ scrolling = TRUE
+ // always scroll from north; turn handles everything
+ for(var/atom/movable/screen/parallax_layer/P in layers)
+ if(P.absolute)
+ continue
+ var/matrix/translate_matrix = matrix()
+ translate_matrix.Translate(cos(turn) * 480, sin(turn) * 480)
+ var/matrix/target_matrix = matrix()
+ var/move_speed = speed * P.speed
+ // do the first segment by shifting down one screen
+ P.transform = translate_matrix
+ animate(P, transform = target_matrix, time = move_speed, easing = QUAD_EASING|EASE_IN, flags = ANIMATION_END_NOW)
+ // queue up another incase lag makes QueueLoop not fire on time, this time by shifting up
+ animate(transform = translate_matrix, time = 0)
+ animate(transform = target_matrix, time = move_speed)
+ P.QueueLoop(move_speed, speed * P.speed, translate_matrix, target_matrix)
+
+/**
+ * Smoothly stops the animation, turning to a certain angle as needed.
+ */
+/datum/parallax_holder/proc/StopScrolling(turn = 0, time = 30)
+ // reset turn
+ if(turn != scroll_turn && GetPlaneMaster())
+ var/matrix/turn_transform = matrix()
+ turn_transform.Turn(turn)
+ scroll_turn = turn
+ animate(GetPlaneMaster(), transform = turn_transform, time = time, easing = QUAD_EASING | EASE_OUT, flags = ANIMATION_END_NOW | ANIMATION_LINEAR_TRANSFORM)
+ if(scroll_speed == 0)
+ // we're done
+ scrolling = FALSE
+ scroll_speed = 0
+ return
+ scrolling = FALSE
+ scroll_speed = 0
+ // someone can do the math for "stop after a smooth iteration" later.
+ for(var/atom/movable/screen/parallax_layer/P in layers)
+ if(P.absolute)
+ continue
+ P.CancelAnimation()
+ var/matrix/translate_matrix = matrix()
+ translate_matrix.Translate(cos(turn) * 480, sin(turn) * 480)
+ P.transform = translate_matrix
+ animate(P, transform = matrix(), time = time, easing = QUAD_EASING | EASE_OUT)
+
+/**
+ * fully resets animation state
+ */
+/datum/parallax_holder/proc/HardResetAnimations()
+ // reset vars
+ scroll_turn = 0
+ scroll_speed = 0
+ scrolling = FALSE
+ // reset turn
+ if(GetPlaneMaster())
+ animate(GetPlaneMaster(), transform = matrix(), time = 0, flags = ANIMATION_END_NOW)
+ // reset objects
+ for(var/atom/movable/screen/parallax_layer/P in layers)
+ if(P.absolute)
+ continue
+ P.CancelAnimation()
+ animate(P, transform = matrix(), time = 0, flags = ANIMATION_END_NOW)
+
+/client/proc/CreateParallax()
+ if(!parallax_holder)
+ parallax_holder = new(src)
+/atom/movable/screen/parallax_vis
+ screen_loc = "CENTER,CENTER"
diff --git a/code/_rendering/parallax/parallax_object.dm b/code/_rendering/parallax/parallax_object.dm
new file mode 100644
index 0000000000..8307fc5f08
--- /dev/null
+++ b/code/_rendering/parallax/parallax_object.dm
@@ -0,0 +1,137 @@
+
+/atom/movable/screen/parallax_layer
+ icon = 'icons/screen/parallax.dmi'
+ blend_mode = BLEND_ADD
+ plane = PLANE_SPACE_PARALLAX
+ screen_loc = "CENTER-7,CENTER-7"
+ mouse_opacity = MOUSE_OPACITY_TRANSPARENT
+ appearance_flags = PIXEL_SCALE | KEEP_TOGETHER
+
+ // notice - all parallax layers are 15x15 tiles. They roll over every 240 pixels.
+ /// pixel x/y shift per real x/y
+ var/speed = 1
+ /// current cached offset x
+ var/offset_x = 0
+ /// current cached offset y
+ var/offset_y = 0
+ /// normal centered x
+ var/center_x = 0
+ /// normal centered y
+ var/center_y = 0
+ /// absolute - always determine shift x/y as a function of real x/y instead of allowing for relative scroll.
+ var/absolute = FALSE
+ /// parallax level required to see this
+ var/parallax_intensity = PARALLAX_INSANE
+ /// current view we're adapted to
+ var/view_current
+ /// dynamic self tile - tile to our view size. set this to false for static parallax layers.
+ var/dynamic_self_tile = TRUE
+ /// map id
+ var/map_id
+ /// queued animation timerid
+ var/queued_animation
+
+/atom/movable/screen/parallax_layer/proc/ResetPosition(x, y)
+ // remember that our offsets/directiosn are relative to the player's viewport
+ // this means we need to scroll reverse to them.
+ offset_x = -(center_x + speed * x)
+ offset_y = -(center_y + speed * y)
+ if(!absolute)
+ if(offset_x > 240)
+ offset_x -= 480
+ if(offset_x < -240)
+ offset_x += 480
+ if(offset_y > 240)
+ offset_y -= 480
+ if(offset_y < -240)
+ offset_y += 480
+ screen_loc = "[map_id && "[map_id]:"]CENTER-7:[round(offset_x,1)],CENTER-7:[round(offset_y,1)]"
+
+/atom/movable/screen/parallax_layer/proc/RelativePosition(x, y, rel_x, rel_y)
+ if(absolute)
+ return ResetPosition(x, y)
+ offset_x -= rel_x * speed
+ offset_y -= rel_y * speed
+ if(offset_x > 240)
+ offset_x -= 480
+ if(offset_x < -240)
+ offset_x += 480
+ if(offset_y > 240)
+ offset_y -= 480
+ if(offset_y < -240)
+ offset_y += 480
+ screen_loc = "[map_id && "[map_id]:"]CENTER-7:[round(offset_x,1)],CENTER-7:[round(offset_y,1)]"
+
+/atom/movable/screen/parallax_layer/proc/SetView(client_view = world.view, force_update = FALSE)
+ if(view_current == client_view && !force_update)
+ return
+ view_current = client_view
+ if(!dynamic_self_tile)
+ return
+ var/list/real_view = getviewsize(client_view)
+ var/count_x = CEILING((real_view[1] / 2) / 15, 1) + 1
+ var/count_y = CEILING((real_view[2] / 2) / 15, 1) + 1
+ var/list/new_overlays = GetOverlays()
+ for(var/x in -count_x to count_x)
+ for(var/y in -count_y to count_y)
+ if(!x && !y)
+ continue
+ var/mutable_appearance/clone = new
+ // appearance clone
+ clone.icon = icon
+ clone.icon_state = icon_state
+ clone.overlays = GetOverlays()
+ // do NOT inherit our overlays! parallax layers should never have overlays,
+ // because if it inherited us it'll result in exponentially increasing overlays
+ // due to cut_overlays() above over there being a queue operation and not instant!
+ // clone.overlays = list()
+ // currently instantly using overlays =.
+ // clone.blend_mode = blend_mode
+ // clone.mouse_opacity = mouse_opacity
+ // clone.plane = plane
+ // clone.layer = layer
+ // shift to position
+ clone.transform = matrix(1, 0, x * 480, 0, 1, y * 480)
+ new_overlays += clone
+ overlays = new_overlays
+
+/atom/movable/screen/parallax_layer/proc/ShouldSee(client/C, atom/location)
+ return TRUE
+
+/**
+ * Return "natural" overlays, as we're goin to do some fuckery to overlays above.
+ */
+/atom/movable/screen/parallax_layer/proc/GetOverlays()
+ return list()
+
+/atom/movable/screen/parallax_layer/proc/Clone()
+ var/atom/movable/screen/parallax_layer/layer = new type
+ layer.speed = speed
+ layer.offset_x = offset_x
+ layer.offset_y = offset_y
+ layer.absolute = absolute
+ layer.parallax_intensity = parallax_intensity
+ layer.view_current = view_current
+ layer.appearance = appearance
+
+/atom/movable/screen/parallax_layer/proc/default_x()
+ return center_x
+
+/atom/movable/screen/parallax_layer/proc/default_y()
+ return center_y
+
+/atom/movable/screen/parallax_layer/proc/QueueLoop(delay, speed, matrix/translate_matrix, matrix/target_matrix)
+ if(queued_animation)
+ CancelAnimation()
+ queued_animation = addtimer(CALLBACK(src, .proc/_loop, speed, translate_matrix, target_matrix), delay, TIMER_STOPPABLE)
+
+/atom/movable/screen/parallax_layer/proc/_loop(speed, matrix/translate_matrix = matrix(1, 0, 0, 0, 1, 480), matrix/target_matrix = matrix())
+ transform = translate_matrix
+ animate(src, transform = target_matrix, time = speed, loop = -1)
+ animate(transform = translate_matrix, time = 0)
+ queued_animation = null
+
+/atom/movable/screen/parallax_layer/proc/CancelAnimation()
+ if(queued_animation)
+ deltimer(queued_animation)
+ queued_animation = null
diff --git a/code/_rendering/parallax/types/space.dm b/code/_rendering/parallax/types/space.dm
new file mode 100644
index 0000000000..38ad62aede
--- /dev/null
+++ b/code/_rendering/parallax/types/space.dm
@@ -0,0 +1,66 @@
+/datum/parallax/space
+ var/static/planet_offset_x = rand(100, 160)
+ var/static/planet_offset_y = rand(100, 160)
+ var/static/random_layer = pickweightAllowZero(list(
+ /atom/movable/screen/parallax_layer/space/random/asteroids = 35,
+ /atom/movable/screen/parallax_layer/space/random/space_gas = 35,
+ null = 30
+ ))
+ var/static/random_gas_color = pick(COLOR_TEAL, COLOR_GREEN, COLOR_YELLOW, COLOR_CYAN, COLOR_ORANGE, COLOR_PURPLE)
+
+/datum/parallax/space/CreateObjects()
+ . = ..()
+ . += new /atom/movable/screen/parallax_layer/space/layer_1
+ . += new /atom/movable/screen/parallax_layer/space/layer_2
+ . += new /atom/movable/screen/parallax_layer/space/layer_3
+ var/atom/movable/screen/parallax_layer/space/planet/P = new
+ P.pixel_x = planet_offset_x
+ P.pixel_y = planet_offset_y
+ . += P
+ if(random_layer)
+ . += new random_layer
+ if(ispath(random_layer, /atom/movable/screen/parallax_layer/space/random/space_gas))
+ var/atom/movable/screen/parallax_layer/space/random/space_gas/SG = locate(random_layer) in objects
+ SG.add_atom_colour(random_gas_color, ADMIN_COLOUR_PRIORITY)
+
+/atom/movable/screen/parallax_layer/space/layer_1
+ icon_state = "layer1"
+ speed = 0.6
+ layer = 1
+ parallax_intensity = PARALLAX_LOW
+
+/atom/movable/screen/parallax_layer/space/layer_2
+ icon_state = "layer2"
+ speed = 1
+ layer = 2
+ parallax_intensity = PARALLAX_MED
+
+/atom/movable/screen/parallax_layer/space/layer_3
+ icon_state = "layer3"
+ speed = 1.4
+ layer = 3
+ parallax_intensity = PARALLAX_HIGH
+
+/atom/movable/screen/parallax_layer/space/random
+ blend_mode = BLEND_OVERLAY
+ speed = 3
+ layer = 3
+ parallax_intensity = PARALLAX_INSANE
+
+/atom/movable/screen/parallax_layer/space/random/space_gas
+ icon_state = "space_gas"
+
+/atom/movable/screen/parallax_layer/space/random/asteroids
+ icon_state = "asteroids"
+
+/atom/movable/screen/parallax_layer/space/planet
+ icon_state = "planet"
+ blend_mode = BLEND_OVERLAY
+ absolute = TRUE //Status of seperation
+ speed = 3
+ layer = 30
+ dynamic_self_tile = FALSE
+
+/atom/movable/screen/parallax_layer/space/planet/ShouldSee(client/C, atom/location)
+ var/turf/T = get_turf(location)
+ return ..() && T && is_station_level(T.z)
diff --git a/code/controllers/subsystem/atoms.dm b/code/controllers/subsystem/atoms.dm
index 02aad6dec3..8a84251341 100644
--- a/code/controllers/subsystem/atoms.dm
+++ b/code/controllers/subsystem/atoms.dm
@@ -14,6 +14,9 @@ SUBSYSTEM_DEF(atoms)
var/list/BadInitializeCalls = list()
+ /// Atoms that will be deleted once the subsystem is initialized
+ var/list/queued_deletions = list()
+
initialized = INITIALIZATION_INSSATOMS
/datum/controller/subsystem/atoms/Initialize(timeofday)
@@ -62,6 +65,12 @@ SUBSYSTEM_DEF(atoms)
testing("Late initialized [late_loaders.len] atoms")
late_loaders.Cut()
+ for (var/queued_deletion in queued_deletions)
+ qdel(queued_deletion)
+
+ testing("[queued_deletions.len] atoms were queued for deletion.")
+ queued_deletions.Cut()
+
/// Init this specific atom
/datum/controller/subsystem/atoms/proc/InitAtom(atom/A, list/arguments)
var/the_type = A.type
@@ -151,6 +160,14 @@ SUBSYSTEM_DEF(atoms)
if(fails & BAD_INIT_SLEPT)
. += "- Slept during Initialize()\n"
+/// Prepares an atom to be deleted once the atoms SS is initialized.
+/datum/controller/subsystem/atoms/proc/prepare_deletion(atom/target)
+ if (initialized == INITIALIZATION_INNEW_REGULAR)
+ // Atoms SS has already completed, just kill it now.
+ qdel(target)
+ else
+ queued_deletions += WEAKREF(target)
+
/datum/controller/subsystem/atoms/Shutdown()
var/initlog = InitLog()
if(initlog)
diff --git a/code/controllers/subsystem/parallax.dm b/code/controllers/subsystem/parallax.dm
index 86bd517602..248b33d86d 100644
--- a/code/controllers/subsystem/parallax.dm
+++ b/code/controllers/subsystem/parallax.dm
@@ -5,21 +5,6 @@ SUBSYSTEM_DEF(parallax)
priority = FIRE_PRIORITY_PARALLAX
runlevels = RUNLEVEL_LOBBY | RUNLEVELS_DEFAULT
var/list/currentrun
- var/planet_x_offset = 128
- var/planet_y_offset = 128
- var/random_layer
- var/random_parallax_color
-
-
-//These are cached per client so needs to be done asap so people joining at roundstart do not miss these.
-/datum/controller/subsystem/parallax/PreInit()
- . = ..()
- if(prob(70)) //70% chance to pick a special extra layer
- random_layer = pick(/atom/movable/screen/parallax_layer/random/space_gas, /atom/movable/screen/parallax_layer/random/asteroids)
- random_parallax_color = pick(COLOR_TEAL, COLOR_GREEN, COLOR_YELLOW, COLOR_CYAN, COLOR_ORANGE, COLOR_PURPLE)//Special color for random_layer1. Has to be done here so everyone sees the same color. [COLOR_SILVER]
- planet_y_offset = rand(100, 160)
- planet_x_offset = rand(100, 160)
-
/datum/controller/subsystem/parallax/fire(resumed = FALSE)
if (!resumed)
@@ -28,28 +13,82 @@ SUBSYSTEM_DEF(parallax)
//cache for sanic speed (lists are references anyways)
var/list/currentrun = src.currentrun
- while(length(currentrun))
- var/client/processing_client = currentrun[currentrun.len]
- currentrun.len--
- if (QDELETED(processing_client) || !processing_client.eye)
+ if(times_fired % 5) // lazy tick
+ while(length(currentrun))
+ var/client/processing_client = currentrun[currentrun.len]
+ currentrun.len--
+ if (QDELETED(processing_client) || !processing_client.eye)
+ if (MC_TICK_CHECK)
+ return
+ continue
+ processing_client.parallax_holder?.Update()
if (MC_TICK_CHECK)
return
- continue
-
- var/atom/movable/movable_eye = processing_client.eye
- if(!istype(movable_eye))
- continue
-
- for (movable_eye; isloc(movable_eye.loc) && !isturf(movable_eye.loc); movable_eye = movable_eye.loc);
-
- if(movable_eye == processing_client.movingmob)
+ else // full tick
+ while(length(currentrun))
+ var/client/processing_client = currentrun[currentrun.len]
+ currentrun.len--
+ if (QDELETED(processing_client) || !processing_client.eye)
+ if (MC_TICK_CHECK)
+ return
+ continue
+ processing_client.parallax_holder?.Update(TRUE)
if (MC_TICK_CHECK)
return
- continue
- if(!isnull(processing_client.movingmob))
- LAZYREMOVE(processing_client.movingmob.client_mobs_in_contents, processing_client.mob)
- LAZYADD(movable_eye.client_mobs_in_contents, processing_client.mob)
- processing_client.movingmob = movable_eye
- if (MC_TICK_CHECK)
- return
+
currentrun = null
+
+/**
+ * Gets parallax type for zlevel.
+ */
+/datum/controller/subsystem/parallax/proc/get_parallax_type(z)
+ return /datum/parallax/space
+
+/**
+ * Gets parallax for zlevel.
+ */
+/datum/controller/subsystem/parallax/proc/get_parallax_datum(z)
+ var/datum_type = get_parallax_type(z)
+ return new datum_type
+
+/**
+ * Gets parallax added vis contents for zlevel
+ */
+/datum/controller/subsystem/parallax/proc/get_parallax_vis_contents(z)
+ return list()
+
+/**
+ * Gets parallax motion for a zlevel
+ *
+ * Returns null or list(speed, dir deg clockwise from north, windup, turnrate)
+ * THE RETURNED LIST MUST BE A 4-TUPLE, OR PARALLAX WILL CRASH.
+ * DO NOT SCREW WITH THIS UNLESS YOU KNOW WHAT YOU ARE DOING.
+ *
+ * This will override area motion
+ */
+/datum/controller/subsystem/parallax/proc/get_parallax_motion(z)
+ return null
+
+/**
+ * updates all parallax for clients on a z
+ */
+/datum/controller/subsystem/parallax/proc/update_clients_on_z(z)
+ for(var/client/C in GLOB.clients)
+ if(C.mob.z == z)
+ C.parallax_holder?.Update()
+
+/**
+ * resets all parallax for clients on a z
+ */
+/datum/controller/subsystem/parallax/proc/reset_clients_on_z(z)
+ for(var/client/C in GLOB.clients)
+ if(C.mob.z == z)
+ C.parallax_holder?.Reset()
+
+/**
+ * updates motion of all clients on z
+ */
+/datum/controller/subsystem/parallax/proc/update_z_motion(z)
+ for(var/client/C in GLOB.clients)
+ if(C.mob.z == z)
+ C.parallax_holder?.UpdateMotion()
diff --git a/code/controllers/subsystem/shuttle.dm b/code/controllers/subsystem/shuttle.dm
index 6af79d4d14..da295d70e2 100644
--- a/code/controllers/subsystem/shuttle.dm
+++ b/code/controllers/subsystem/shuttle.dm
@@ -513,7 +513,9 @@ SUBSYSTEM_DEF(shuttle)
if(!midpoint)
return FALSE
var/area/shuttle/transit/A = new()
- A.parallax_movedir = travel_dir
+ A.parallax_moving = TRUE
+ A.parallax_move_angle = dir2angle(travel_dir)
+ A.parallax_move_speed = M.parallax_speed
A.contents = proposal.reserved_turfs
var/obj/docking_port/stationary/transit/new_transit_dock = new(midpoint)
new_transit_dock.reserved_area = proposal
diff --git a/code/controllers/subsystem/ticker.dm b/code/controllers/subsystem/ticker.dm
index 6f1fe6d8d0..05bef50714 100755
--- a/code/controllers/subsystem/ticker.dm
+++ b/code/controllers/subsystem/ticker.dm
@@ -603,8 +603,10 @@ SUBSYSTEM_DEF(ticker)
news_message = "The burst of energy released near [station_name()] has been confirmed as merely a test of a new weapon. However, due to an unexpected mechanical error, their communications system has been knocked offline."
if(SHUTTLE_HIJACK)
news_message = "During routine evacuation procedures, the emergency shuttle of [station_name()] had its navigation protocols corrupted and went off course, but was recovered shortly after."
- if(GANG_VICTORY)
- news_message = "Company officials reaffirmed that sudden deployments of special forces are not in any way connected to rumors of [station_name()] being covered in graffiti."
+ if(GANG_OPERATING)
+ news_message = "The company would like to state that any rumors of criminal organizing on board stations such as [station_name()] are falsehoods, and not to be emulated."
+ if(GANG_DESTROYED)
+ news_message = "The crew of [station_name()] would like to thank the Spinward Stellar Coalition Police Department for quickly resolving a minor terror threat to the station."
if(SSblackbox.first_death)
var/list/ded = SSblackbox.first_death
diff --git a/code/datums/cinematic.dm b/code/datums/cinematic.dm
index 2a8a0ea98a..6052b84364 100644
--- a/code/datums/cinematic.dm
+++ b/code/datums/cinematic.dm
@@ -101,7 +101,7 @@
if(!C)
return
watching += C
- M.overlay_fullscreen("cinematic",/atom/movable/screen/fullscreen/cinematic_backdrop)
+ M.overlay_fullscreen("cinematic",/atom/movable/screen/fullscreen/tiled/cinematic_backdrop)
C.screen += screen
//Sound helper
diff --git a/code/datums/components/tackle.dm b/code/datums/components/tackle.dm
index 911ca7ecbe..c336213388 100644
--- a/code/datums/components/tackle.dm
+++ b/code/datums/components/tackle.dm
@@ -370,7 +370,7 @@
user.emote("scream")
user.gain_trauma(/datum/brain_trauma/severe/paralysis/spinesnapped) // oopsie indeed!
shake_camera(user, 7, 7)
- user.overlay_fullscreen("flash", /atom/movable/screen/fullscreen/flash)
+ user.overlay_fullscreen("flash", /atom/movable/screen/fullscreen/tiled/flash)
user.clear_fullscreen("flash", 4.5)
if(94 to 98)
@@ -381,7 +381,7 @@
user.gain_trauma_type(BRAIN_TRAUMA_MILD)
user.playsound_local(get_turf(user), 'sound/weapons/flashbang.ogg', 100, TRUE, 8, 0.9)
shake_camera(user, 6, 6)
- user.overlay_fullscreen("flash", /atom/movable/screen/fullscreen/flash)
+ user.overlay_fullscreen("flash", /atom/movable/screen/fullscreen/tiled/flash)
user.clear_fullscreen("flash", 3.5)
if(84 to 93)
@@ -394,7 +394,7 @@
user.playsound_local(get_turf(user), 'sound/weapons/flashbang.ogg', 100, TRUE, 8, 0.9)
user.DefaultCombatKnockdown(40)
shake_camera(user, 5, 5)
- user.overlay_fullscreen("flash", /atom/movable/screen/fullscreen/flash)
+ user.overlay_fullscreen("flash", /atom/movable/screen/fullscreen/tiled/flash)
user.clear_fullscreen("flash", 2.5)
if(64 to 83)
diff --git a/code/datums/mind.dm b/code/datums/mind.dm
index 73e3a65145..72b9d91b9d 100644
--- a/code/datums/mind.dm
+++ b/code/datums/mind.dm
@@ -200,7 +200,6 @@
/datum/mind/proc/has_antag_datum(datum_type, check_subtypes = TRUE)
if(!datum_type)
return
- . = FALSE
for(var/a in antag_datums)
var/datum/antagonist/A = a
if(check_subtypes && istype(A, datum_type))
diff --git a/code/datums/mood_events/generic_negative_events.dm b/code/datums/mood_events/generic_negative_events.dm
index b85a33ec3c..4b2d872aec 100644
--- a/code/datums/mood_events/generic_negative_events.dm
+++ b/code/datums/mood_events/generic_negative_events.dm
@@ -295,6 +295,37 @@
mood_change = -3
timeout = 1 MINUTES
+/datum/mood_event/high_five_alone
+ description = "I tried getting a high-five with no one around, how embarassing!\n"
+ mood_change = -2
+ timeout = 1 MINUTES
+
+/datum/mood_event/high_five_full_hand
+ description = "Oh God, I don't even know how to high-five correctly...\n"
+ mood_change = -1
+ timeout = 45 SECONDS
+
+/datum/mood_event/left_hanging
+ description = "But everyone loves high fives! Maybe people just... hate me?\n"
+ mood_change = -2
+ timeout = 1.5 MINUTES
+
+/datum/mood_event/too_slow
+ description = "NO! HOW COULD I BE.... TOO SLOW???\n"
+ mood_change = -2 // multiplied by how many people saw it happen, up to 8, so potentially massive. the ULTIMATE prank carries a lot of weight
+ timeout = 2 MINUTES
+
+/datum/mood_event/too_slow/add_effects(param)
+ var/people_laughing_at_you = 1 // start with 1 in case they're on the same tile or something
+ for(var/mob/living/carbon/iter_carbon in oview(owner, 7))
+ if(iter_carbon.stat == CONSCIOUS)
+ people_laughing_at_you++
+ if(people_laughing_at_you > 7)
+ break
+
+ mood_change *= people_laughing_at_you
+ return ..()
+
/datum/mood_event/sacrifice_bad
description = "Those darn savages!\n"
mood_change = -5
diff --git a/code/datums/mood_events/generic_positive_events.dm b/code/datums/mood_events/generic_positive_events.dm
index 39b3212c68..b0d73615b9 100644
--- a/code/datums/mood_events/generic_positive_events.dm
+++ b/code/datums/mood_events/generic_positive_events.dm
@@ -206,6 +206,21 @@
description = "Feels nice to get that out of the way!\n"
mood_change = 3
+/datum/mood_event/high_five
+ description = "I love getting high fives!\n"
+ mood_change = 2
+ timeout = 45 SECONDS
+
+/datum/mood_event/high_ten
+ description = "AMAZING! A HIGH-TEN!\n"
+ mood_change = 3
+ timeout = 45 SECONDS
+
+/datum/mood_event/down_low
+ description = "HA! What a rube, they never stood a chance...\n"
+ mood_change = 4
+ timeout = 1.5 MINUTES
+
/datum/mood_event/sacrifice_good
description = "The gods are pleased with this offering!\n"
mood_change = 5
diff --git a/code/datums/status_effects/debuffs.dm b/code/datums/status_effects/debuffs.dm
index 2e0df8917d..dbec6c6ee2 100644
--- a/code/datums/status_effects/debuffs.dm
+++ b/code/datums/status_effects/debuffs.dm
@@ -771,7 +771,7 @@
/datum/status_effect/necropolis_curse/proc/apply_curse(set_curse)
curse_flags |= set_curse
if(curse_flags & CURSE_BLINDING)
- owner.overlay_fullscreen("curse", /atom/movable/screen/fullscreen/curse, 1)
+ owner.overlay_fullscreen("curse", /atom/movable/screen/fullscreen/scaled/curse, 1)
/datum/status_effect/necropolis_curse/proc/remove_curse(remove_curse)
if(remove_curse & CURSE_BLINDING)
@@ -929,11 +929,13 @@
/atom/movable/screen/alert/status_effect/strandling/Click(location, control, params)
. = ..()
- to_chat(mob_viewer, "You attempt to remove the durathread strand from around your neck.")
- if(do_after(mob_viewer, 35, null, mob_viewer))
- if(isliving(mob_viewer))
- var/mob/living/L = mob_viewer
- to_chat(mob_viewer, "You successfully remove the durathread strand.")
+ if(usr != owner)
+ return
+ to_chat(owner, "You attempt to remove the durathread strand from around your neck.")
+ if(do_after(owner, 35, null, owner))
+ if(isliving(owner))
+ var/mob/living/L = owner
+ to_chat(owner, "You successfully remove the durathread strand.")
L.remove_status_effect(STATUS_EFFECT_CHOKINGSTRAND)
diff --git a/code/datums/status_effects/neutral.dm b/code/datums/status_effects/neutral.dm
index 2c274802b4..53ba29622e 100644
--- a/code/datums/status_effects/neutral.dm
+++ b/code/datums/status_effects/neutral.dm
@@ -83,3 +83,89 @@
/datum/status_effect/throat_soothed/on_remove()
REMOVE_TRAIT(owner, TRAIT_SOOTHED_THROAT, "[STATUS_EFFECT_TRAIT]_[id]")
return ..()
+
+// this status effect is used to negotiate the high-fiving capabilities of all concerned parties
+/datum/status_effect/offering
+ id = "offering"
+ duration = -1
+ tick_interval = -1
+ status_type = STATUS_EFFECT_UNIQUE
+ alert_type = null
+ /// The people who were offered this item at the start
+ var/list/possible_takers
+ /// The actual item being offered
+ var/obj/item/offered_item
+ /// The type of alert given to people when offered, in case you need to override some behavior (like for high-fives)
+ var/give_alert_type = /atom/movable/screen/alert/give
+
+/datum/status_effect/offering/on_creation(mob/living/new_owner, obj/item/offer, give_alert_override)
+ . = ..()
+ if(!.)
+ return
+
+ offered_item = offer
+ if(give_alert_override)
+ give_alert_type = give_alert_override
+
+ for(var/mob/living/carbon/possible_taker in orange(1, owner))
+ if(!owner.CanReach(possible_taker) || IS_DEAD_OR_INCAP(possible_taker) || !possible_taker.can_hold_items())
+ continue
+ register_candidate(possible_taker)
+
+ if(!possible_takers) // no one around
+ qdel(src)
+ return
+
+ RegisterSignal(owner, COMSIG_MOVABLE_MOVED, .proc/check_owner_in_range)
+ RegisterSignal(offered_item, list(COMSIG_PARENT_QDELETING, COMSIG_ITEM_DROPPED), .proc/dropped_item)
+ //RegisterSignal(owner, COMSIG_PARENT_EXAMINE_MORE, .proc/check_fake_out)
+
+/datum/status_effect/offering/Destroy()
+ for(var/i in possible_takers)
+ var/mob/living/carbon/removed_taker = i
+ remove_candidate(removed_taker)
+ LAZYCLEARLIST(possible_takers)
+ return ..()
+
+/// Hook up the specified carbon mob to be offered the item in question, give them the alert and signals and all
+/datum/status_effect/offering/proc/register_candidate(mob/living/carbon/possible_candidate)
+ var/atom/movable/screen/alert/give/G = possible_candidate.throw_alert("[owner]", give_alert_type)
+ if(!G)
+ return
+ LAZYADD(possible_takers, possible_candidate)
+ RegisterSignal(possible_candidate, COMSIG_MOVABLE_MOVED, .proc/check_taker_in_range)
+ G.setup(possible_candidate, owner, offered_item)
+
+/// Remove the alert and signals for the specified carbon mob. Automatically removes the status effect when we lost the last taker
+/datum/status_effect/offering/proc/remove_candidate(mob/living/carbon/removed_candidate)
+ removed_candidate.clear_alert("[owner]")
+ LAZYREMOVE(possible_takers, removed_candidate)
+ UnregisterSignal(removed_candidate, COMSIG_MOVABLE_MOVED)
+ if(!possible_takers && !QDELING(src))
+ qdel(src)
+
+/// One of our possible takers moved, see if they left us hanging
+/datum/status_effect/offering/proc/check_taker_in_range(mob/living/carbon/taker)
+ SIGNAL_HANDLER
+ if(owner.CanReach(taker) && !IS_DEAD_OR_INCAP(taker))
+ return
+
+ remove_candidate(taker)
+
+/// The offerer moved, see if anyone is out of range now
+/datum/status_effect/offering/proc/check_owner_in_range(mob/living/carbon/source)
+ SIGNAL_HANDLER
+
+ for(var/i in possible_takers)
+ var/mob/living/carbon/checking_taker = i
+ if(!istype(checking_taker) || !owner.CanReach(checking_taker) || IS_DEAD_OR_INCAP(checking_taker))
+ remove_candidate(checking_taker)
+
+/// We lost the item, give it up
+/datum/status_effect/offering/proc/dropped_item(obj/item/source)
+ SIGNAL_HANDLER
+ qdel(src)
+
+/datum/status_effect/offering/secret_handshake
+ id = "secret_handshake"
+ give_alert_type = /atom/movable/screen/alert/give/secret_handshake
diff --git a/code/datums/status_effects/status_effect.dm b/code/datums/status_effects/status_effect.dm
index fe605ba4a6..f3171f6197 100644
--- a/code/datums/status_effects/status_effect.dm
+++ b/code/datums/status_effects/status_effect.dm
@@ -25,18 +25,19 @@
/datum/status_effect/proc/on_creation(mob/living/new_owner, ...)
if(new_owner)
owner = new_owner
- if(owner)
- LAZYADD(owner.status_effects, src)
- if(!owner || !on_apply())
+ if(QDELETED(owner) || !on_apply())
qdel(src)
return
+ if(owner)
+ LAZYADD(owner.status_effects, src)
if(duration != -1)
duration = world.time + duration
next_tick = world.time + tick_interval
if(alert_type)
var/atom/movable/screen/alert/status_effect/A = owner.throw_alert(id, alert_type)
- A.attached_effect = src //so the alert can reference us, if it needs to
- linked_alert = A //so we can reference the alert, if we need to
+ if(istype(A))
+ A?.attached_effect = src //so the alert can reference us, if it needs to
+ linked_alert = A //so we can reference the alert, if we need to
START_PROCESSING(SSstatus_effects, src)
return TRUE
diff --git a/code/game/area/areas.dm b/code/game/area/areas.dm
index 6ad95c8b56..b922f52315 100644
--- a/code/game/area/areas.dm
+++ b/code/game/area/areas.dm
@@ -50,7 +50,12 @@
var/has_gravity = FALSE
- var/parallax_movedir = 0
+ /// Parallax moving?
+ var/parallax_moving = FALSE
+ /// Parallax move speed - 0 to disable
+ var/parallax_move_speed = 0
+ /// Parallax move dir - degrees clockwise from north
+ var/parallax_move_angle = 0
var/list/ambientsounds = GENERIC
flags_1 = CAN_BE_DIRTY_1
diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm
index 391d3b467d..5da389fbb8 100644
--- a/code/game/atoms_movable.dm
+++ b/code/game/atoms_movable.dm
@@ -28,7 +28,6 @@
var/pass_flags = 0
var/moving_diagonally = 0 //0: not doing a diagonal move. 1 and 2: doing the first/second step of the diagonal move
var/atom/movable/moving_from_pull //attempt to resume grab after moving instead of before.
- var/list/client_mobs_in_contents // This contains all the client mobs within this container
var/list/acted_explosions //for explosion dodging
var/datum/forced_movement/force_moving = null //handled soley by forced_movement.dm
@@ -108,8 +107,6 @@
for(var/movable_content in contents)
qdel(movable_content)
- LAZYCLEARLIST(client_mobs_in_contents)
-
moveToNullspace()
/atom/movable/proc/update_emissive_block()
diff --git a/code/game/atoms_movement.dm b/code/game/atoms_movement.dm
index 43a22c9df0..9fd007e9ee 100644
--- a/code/game/atoms_movement.dm
+++ b/code/game/atoms_movement.dm
@@ -176,12 +176,8 @@
if (!inertia_moving)
inertia_next_move = world.time + inertia_move_delay
newtonian_move(Dir)
- if (length(client_mobs_in_contents))
- update_parallax_contents()
-
return TRUE
-
// Make sure you know what you're doing if you call this, this is intended to only be called by byond directly.
// You probably want CanPass()
/atom/movable/Cross(atom/movable/AM)
diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm
index 0052306949..601848c22c 100644
--- a/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm
+++ b/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm
@@ -242,6 +242,69 @@
log_game("DYNAMIC: [key_name(M)] was selected by the [name] ruleset and has been made into a midround traitor.")
return TRUE
+//////////////////////////////////////////////
+// //
+// FAMILIES //
+// //
+//////////////////////////////////////////////
+
+/datum/dynamic_ruleset/midround/families
+ name = "Family Head Aspirants"
+ persistent = TRUE
+ antag_datum = /datum/antagonist/gang
+ antag_flag = ROLE_FAMILY_HEAD_ASPIRANT
+ antag_flag_override = ROLE_FAMILIES
+ protected_roles = list("Prisoner", "Head of Personnel")
+ restricted_roles = list("AI", "Cyborg", "Prisoner", "Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Chaplain", "Head of Personnel", "Quartermaster", "Chief Engineer", "Chief Medical Officer", "Research Director")
+ required_candidates = 9
+ weight = 3
+ cost = 15
+ requirements = list(101,101,101,50,30,20,10,10,10,10)
+ flags = HIGH_IMPACT_RULESET
+ blocking_rules = list(/datum/dynamic_ruleset/roundstart/families)
+ /// A reference to the handler that is used to run pre_execute(), execute(), etc..
+ var/datum/gang_handler/handler
+
+/datum/dynamic_ruleset/midround/families/trim_candidates()
+ ..()
+ candidates = living_players
+ for(var/mob/living/player in candidates)
+ if(issilicon(player))
+ candidates -= player
+ else if(is_centcom_level(player.z))
+ candidates -= player
+ else if(player.mind && (player.mind.special_role || player.mind.antag_datums?.len > 0))
+ candidates -= player
+ else if(HAS_TRAIT(player, TRAIT_MINDSHIELD))
+ candidates -= player
+
+
+/datum/dynamic_ruleset/midround/families/ready(forced = FALSE)
+ if (required_candidates > living_players.len)
+ return FALSE
+ return ..()
+
+/datum/dynamic_ruleset/midround/families/pre_execute()
+ ..()
+ handler = new /datum/gang_handler(candidates,restricted_roles)
+ handler.gang_balance_cap = clamp((indice_pop - 3), 2, 5) // gang_balance_cap by indice_pop: (2,2,2,2,2,3,4,5,5,5)
+ handler.midround_ruleset = TRUE
+ handler.use_dynamic_timing = TRUE
+ return handler.pre_setup_analogue()
+
+/datum/dynamic_ruleset/midround/families/execute()
+ return handler.post_setup_analogue(TRUE)
+
+/datum/dynamic_ruleset/midround/families/clean_up()
+ QDEL_NULL(handler)
+ ..()
+
+/datum/dynamic_ruleset/midround/families/rule_process()
+ return handler.process_analogue()
+
+/datum/dynamic_ruleset/midround/families/round_result()
+ return handler.set_round_result_analogue()
+
//////////////////////////////////////////////
// //
// Malfunctioning AI //
diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm
index 9e985e9c47..a9d0ec7d3c 100644
--- a/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm
+++ b/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm
@@ -485,6 +485,47 @@
SSticker.mode_result = "loss - servants failed their objective (summon ratvar)"
SSticker.news_report = CULT_FAILURE
+//////////////////////////////////////////////
+// //
+// FAMILIES //
+// //
+//////////////////////////////////////////////
+
+/datum/dynamic_ruleset/roundstart/families
+ name = "Families"
+ persistent = TRUE
+ antag_datum = /datum/antagonist/gang
+ antag_flag = ROLE_FAMILIES
+ protected_roles = list("Prisoner", "Head of Personnel")
+ restricted_roles = list("AI", "Cyborg", "Prisoner", "Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Chaplain", "Head of Personnel", "Quartermaster", "Chief Engineer", "Chief Medical Officer", "Research Director")
+ required_candidates = 9
+ weight = 3
+ cost = 15
+ requirements = list(101,101,101,50,30,20,10,10,10,10)
+ flags = HIGH_IMPACT_RULESET
+ /// A reference to the handler that is used to run pre_execute(), execute(), etc..
+ var/datum/gang_handler/handler
+
+/datum/dynamic_ruleset/roundstart/families/pre_execute()
+ ..()
+ handler = new /datum/gang_handler(candidates,restricted_roles)
+ handler.gangs_to_generate = (antag_cap[indice_pop] / 2)
+ handler.gang_balance_cap = clamp((indice_pop - 3), 2, 5) // gang_balance_cap by indice_pop: (2,2,2,2,2,3,4,5,5,5)
+ return handler.pre_setup_analogue()
+
+/datum/dynamic_ruleset/roundstart/families/execute()
+ return handler.post_setup_analogue(TRUE)
+
+/datum/dynamic_ruleset/roundstart/families/clean_up()
+ QDEL_NULL(handler)
+ ..()
+
+/datum/dynamic_ruleset/roundstart/families/rule_process()
+ return handler.process_analogue()
+
+/datum/dynamic_ruleset/roundstart/families/round_result()
+ return handler.set_round_result_analogue()
+
// Admin only rulesets. The threat requirement is 101 so it is not possible to roll them.
//////////////////////////////////////////////
diff --git a/code/game/gamemodes/gang/gang.dm b/code/game/gamemodes/gang/gang.dm
new file mode 100644
index 0000000000..47c24a8fcf
--- /dev/null
+++ b/code/game/gamemodes/gang/gang.dm
@@ -0,0 +1,50 @@
+/datum/game_mode/gang
+ name = "Families"
+ config_tag = "families"
+ antag_flag = ROLE_TRAITOR
+ false_report_weight = 5
+ required_players = 0
+ required_enemies = 1
+ recommended_enemies = 4
+ announce_span = "danger"
+ announce_text = "Grove For Lyfe!"
+ reroll_friendly = FALSE
+ restricted_jobs = list("Cyborg", "AI", "Prisoner","Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel")//N O
+ protected_jobs = list()
+
+ /// A reference to the handler that is used to run pre_setup(), post_setup(), etc..
+ var/datum/gang_handler/handler
+
+/datum/game_mode/gang/warriors
+ name = "Warriors"
+ config_tag = "warriors"
+ announce_text = "Can you survive this onslaught?"
+
+/datum/game_mode/gang/warriors/pre_setup()
+ handler = new /datum/gang_handler(antag_candidates,restricted_jobs)
+ var/list/datum/antagonist/gang/gangs_to_generate = subtypesof(/datum/antagonist/gang)
+ handler.gangs_to_generate = gangs_to_generate.len
+ handler.gang_balance_cap = 3
+ return handler.pre_setup_analogue()
+
+/datum/game_mode/gang/pre_setup()
+ handler = new /datum/gang_handler(antag_candidates,restricted_jobs)
+ return handler.pre_setup_analogue()
+
+/datum/game_mode/gang/Destroy()
+ QDEL_NULL(handler)
+ return ..()
+
+/datum/game_mode/gang/post_setup()
+ handler.post_setup_analogue(FALSE)
+ gamemode_ready = TRUE
+ return ..()
+
+/datum/game_mode/gang/process()
+ handler.process_analogue()
+
+/datum/game_mode/gang/set_round_result()
+ return handler.set_round_result_analogue()
+
+/datum/game_mode/gang/generate_report()
+ return "Something something grove street home at least until I fucked everything up idk nobody reads these reports."
diff --git a/code/game/gamemodes/gangs/dominator.dm b/code/game/gamemodes/gangs/dominator.dm
deleted file mode 100644
index a253aa906c..0000000000
--- a/code/game/gamemodes/gangs/dominator.dm
+++ /dev/null
@@ -1,247 +0,0 @@
-#define DOM_BLOCKED_SPAM_CAP 6
-//32 instead of 40 for safety reasons. How many turfs aren't walls around dominator for it to work
-//Update ppl somehow fuckup at 32, now we are down to 25. I hope to god they don't try harder to wall it.
-#define DOM_REQUIRED_TURFS 25
-#define DOM_HULK_HITS_REQUIRED 10
-
-/obj/machinery/dominator
- name = "dominator"
- desc = "A visibly sinister device. Looks like you can break it if you hit it enough."
- icon = 'icons/obj/machines/dominator.dmi'
- icon_state = "dominator"
- density = TRUE
- anchored = TRUE
- layer = HIGH_OBJ_LAYER
- max_integrity = 300
- integrity_failure = 0.33
- armor = list("melee" = 20, "bullet" = 50, "laser" = 50, "energy" = 50, "bomb" = 10, "bio" = 100, "rad" = 100, "fire" = 10, "acid" = 70)
- var/datum/team/gang/gang
- var/operating = FALSE //false=standby or broken, true=takeover
- var/warned = FALSE //if this device has set off the warning at <3 minutes yet
- var/spam_prevention = DOM_BLOCKED_SPAM_CAP //first message is immediate
- var/datum/effect_system/spark_spread/spark_system
- var/obj/effect/countdown/dominator/countdown
-
-/obj/machinery/dominator/Initialize()
- . = ..()
- set_light(l_range = 2, l_power = 0.75)
- GLOB.poi_list |= src
- spark_system = new
- spark_system.set_up(5, TRUE, src)
- countdown = new(src)
- update_icon()
-
-/obj/machinery/dominator/Destroy()
- if(!(stat & BROKEN))
- set_broken()
- GLOB.poi_list.Remove(src)
- gang = null
- QDEL_NULL(spark_system)
- QDEL_NULL(countdown)
- STOP_PROCESSING(SSmachines, src)
- return ..()
-
-/obj/machinery/dominator/emp_act(severity)
- take_damage(75+severity/4, BURN, "energy", 0)
- ..()
-
-/obj/machinery/dominator/hulk_damage()
- return (max_integrity - integrity_failure) / DOM_HULK_HITS_REQUIRED
-
-/obj/machinery/dominator/update_icon()
- cut_overlays()
- if(stat & BROKEN)
- icon_state = "dominator-broken"
- return
- icon_state = "dominator"
- if(operating)
- var/mutable_appearance/dominator_overlay = mutable_appearance('icons/obj/machines/dominator.dmi', "dominator-overlay")
- if(gang)
- dominator_overlay.color = gang.color
- add_overlay(dominator_overlay)
- if(obj_integrity/max_integrity < 0.66)
- add_overlay("damage")
-
-/obj/machinery/dominator/examine(mob/user)
- . = ..()
- if(stat & BROKEN)
- return
-
- if(gang && gang.domination_time != NOT_DOMINATING)
- if(gang.domination_time > world.time)
- . += "Hostile Takeover in progress. Estimated [gang.domination_time_remaining()] seconds remain."
- else
- . += "Hostile Takeover of [station_name()] successful. Have a great day."
- else
- . += "System on standby."
- . += "System Integrity: [round((obj_integrity/max_integrity)*100,1)]%"
-
-/obj/machinery/dominator/process()
- ..()
- if(gang && gang.domination_time != NOT_DOMINATING)
- var/time_remaining = gang.domination_time_remaining()
- if(time_remaining > 0)
- if(!is_station_level(z))
- explosion(src, 5, 10, 20, 30) //you now get a nice explosion if this moves off station.
- qdel(src) //to make sure it doesn't continue to exist.
- if(excessive_walls_check())
- gang.domination_time += 20
- if(spam_prevention < DOM_BLOCKED_SPAM_CAP)
- spam_prevention++
- else
- playsound(loc, 'sound/machines/buzz-two.ogg', 50, 0) // Play sound buzz-two.ogg, not before cause its annoying.
- gang.message_gangtools("Warning: There are too many walls around your gang's dominator, its signal is being blocked!")
- say("Error: Takeover signal is currently blocked! There are too many walls within 3 standard units of this device.")
- spam_prevention = 0
- return
- . = TRUE
- playsound(loc, 'sound/items/timer.ogg', 10, 0)
- if(!warned && (time_remaining < 180))
- warned = TRUE
- var/area/domloc = get_base_area(loc)
- gang.message_gangtools("Less than 3 minutes remains in hostile takeover. Defend your dominator at [domloc.map_name]!")
- for(var/G in GLOB.gangs)
- var/datum/team/gang/tempgang = G
- if(tempgang != gang)
- tempgang.message_gangtools("WARNING: [gang.name] Gang takeover imminent. Their dominator at [domloc.map_name] must be destroyed!",1,1)
- else
- endgame()
-
- if(!.)
- STOP_PROCESSING(SSmachines, src)
-
-/obj/machinery/dominator/proc/endgame()
- set waitfor = FALSE
- Cinematic(CINEMATIC_MALF,world) //Here is the gang victory trigger on the dominator ending.
- gang.winner = TRUE
- SSticker.news_report = GANG_VICTORY
- SSticker.force_ending = TRUE
-
-/obj/machinery/dominator/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0)
- switch(damage_type)
- if(BRUTE)
- if(damage_amount)
- playsound(src, 'sound/effects/bang.ogg', 50, 1)
- else
- playsound(loc, 'sound/weapons/tap.ogg', 50, 1)
- if(BURN)
- playsound(src.loc, 'sound/items/welder.ogg', 100, 1)
-
-/obj/machinery/dominator/take_damage(damage_amount, damage_type = BRUTE, damage_flag = 0, sound_effect = 1)
- . = ..()
- if(.)
- if(obj_integrity/max_integrity > 0.66)
- if(prob(damage_amount*2))
- spark_system.start()
- else if(!(stat & BROKEN))
- spark_system.start()
- update_icon()
-
-
-/obj/machinery/dominator/obj_break(damage_flag)
- if(!(stat & BROKEN) && !(flags_1 & NODECONSTRUCT_1))
- set_broken()
-
-/obj/machinery/dominator/deconstruct(disassembled = TRUE)
- if(!(flags_1 & NODECONSTRUCT_1))
- if(!(stat & BROKEN))
- set_broken()
- new /obj/item/stack/sheet/plasteel(src.loc)
- qdel(src)
-
-/obj/machinery/dominator/attacked_by(obj/item/I, mob/living/user, attackchain_flags = NONE, damage_multiplier = 1)
- add_fingerprint(user)
- return ..()
-
-/obj/machinery/dominator/on_attack_hand(mob/user, act_intent = user.a_intent, unarmed_attack_flags)
- if(operating || (stat & BROKEN))
- examine(user)
- return
-
- var/datum/team/gang/tempgang
-
- var/datum/antagonist/gang/GA = user.mind.has_antag_datum(/datum/antagonist/gang)
- if(GA)
- tempgang = GA.gang
- if(!tempgang)
- examine(user)
- return
-
- if(tempgang.domination_time != NOT_DOMINATING)
- to_chat(user, "Error: Hostile Takeover is already in progress.")
- return
-
- if(!tempgang.dom_attempts)
- to_chat(user, "Error: Unable to breach station network. Firewall has logged our signature and is blocking all further attempts.")
- return
-
- var/time = round(tempgang.determine_domination_time()/60,0.1)
- if(alert(user,"A takeover will require [time] minutes.\nYour gang will be unable to gain influence while it is active.\nThe entire station will likely be alerted to it once it starts.\nYou have [tempgang.dom_attempts] attempt(s) remaining. Are you ready?","Confirm","Ready","Later") == "Ready")
- if((tempgang.domination_time != NOT_DOMINATING) || !tempgang.dom_attempts || !in_range(src, user) || !isturf(loc))
- return 0
-
- var/area/A = get_base_area(loc)
- var/locname = A.map_name
-
- gang = tempgang
- gang.dom_attempts --
- priority_announce("Network breach detected in [locname]. The [gang.name] Gang is attempting to seize control of the station!","Network Alert")
- gang.domination()
- SSshuttle.registerHostileEnvironment(src)
- name = "[gang.name] Gang [name]"
- operating = TRUE
- update_icon()
-
- countdown.start()
- countdown.color = gang.color
-
- set_light(l_range = 3, l_power = 0.9)
- light_color = gang.color
- START_PROCESSING(SSmachines, src)
-
- gang.message_gangtools("Hostile takeover in progress: Estimated [time] minutes until victory.[gang.dom_attempts ? "" : " This is your final attempt."]")
- for(var/G in GLOB.gangs)
- var/datum/team/gang/vagos = G
- if(vagos != gang)
- vagos.message_gangtools("Enemy takeover attempt detected in [locname]: Estimated [time] minutes until our defeat.",1,1)
-
-/obj/machinery/dominator/proc/excessive_walls_check() // why the fuck was this even a global proc...
- var/open = 0
- for(var/turf/T in view(3, src))
- if(!iswallturf(T)) //Check for /closed/wall, isclosedturf() moves it back to just checking for /closed/ which makes it very finicky.
- open++
- //to_chat(world, "THE DOMINATOR SEES [open] OPEN TURFS") uncomment to see what this shitty fucking wallcheck sees
- if(open < DOM_REQUIRED_TURFS)
- return TRUE
- else
- return FALSE
-/obj/machinery/dominator/proc/set_broken()
- if(gang)
- gang.domination_time = NOT_DOMINATING
-
- var/takeover_in_progress = FALSE
- for(var/G in GLOB.gangs)
- var/datum/team/gang/ballas = G
- if(ballas.domination_time != NOT_DOMINATING)
- takeover_in_progress = TRUE
- break
- if(!takeover_in_progress)
- var/was_stranded = SSshuttle.emergency.mode == SHUTTLE_STRANDED
- if(!was_stranded)
- priority_announce("All hostile activity within station systems has ceased.","Network Alert")
-
- if(NUM2SECLEVEL(GLOB.security_level) == "delta")
- set_security_level("red")
-
- SSshuttle.clearHostileEnvironment(src)
- gang.message_gangtools("Hostile takeover cancelled: Dominator is no longer operational.[gang.dom_attempts ? " You have [gang.dom_attempts] attempt remaining." : " The station network will have likely blocked any more attempts by us."]",1,1)
-
- set_light(0)
- operating = FALSE
- stat |= BROKEN
- update_icon()
- STOP_PROCESSING(SSmachines, src)
-
-#undef DOM_BLOCKED_SPAM_CAP
-#undef DOM_REQUIRED_TURFS
-#undef DOM_HULK_HITS_REQUIRED
diff --git a/code/game/gamemodes/gangs/dominator_countdown.dm b/code/game/gamemodes/gangs/dominator_countdown.dm
deleted file mode 100644
index 3b61a07829..0000000000
--- a/code/game/gamemodes/gangs/dominator_countdown.dm
+++ /dev/null
@@ -1,13 +0,0 @@
-/obj/effect/countdown/dominator
- name = "dominator countdown"
- text_size = 1
- color = "#e5e5e5" // Overwritten when the dominator starts
-
-/obj/effect/countdown/dominator/get_value()
- var/obj/machinery/dominator/D = attached_to
- if(!istype(D))
- return
- else if(D.gang && D.gang.domination_time != NOT_DOMINATING)
- return D.gang.domination_time_remaining()
- else
- return "OFFLINE"
diff --git a/code/game/gamemodes/gangs/gang.dm b/code/game/gamemodes/gangs/gang.dm
deleted file mode 100644
index 6efdf2a4aa..0000000000
--- a/code/game/gamemodes/gangs/gang.dm
+++ /dev/null
@@ -1,479 +0,0 @@
-/datum/antagonist/gang
- name = "Gangster"
- roundend_category = "gangsters"
- can_coexist_with_others = FALSE
- job_rank = ROLE_GANG
- antagpanel_category = "Gang"
- threat = 2
- var/hud_type = "gangster"
- var/message_name = "Gangster"
- var/datum/team/gang/gang
-
-/datum/antagonist/gang/can_be_owned(datum/mind/new_owner)
- . = ..()
- if(.)
- if(new_owner.unconvertable)
- return FALSE
-
-/datum/antagonist/gang/apply_innate_effects(mob/living/mob_override)
- var/mob/living/M = mob_override || owner.current
- update_gang_icons_added(M)
-
-/datum/antagonist/gang/remove_innate_effects(mob/living/mob_override)
- var/mob/living/M = mob_override || owner.current
- update_gang_icons_removed(M)
-
-/datum/antagonist/gang/get_team()
- return gang
-
-/datum/antagonist/gang/greet()
- gang.greet_gangster(owner)
-
-/datum/antagonist/gang/farewell()
- if(ishuman(owner.current))
- owner.current.visible_message("[owner.current] looks like [owner.current.p_theyve()] just remembered [owner.current.p_their()] real allegiance!", null, null, null, owner.current)
- to_chat(owner, "You are no longer a gangster! Your memories from the time you were in a gang are hazy... You don't seem to be able to recall the names of your previous allies, not even your bosses...")
-
-/datum/antagonist/gang/on_gain()
- if(!gang)
- create_team()
- ..()
- var/mob/living/carbon/human/H = owner.current
- if(istype(H))
- if(owner.assigned_role == "Clown")
- to_chat(owner, "Your training has allowed you to overcome your clownish nature, allowing you to wield weapons without harming yourself.")
- H.dna.remove_mutation(CLOWNMUT)
- add_to_gang()
-
-/datum/antagonist/gang/on_removal()
- remove_from_gang()
- ..()
-
-/datum/antagonist/gang/create_team(team)
- if(!gang) // add_antag_datum calls create_team, so we need to avoid generating two gangs in that case
- if(team)
- gang = team
- return
- var/datum/team/gang/gangteam = pick_n_take(GLOB.possible_gangs)
- if(gangteam)
- gang = new gangteam
-
-/datum/antagonist/gang/proc/equip_gang() // Bosses get equipped with their tools
- return
-
-/datum/antagonist/gang/proc/update_gang_icons_added(mob/living/M)
- var/datum/atom_hud/antag/gang/ganghud = GLOB.huds[gang.hud_entry_num]
- if(!ganghud)
- ganghud = new/datum/atom_hud/antag/gang()
- gang.hud_entry_num = GLOB.huds.len+1 // this is the index the gang hud will be added at
- GLOB.huds += ganghud
- ganghud.color = gang.color
- ganghud.join_hud(M)
- set_antag_hud(M,hud_type)
-
-/datum/antagonist/gang/proc/update_gang_icons_removed(mob/living/M)
- var/datum/atom_hud/antag/gang/ganghud = GLOB.huds[gang.hud_entry_num]
- if(ganghud)
- ganghud.leave_hud(M)
- set_antag_hud(M, null)
-
-/datum/antagonist/gang/proc/can_be_converted(mob/living/candidate)
- if(!candidate.mind)
- return FALSE
- if(!can_be_owned(candidate.mind))
- return FALSE
- var/mob/living/carbon/human/H = candidate
- if(!istype(H)) //Can't nonhumans
- return FALSE
- return TRUE
-
-/datum/antagonist/gang/proc/promote() // Bump up to boss
- var/datum/team/gang/old_gang = gang
- var/datum/mind/old_owner = owner
- owner.remove_antag_datum(/datum/antagonist/gang)
- var/datum/antagonist/gang/boss/lieutenant/new_boss = new
- new_boss.silent = TRUE
- old_owner.add_antag_datum(new_boss,old_gang)
- new_boss.silent = FALSE
- log_game("[key_name(old_owner)] has been promoted to Lieutenant in the [old_gang.name] Gang")
- to_chat(old_owner, "You have been promoted to Lieutenant!")
-
-
-// Admin commands
-/datum/antagonist/gang/get_admin_commands()
- . = ..()
- .["Promote"] = CALLBACK(src,.proc/admin_promote)
- .["Set Influence"] = CALLBACK(src, .proc/admin_adjust_influence)
- if(gang.domination_time != NOT_DOMINATING)
- .["Set domination time left"] = CALLBACK(src, .proc/set_dom_time_left)
-
-/datum/antagonist/gang/admin_add(datum/mind/new_owner,mob/admin)
- var/new_or_existing = input(admin, "Which gang do you want to be assigned to the user?", "Gangs") as null|anything in list("New","Existing")
- if(isnull(new_or_existing))
- return
- else if(new_or_existing == "New")
- var/newgang = input(admin, "Select a gang, or select random to pick a random one.", "New gang") as null|anything in GLOB.possible_gangs + "Random"
- if(isnull(newgang))
- return
- else if(newgang == "Random")
- var/datum/team/gang/G = pick_n_take(GLOB.possible_gangs)
- gang = new G
- else
- GLOB.possible_gangs -= newgang
- gang = new newgang
- else
- if(!GLOB.gangs.len) // no gangs exist
- to_chat(admin, "No gangs exist, please create a new one instead.")
- return
- var/existinggang = input(admin, "Select a gang, or select random to pick a random one.", "Existing gang") as null|anything in GLOB.gangs + "Random"
- if(isnull(existinggang))
- return
- else if(existinggang == "Random")
- gang = pick(GLOB.gangs)
- else
- gang = existinggang
- ..()
- return TRUE
-
-/datum/antagonist/gang/proc/admin_promote(mob/admin)
- message_admins("[key_name_admin(admin)] has promoted [owner] to gang boss.")
- log_admin("[key_name(admin)] has promoted [owner] to boss.")
- promote()
-
-/datum/antagonist/gang/proc/admin_adjust_influence()
- var/inf = input("Influence for [gang.name]","Gang influence", gang.influence) as null | num
- if(!isnull(inf))
- gang.influence = inf
- message_admins("[key_name_admin(usr)] changed [gang.name]'s influence to [inf].")
- log_admin("[key_name(usr)] changed [gang.name]'s influence to [inf].")
-
-/datum/antagonist/gang/proc/add_to_gang()
- gang.add_member(owner)
- owner.current.log_message("Has been converted to the [gang.name] gang!", INDIVIDUAL_ATTACK_LOG)
-
-/datum/antagonist/gang/proc/remove_from_gang()
- gang.remove_member(owner)
- owner.current.log_message("Has been deconverted from the [gang.name] gang!", INDIVIDUAL_ATTACK_LOG)
-
-/datum/antagonist/gang/proc/set_dom_time_left(mob/admin)
- if(gang.domination_time == NOT_DOMINATING)
- return // an admin shouldn't need this
- var/seconds = input(admin, "Set the time left for the gang to win, in seconds", "Domination time left") as null|num
- if(seconds && seconds > 0)
- gang.domination_time = world.time + seconds*10
- gang.message_gangtools("Takeover shortened to [gang.domination_time_remaining()] seconds by your Syndicate benefactors.")
-
-// Boss type. Those can use gang tools to buy items for their gang, in particular the Dominator, used to win the gamemode, along with more gang tools to promote fellow gangsters to boss status.
-/datum/antagonist/gang/boss
- name = "Gang boss"
- hud_type = "gang_boss"
- message_name = "Leader"
- threat = 10
-
-/datum/antagonist/gang/boss/on_gain()
- ..()
- if(gang)
- gang.leaders += owner
-
-/datum/antagonist/gang/boss/on_removal()
- if(gang)
- gang.leaders -= owner
- ..()
-
-/datum/antagonist/gang/boss/antag_listing_name()
- return ..() + "(Boss)"
-
-/datum/antagonist/gang/boss/equip_gang(gangtool = TRUE, pen = TRUE, spraycan = TRUE, hud = TRUE) // usually has to be called separately
- var/mob/living/carbon/human/H = owner.current
- if(!istype(H))
- return
-
- var/list/slots = list (
- "backpack" = SLOT_IN_BACKPACK,
- "left pocket" = SLOT_L_STORE,
- "right pocket" = SLOT_R_STORE,
- "hands" = SLOT_HANDS
- )
-
- if(gangtool)//Here is where all of the text occurs when a gang boss first spawns in.
- var/obj/item/device/gangtool/G = new()
- var/where = H.equip_in_one_of_slots(G, slots, critical = TRUE)
- if (!where)
- to_chat(H, "Your Syndicate benefactors were unfortunately unable to get you a Gangtool.")
- else
- G.register_device(H)
- to_chat(H, "The Gangtool in your [where] will allow you to purchase weapons and equipment, send messages to your gang, and recall the emergency shuttle from anywhere on the station.")
- to_chat(H, "As the gang boss, you can also promote your gang members to lieutenant. Unlike regular gangsters, Lieutenants cannot be deconverted and are able to use gangtools too.")
-
- if(pen)
- var/obj/item/pen/gang/T = new()
- var/where2 = H.equip_in_one_of_slots(T, slots, critical = TRUE)
- if (!where2)
- to_chat(H, "Your Syndicate benefactors were unfortunately unable to get you a recruitment pen to start.")
- else
- to_chat(H, "The recruitment pen in your [where2] will help you get your gang started. Stab unsuspecting crew members with it to recruit them. All gangsters can use these, distribute them to see your gang grow.")
-
- if(spraycan)
- var/obj/item/toy/crayon/spraycan/gang/SC = new(null,gang)
- var/where3 = H.equip_in_one_of_slots(SC, slots, critical = TRUE)
- if (!where3)
- to_chat(H, "Your Syndicate benefactors were unfortunately unable to get you a territory spraycan to start.")
- else
- to_chat(H, "The territory spraycan in your [where3] can be used to claim areas of the station for your gang. The more territory your gang controls, the more influence you get. All gangsters can use these, so distribute them to grow your influence faster.")
-
- if(hud)
- var/obj/item/clothing/glasses/hud/security/chameleon/C = new(null,gang)
- var/where4 = H.equip_in_one_of_slots(C, slots, critical = TRUE)
- if (!where4)
- to_chat(H, "Your Syndicate benefactors were unfortunately unable to get you a chameleon security HUD.")
- else
- to_chat(H, "The chameleon security HUD in your [where4] will help you keep track of who is mindshield-implanted, and unable to be recruited.")
-
-// Admin commands for bosses
-/datum/antagonist/gang/boss/admin_add(datum/mind/new_owner,mob/admin)
- if(!new_owner.has_antag_datum(parent_type))
- ..()
- to_chat(new_owner.current, "You are a member of the [gang.name] Gang leadership now!")
- return
- promote()
- message_admins("[key_name_admin(admin)] has made [new_owner.current] a boss of the [gang.name] gang.")
- log_admin("[key_name(admin)] has made [new_owner.current] a boss of the [gang.name] gang.")
- to_chat(new_owner.current, "You are a member of the [gang.name] Gang leadership now!")
-
-/datum/antagonist/gang/boss/get_admin_commands()
- . = ..()
- . -= "Promote"
- .["Take gangtool"] = CALLBACK(src,.proc/admin_take_gangtool)
- .["Give gangtool"] = CALLBACK(src,.proc/admin_give_gangtool)
- .["Demote"] = CALLBACK(src,.proc/admin_demote)
-
-/datum/antagonist/gang/boss/proc/demote()
- var/old_gang = gang
- var/datum/mind/old_owner = owner
- silent = TRUE
- owner.remove_antag_datum(/datum/antagonist/gang/boss)
- var/datum/antagonist/gang/new_gangster = new /datum/antagonist/gang()
- new_gangster.silent = TRUE
- old_owner.add_antag_datum(new_gangster,old_gang)
- new_gangster.silent = FALSE
- log_game("[key_name(old_owner)] has been demoted to Gangster in the [gang.name] Gang")
- to_chat(old_owner, "The gang has been disappointed of your leader traits! You are a regular gangster now!")
-
-/datum/antagonist/gang/boss/proc/admin_take_gangtool(mob/admin)
- var/list/L = owner.current.get_contents()
- var/obj/item/device/gangtool/gangtool = locate() in L
- if (!gangtool)
- to_chat(admin, "Deleting gangtool failed!")
- return
- qdel(gangtool)
-
-/datum/antagonist/gang/boss/proc/admin_give_gangtool(mob/admin)
- equip_gang(TRUE, FALSE, FALSE, FALSE)
-
-/datum/antagonist/gang/boss/proc/admin_demote(datum/mind/target,mob/user)
- message_admins("[key_name_admin(user)] has demoted [owner.current] from gang boss.")
- log_admin("[key_name(user)] has demoted [owner.current] from gang boss.")
- admin_take_gangtool(user)
- demote()
-
-/datum/antagonist/gang/boss/lieutenant
- name = "Gang Lieutenant"
- message_name = "Lieutenant"
- hud_type = "gang_lt"
-
-#define MAXIMUM_RECALLS 3
-#define INFLUENCE_INTERVAL 1200 //This handles the interval between each count of influence.
-// Gang team datum. This handles the gang itself.
-/datum/team/gang
- name = "Gang"
- member_name = "gangster"
- var/hud_entry_num // because if you put something other than a number in GLOB.huds, god have mercy on your fucking soul friend
- var/list/leaders = list() // bosses
- var/max_leaders = MAX_LEADERS_GANG
- var/list/territories = list() // territories owned by the gang.
- var/list/lost_territories = list() // territories lost by the gang.
- var/list/new_territories = list() // territories captured by the gang.
- var/list/gangtools = list()
- var/domination_time = NOT_DOMINATING
- var/dom_attempts = INITIAL_DOM_ATTEMPTS
- var/color
- var/influence = 0 // influence of the gang, based on how many territories they own. Can be used to buy weapons and tools from a gang uplink.
- var/winner // Once the gang wins with a dominator, this becomes true. For roundend credits purposes.
- var/list/inner_outfits = list()
- var/list/outer_outfits = list()
- var/next_point_time
- var/recalls = MAXIMUM_RECALLS // Once this reaches 0, this gang cannot force recall the shuttle with their gangtool anymore
-
-/datum/team/gang/New(starting_members)
- . = ..()
- GLOB.gangs += src
- if(starting_members)
- if(islist(starting_members))
- for(var/datum/mind/groveboss in starting_members)
- leaders += groveboss
- var/datum/antagonist/gang/boss/gb = new
- groveboss.add_antag_datum(gb, src)
- gb.equip_gang()
-
- else
- var/datum/mind/CJ = starting_members
- if(istype(CJ))
- leaders += CJ
- var/datum/antagonist/gang/boss/bossdatum = new
- CJ.add_antag_datum(bossdatum, src)
- bossdatum.equip_gang()
- next_point_time = world.time + INFLUENCE_INTERVAL
- addtimer(CALLBACK(src, .proc/handle_territories), INFLUENCE_INTERVAL)
-
-/datum/team/gang/Destroy()
- GLOB.gangs -= src
- ..()
-
-/datum/team/gang/roundend_report() //roundend report.
- var/list/report = list()
- report += ""
- if(winner)
- report += "The [name] gang successfully activated the mind dominator!"
- else
- report += "The [name] gang has failed!"
-
- report += "The [name] gang bosses were:"
- report += printplayerlist(leaders)
- report += "The [name] [member_name]s were:"
- report += printplayerlist(members-leaders)
-
- return "
[report.Join("
")]
"
-
-/datum/team/gang/proc/greet_gangster(datum/mind/gangster) //The text a person receives when recruited.
- var/message = "You are now a member of the [name] Gang!"
- message += "Help your bosses take over the station by claiming territory with spraycans. Simply spray on any unclaimed area of the station."
- message += "You can also use recruitment pens to recruit more to your cause, If your boss provides you one."
- message += "Their ultimate objective is to take over the station with a Dominator machine."
- message += "You can identify your mates by their large, \[G\] icon."
- to_chat(gangster, message)
- gangster.store_memory("You are a member of the [name] Gang!")
-
-/datum/team/gang/proc/handle_territories()
- next_point_time = world.time + INFLUENCE_INTERVAL
- if(!leaders.len)
- return
- var/added_names = ""
- var/lost_names = ""
-
- //Re-add territories that were reclaimed, so if they got tagged over, they can still earn income if they tag it back before the next status report
- var/list/reclaimed_territories = new_territories & lost_territories
- territories |= reclaimed_territories
- new_territories -= reclaimed_territories
- lost_territories -= reclaimed_territories
-
- //Process lost territories
- for(var/area in lost_territories)
- if(lost_names != "")
- lost_names += ", "
- lost_names += "[lost_territories[area]]"
- territories -= area
-
- //Calculate and report influence growth
-
- //Process new territories
- for(var/area in new_territories)
- if(added_names != "")
- added_names += ", "
- added_names += "[new_territories[area]]"
- territories += area
-
- //Report territory changes
- var/message = "[src] Gang Status Report:.
*---------*
"
- message += "[new_territories.len] new territories:
[added_names]
"
- message += "[lost_territories.len] territories lost:
[lost_names]
"
- //Clear the lists
- new_territories = list()
- lost_territories = list()
- var/total_territories = total_claimable_territories()
- var/control = round((territories.len/total_territories)*100, 1)
- var/uniformed = check_clothing()
- message += "Your gang now has [control]% control of the station.
*---------*
"
- if(domination_time != NOT_DOMINATING)
- var/new_time = max(world.time, domination_time - (uniformed * 4) - (territories.len * 2))
- if(new_time < domination_time)
- message += "Takeover shortened by [(domination_time - new_time)*0.1] seconds for defending [territories.len] territories.
"
- domination_time = new_time
- message += "[domination_time_remaining()] seconds remain in hostile takeover.
"
- else
- var/new_influence = check_territory_income()
- if(new_influence != influence)
- message += "Gang influence has increased by [new_influence - influence] for defending [territories.len] territories and [uniformed] uniformed gangsters.
"
- influence = new_influence
- message += "Your gang now has [influence] influence.
"
- message_gangtools(message)
- addtimer(CALLBACK(src, .proc/handle_territories), INFLUENCE_INTERVAL)
-
-/datum/team/gang/proc/total_claimable_territories()
- var/list/valid_territories = list()
- for(var/z in SSmapping.levels_by_trait(ZTRAIT_STATION)) //First, collect all area types on the station zlevel
- for(var/ar in SSmapping.areas_in_z["[z]"])
- var/area/A = ar
- if(!(A.type in valid_territories) && (A.area_flags & VALID_TERRITORY))
- valid_territories |= A.type
- return valid_territories.len
-
-/datum/team/gang/proc/check_territory_income()
- var/new_influence = min(999,influence + 15 + (check_clothing() * 2) + territories.len)
- return new_influence
-
-/datum/team/gang/proc/check_clothing()
- //Count uniformed gangsters
- var/uniformed = 0
- for(var/datum/mind/gangmind in members)
- if(ishuman(gangmind.current))
- var/mob/living/carbon/human/gangster = gangmind.current
- //Gangster must be alive and should return 0 not continue if conditions are met.
- if(!istype(gangster) || gangster.stat == DEAD)
- return 0
-
- var/obj/item/clothing/outfit
- var/obj/item/clothing/gang_outfit
- if(gangster.w_uniform)
- outfit = gangster.w_uniform
- if(outfit.type in inner_outfits)
- gang_outfit = outfit
- if(gangster.wear_suit)
- outfit = gangster.wear_suit
- if(outfit.type in outer_outfits)
- gang_outfit = outfit
-
- if(gang_outfit)
- uniformed++
- return uniformed
-
-/datum/team/gang/proc/adjust_influence(value)
- influence = max(0, influence + value)
-
-/datum/team/gang/proc/message_gangtools(message)
- if(!gangtools.len || !message)
- return
- for(var/i in gangtools)
- var/obj/item/device/gangtool/tool = i
- var/mob/living/mob = get(tool.loc, /mob/living)
- if(mob && mob.mind && mob.stat == CONSCIOUS)
- var/datum/antagonist/gang/gangster = mob.mind.has_antag_datum(/datum/antagonist/gang)
- if(gangster.gang == src)
- to_chat(mob, "[icon2html(tool, mob)] [message]")
- playsound(mob.loc, 'sound/machines/twobeep.ogg', 50, 1)
- return
-
-/datum/team/gang/proc/domination()
- domination_time = world.time + determine_domination_time()*10
- set_security_level("delta")
-
-/datum/team/gang/proc/determine_domination_time() // calculates the value in seconds (this is the initial domination time!)
- var/total_territories = total_claimable_territories()
- return max(180,480 - (round((territories.len/total_territories)*100, 1) * 9))
-
-/datum/team/gang/proc/domination_time_remaining() // retrieves the value from world.time based deciseconds to seconds
- var/diff = domination_time - world.time
- return round(diff * 0.1)
-
-
-#undef MAXIMUM_RECALLS
-#undef INFLUENCE_INTERVAL
diff --git a/code/game/gamemodes/gangs/gang_datums.dm b/code/game/gamemodes/gangs/gang_datums.dm
deleted file mode 100644
index c3a9cb0647..0000000000
--- a/code/game/gamemodes/gangs/gang_datums.dm
+++ /dev/null
@@ -1,139 +0,0 @@
-// Gang datums go here. If you want to create a new gang, you must be sure to edit:
-// name
-// color (must be a hex, "blue" isn't acceptable due to how spraycans are handled)
-// inner_outfits (must be a list() with typepaths of the clothes in it. One is fine, but there is support for multiple: one will be picked at random when bought)
-// outer_outfits (same as above)
-// You also need to make a gang graffiti, that will go in crayondecal.dmi inside our icons, with the same name of the gang it's assigned to. Nothing else,just the icon.
-// Those are all required. If one is missed, stuff could break.
-
-/datum/team/gang/clandestine
- name = "Clandestine"
- color = "#FF0000"
- inner_outfits = list(/obj/item/clothing/under/syndicate/combat)
- outer_outfits = list(/obj/item/clothing/suit/jacket)
-
-/datum/team/gang/prima
- name = "Prima"
- color = "#FFFF00"
- inner_outfits = list(/obj/item/clothing/under/color/yellow)
- outer_outfits = list(/obj/item/clothing/suit/hastur)
-
-/datum/team/gang/zerog
- name = "Zero-G"
- color = "#C0C0C0"
- inner_outfits = list(/obj/item/clothing/under/suit/white)
- outer_outfits = list(/obj/item/clothing/suit/hooded/wintercoat)
-
-/datum/team/gang/max
- name = "Max"
- color = "#800000"
- inner_outfits = list(/obj/item/clothing/under/color/maroon)
- outer_outfits = list(/obj/item/clothing/suit/poncho/red)
-
-/datum/team/gang/blasto
- name = "Blasto"
- color = "#000080"
- inner_outfits = list(/obj/item/clothing/under/suit/navy)
- outer_outfits = list(/obj/item/clothing/suit/jacket/miljacket)
-
-/datum/team/gang/waffle
- name = "Waffle"
- color = "#808000" //shared color with cyber, but they can keep brown cause waffles.
- inner_outfits = list(/obj/item/clothing/under/suit/green)
- outer_outfits = list(/obj/item/clothing/suit/poncho)
-
-/datum/team/gang/north
- name = "North"
- color = "#00FF00"
- inner_outfits = list(/obj/item/clothing/under/color/green)
- outer_outfits = list(/obj/item/clothing/suit/poncho/green)
-
-/datum/team/gang/omni
- name = "Omni"
- color = "#008080"
- inner_outfits = list(/obj/item/clothing/under/color/teal)
- outer_outfits = list(/obj/item/clothing/suit/chaplain/studentuni)
-
-/datum/team/gang/newton
- name = "Newton"
- color = "#A52A2A"
- inner_outfits = list(/obj/item/clothing/under/color/brown)
- outer_outfits = list(/obj/item/clothing/suit/toggle/owlwings)
-
-/datum/team/gang/cyber
- name = "Cyber"
- color = "#00f904" //Cyber and waffle shared colors, I made these guys green and made weed darker green.
- inner_outfits = list(/obj/item/clothing/under/color/lightbrown)
- outer_outfits = list(/obj/item/clothing/suit/chaplain/pharaoh)
-
-/datum/team/gang/donk
- name = "Donk"
- color = "#0000FF"
- inner_outfits = list(/obj/item/clothing/under/color/darkblue)
- outer_outfits = list(/obj/item/clothing/suit/apron/overalls)
-
-/datum/team/gang/gene
- name = "Gene"
- color = "#00FFFF"
- inner_outfits = list(/obj/item/clothing/under/color/blue)
- outer_outfits = list(/obj/item/clothing/suit/apron)
-
-/datum/team/gang/gib
- name = "Gib"
- color = "#636060" //Applying black to grayscale... Zero-G is already grey too. oh well.
- inner_outfits = list(/obj/item/clothing/under/color/black)
- outer_outfits = list(/obj/item/clothing/suit/jacket/leather/overcoat)
-
-/datum/team/gang/tunnel
- name = "Tunnel"
- color = "#FF00FF" //Gave the leather jacket to the tunnel gang over diablo.
- inner_outfits = list(/obj/item/clothing/under/costume/villain)
- outer_outfits = list(/obj/item/clothing/suit/jacket/leather)
-
-/datum/team/gang/diablo
- name = "Diablo"
- color = "#FF0000" //literal early 90s skinhead regalia.
- inner_outfits = list(/obj/item/clothing/under/pants/classicjeans)
- outer_outfits = list(/obj/item/clothing/suit/suspenders)
-
-/datum/team/gang/psyke
- name = "Psyke"
- color = "#808080"
- inner_outfits = list(/obj/item/clothing/under/color/grey)
- outer_outfits = list(/obj/item/clothing/suit/toggle/owlwings/griffinwings)
-
-/datum/team/gang/osiron
- name = "Osiron"
- color = "#FFFFFF"
- inner_outfits = list(/obj/item/clothing/under/color/white)
- outer_outfits = list(/obj/item/clothing/suit/toggle/labcoat)
-
-/datum/team/gang/sirius
- name = "Sirius"
- color = "#FFC0CB"
- inner_outfits = list(/obj/item/clothing/under/color/pink)
- outer_outfits = list(/obj/item/clothing/suit/jacket/puffer/vest)
-
-/datum/team/gang/sleepingcarp
- name = "Sleeping Carp"
- color = "#800080"
- inner_outfits = list(/obj/item/clothing/under/color/lightpurple)
- outer_outfits = list(/obj/item/clothing/suit/hooded/carp_costume)
-
-/datum/team/gang/h
- name = "H"
- color = "#993333"
- inner_outfits = list(/obj/item/clothing/under/costume/jabroni) //Why not?
- outer_outfits = list(/obj/item/clothing/suit/toggle/owlwings)
-
-/datum/team/gang/rigatonifamily
- name = "Rigatoni family"
- color = "#cc9900" // p a s t a colored
- inner_outfits = list(/obj/item/clothing/under/rank/civilian/chef)
- outer_outfits = list(/obj/item/clothing/suit/apron/chef)
-
-/datum/team/gang/weed
- name = "Weed"
- color = "#6cd648"
- inner_outfits = list(/obj/item/clothing/under/color/darkgreen)
- outer_outfits = list(/obj/item/clothing/suit/vapeshirt)
diff --git a/code/game/gamemodes/gangs/gang_decals.dm b/code/game/gamemodes/gangs/gang_decals.dm
deleted file mode 100644
index 75d8d459ef..0000000000
--- a/code/game/gamemodes/gangs/gang_decals.dm
+++ /dev/null
@@ -1,38 +0,0 @@
-/obj/effect/decal/cleanable/crayon/Initialize(mapload, main, type, e_name, graf_rot, alt_icon = null)
- . = ..()
- if(type == "poseur tag")
- var/datum/team/gang/gang = pick(subtypesof(/datum/team/gang))
- var/gangname = initial(gang.name)
- icon = 'icons/effects/crayondecal.dmi'
- icon_state = "[gangname]"
- type = null
-
-/obj/effect/decal/cleanable/crayon/gang
- icon = 'icons/effects/crayondecal.dmi'
- layer = ABOVE_NORMAL_TURF_LAYER //Harder to hide
- plane = ABOVE_WALL_PLANE
- do_icon_rotate = FALSE //These are designed to always face south, so no rotation please.
- var/datum/team/gang/gang
-
-/obj/effect/decal/cleanable/crayon/gang/Initialize(mapload, datum/team/gang/G, e_name = "gang tag", rotation = 0, mob/user)
- if(!G)
- return INITIALIZE_HINT_QDEL
- gang = G
- var/newcolor = G.color
- var/area/territory = get_base_area(src)
- icon_state = G.name
- G.new_territories |= list(territory.type = territory.name)
- //If this isn't tagged by a specific gangster there's no bonus income.
- .=..(mapload, newcolor, icon_state, e_name, rotation)
-
-/obj/effect/decal/cleanable/crayon/gang/Destroy()
- if(gang)
- var/area/territory = get_base_area(src)
- gang.territories -= territory.type
- gang.new_territories -= territory.type
- gang.lost_territories |= list(territory.type = territory.name)
- gang = null
- return ..()
-
-/obj/effect/decal/cleanable/crayon/NeverShouldHaveComeHere(turf/T)
- return isspaceturf(T) || islava(T) || istype(T, /turf/open/water) || ischasm(T)
diff --git a/code/game/gamemodes/gangs/gang_hud.dm b/code/game/gamemodes/gangs/gang_hud.dm
deleted file mode 100644
index 825d361ab0..0000000000
--- a/code/game/gamemodes/gangs/gang_hud.dm
+++ /dev/null
@@ -1,34 +0,0 @@
-/datum/atom_hud/antag/gang
- var/color = null
-
-/datum/atom_hud/antag/gang/add_to_hud(atom/A)
- if(!A)
- return
- var/image/holder = A.hud_list[ANTAG_HUD]
- if(holder)
- holder.color = color
- ..()
-
-/datum/atom_hud/antag/gang/remove_from_hud(atom/A)
- if(!A)
- return
- var/image/holder = A.hud_list[ANTAG_HUD]
- if(holder)
- holder.color = null
- ..()
-
-/datum/atom_hud/antag/gang/join_hud(mob/M)
- if(!istype(M))
- CRASH("join_hud(): [M] ([M.type]) is not a mob!")
- var/image/holder = M.hud_list[ANTAG_HUD]
- if(holder)
- holder.color = color
- ..()
-
-/datum/atom_hud/antag/gang/leave_hud(mob/M)
- if(!istype(M))
- CRASH("leave_hud(): [M] ([M.type]) is not a mob!")
- var/image/holder = M.hud_list[ANTAG_HUD]
- if(holder)
- holder.color = null
- ..()
diff --git a/code/game/gamemodes/gangs/gang_items.dm b/code/game/gamemodes/gangs/gang_items.dm
deleted file mode 100644
index d1cf006600..0000000000
--- a/code/game/gamemodes/gangs/gang_items.dm
+++ /dev/null
@@ -1,411 +0,0 @@
-/datum/gang_item
- var/name
- var/item_path
- var/cost
- var/spawn_msg
- var/category
- var/list/gang_whitelist = list()
- var/list/gang_blacklist = list()
- var/id
-
-/datum/gang_item/proc/purchase(mob/living/carbon/user, datum/team/gang/gang, obj/item/device/gangtool/gangtool, check_canbuy = TRUE)
- if(check_canbuy && !can_buy(user, gang, gangtool))
- return FALSE
- var/real_cost = get_cost(user, gang, gangtool)
- if(!spawn_item(user, gang, gangtool))
- gang.adjust_influence(-real_cost)
- to_chat(user, "You bought \the [name].")
- return TRUE
-
-/datum/gang_item/proc/spawn_item(mob/living/carbon/user, datum/team/gang/gang, obj/item/device/gangtool/gangtool) // If this returns anything other than null, something fucked up and influence won't lower.
- if(item_path)
- var/obj/item/O = new item_path(user.loc)
- user.put_in_hands(O)
- else
- return TRUE
- if(spawn_msg)
- to_chat(user, "[spawn_msg]")
-
-/datum/gang_item/proc/can_buy(mob/living/carbon/user, datum/team/gang/gang, obj/item/device/gangtool/gangtool)
- return gang && (gang.influence >= get_cost(user, gang, gangtool)) && can_see(user, gang, gangtool)
-
-/datum/gang_item/proc/can_see(mob/living/carbon/user, datum/team/gang/gang, obj/item/device/gangtool/gangtool)
- return TRUE
-
-/datum/gang_item/proc/get_cost(mob/living/carbon/user, datum/team/gang/gang, obj/item/device/gangtool/gangtool)
- return cost
-
-/datum/gang_item/proc/get_cost_display(mob/living/carbon/user, datum/team/gang/gang, obj/item/device/gangtool/gangtool)
- return "([get_cost(user, gang, gangtool)] Influence)"
-
-/datum/gang_item/proc/get_name_display(mob/living/carbon/user, datum/team/gang/gang, obj/item/device/gangtool/gangtool)
- return name
-
-/datum/gang_item/proc/get_extra_info(mob/living/carbon/user, datum/team/gang/gang, obj/item/device/gangtool/gangtool)
- return
-
-///////////////////
-//CLOTHING
-///////////////////
-
-/datum/gang_item/clothing
- category = "Purchase Gang Clothes (Only the jumpsuit and suit give you added influence):"
-
-/datum/gang_item/clothing/under
- name = "Gang Uniform"
- id = "under"
- cost = 1
-
-/datum/gang_item/clothing/under/spawn_item(mob/living/carbon/user, datum/team/gang/gang, obj/item/device/gangtool/gangtool)
- if(gang.inner_outfits.len)
- var/outfit = pick(gang.inner_outfits)
- if(outfit)
- var/obj/item/O = new outfit(user.loc)
- user.put_in_hands(O)
- to_chat(user, " This is your gang's official uniform, wearing it will increase your influence")
- return
- return TRUE
-
-/datum/gang_item/clothing/suit
- name = "Gang Armored Outerwear"
- id = "suit"
- cost = 1
-
-/datum/gang_item/clothing/suit/spawn_item(mob/living/carbon/user, datum/team/gang/gang, obj/item/device/gangtool/gangtool)
- if(gang.outer_outfits.len)
- var/outfit = pick(gang.outer_outfits)
- if(outfit)
- var/obj/item/O = new outfit(user.loc)
- O.armor = O.armor.setRating(melee = 25, bullet = 35, laser = 15, energy = 10, bomb = 30, bio = 0, rad = 0, fire = 30, acid = 30)
- O.desc += " Tailored for the [gang.name] Gang to offer the wearer moderate protection against ballistics and physical trauma."
- user.put_in_hands(O)
- to_chat(user, " This is your gang's official outerwear, wearing it will increase your influence")
- return
- return TRUE
-
-/datum/gang_item/clothing/hat
- name = "Pimp Hat"
- id = "hat"
- cost = 16
- item_path = /obj/item/clothing/head/collectable/petehat/gang
-
-/obj/item/clothing/head/collectable/petehat/gang
- name = "pimpin' hat"
- desc = "The undisputed king of style."
-
-/datum/gang_item/clothing/mask
- name = "Golden Death Mask"
- id = "mask"
- cost = 18
- item_path = /obj/item/clothing/mask/gskull
-
-/obj/item/clothing/mask/gskull
- name = "golden death mask"
- icon_state = "gskull"
- desc = "Strike terror, and envy, into the hearts of your enemies."
-
-/datum/gang_item/clothing/shoes
- name = "Bling Boots"
- id = "boots"
- cost = 20
- item_path = /obj/item/clothing/shoes/gang
-
-/obj/item/clothing/shoes/gang
- name = "blinged-out boots"
- desc = "Stand aside peasants."
- icon_state = "bling"
-
-/datum/gang_item/clothing/neck
- name = "Gold Necklace"
- id = "necklace"
- cost = 9
- item_path = /obj/item/clothing/neck/necklace/dope
-
-/datum/gang_item/clothing/hands
- name = "Decorative Brass Knuckles"
- id = "hand"
- cost = 11
- item_path = /obj/item/clothing/gloves/gang
-
-/obj/item/clothing/gloves/gang
- name = "braggadocio's brass knuckles"
- desc = "Purely decorative, don't find out the hard way."
- icon_state = "knuckles"
- w_class = 3
-
-/datum/gang_item/clothing/shades //Addition: Why not have cool shades on a gang member anyways?
- name = "Cool Sunglasses"
- id = "glasses"
- cost = 5
- item_path = /obj/item/clothing/glasses/sunglasses
-
-/datum/gang_item/clothing/belt
- name = "Badass Belt"
- id = "belt"
- cost = 13
- item_path = /obj/item/storage/belt/military/gang
-
-/obj/item/storage/belt/military/gang
- name = "badass belt"
- icon_state = "gangbelt"
- item_state = "gang"
- desc = "The belt buckle simply reads 'BAMF'."
-
-///////////////////
-//WEAPONS
-///////////////////
-
-/datum/gang_item/weapon
- category = "Purchase Weapons:"
-
-/datum/gang_item/weapon/ammo
-
-/datum/gang_item/weapon/shuriken
- name = "Shuriken"
- id = "shuriken"
- cost = 2
- item_path = /obj/item/throwing_star
-
-/datum/gang_item/weapon/switchblade
- name = "Switchblade"
- id = "switchblade"
- cost = 5
- item_path = /obj/item/switchblade
-
-/datum/gang_item/weapon/surplus //For when a gang boss is extra broke or cheap.
- name = "Surplus Rifle"
- id = "surplus"
- cost = 6
- item_path = /obj/item/gun/ballistic/automatic/surplus
-
-/datum/gang_item/weapon/ammo/surplus_ammo
- name = "Surplus Rifle Ammo"
- id = "surplus_ammo"
- cost = 3
- item_path = /obj/item/ammo_box/magazine/m10mm/rifle
-
-/datum/gang_item/weapon/improvised
- name = "Sawn-Off Improvised Shotgun"
- id = "sawn"
- cost = 5
- item_path = /obj/item/gun/ballistic/revolver/doublebarrel/improvised/sawn
-
-/datum/gang_item/weapon/ammo/improvised_ammo
- name = "Box of Buckshot"
- id = "buckshot"
- cost = 5
- item_path = /obj/item/storage/box/lethalshot
-
-/datum/gang_item/weapon/pistol
- name = "10mm Pistol"
- id = "pistol"
- cost = 25
- item_path = /obj/item/gun/ballistic/automatic/pistol
-
-/datum/gang_item/weapon/ammo/pistol_ammo
- name = "10mm Ammo"
- id = "pistol_ammo"
- cost = 10
- item_path = /obj/item/ammo_box/magazine/m10mm
-
-/datum/gang_item/weapon/sniper
- name = "Black Market .50cal Sniper Rifle"
- id = "sniper"
- cost = 35
- item_path = /obj/item/gun/ballistic/automatic/sniper_rifle
-
-/datum/gang_item/weapon/ammo/sniper_ammo
- name = "Smuggled .50cal Sniper Rounds"
- id = "sniper_ammo"
- cost = 15
- item_path = /obj/item/ammo_box/magazine/sniper_rounds
-
-/*/datum/gang_item/weapon/ammo/sleeper_ammo //no. absolutely no.
- name = "Illicit Soporific Cartridges"
- id = "sniper_ammo"
- cost = 15 //who the fuck thought a ONE-HIT K.O. for 15 gbp IN AN ENVIRONMENT WHERE WE'RE GETTING RID OF HARDSTUNS is a GOOD IDEA
- item_path = /obj/item/ammo_box/magazine/sniper_rounds/soporific*/
-
-/datum/gang_item/weapon/machinegun
- name = "Mounted Machine Gun"
- id = "MG"
- cost = 45
- item_path = /obj/machinery/manned_turret
- spawn_msg = "The mounted machine gun features enhanced responsiveness. Hold down on the trigger while firing to control where you're shooting."
-
-/datum/gang_item/weapon/machinegun/spawn_item(mob/living/carbon/user, obj/item/device/gangtool/gangtool)
- new item_path(user.loc)
- to_chat(user, spawn_msg)
-
-/datum/gang_item/weapon/uzi
- name = "Uzi SMG"
- id = "uzi"
- cost = 50
- item_path = /obj/item/gun/ballistic/automatic/mini_uzi
-
-/datum/gang_item/weapon/ammo/uzi_ammo
- name = "Uzi Ammo"
- id = "uzi_ammo"
- cost = 20
- item_path = /obj/item/ammo_box/magazine/uzim9mm
-
-///////////////////
-//EQUIPMENT
-///////////////////
-
-/datum/gang_item/equipment
- category = "Purchase Equipment:"
-
-/datum/gang_item/equipment/spraycan
- name = "Territory Spraycan"
- id = "spraycan"
- cost = 1
- item_path = /obj/item/toy/crayon/spraycan/gang
-
-/datum/gang_item/equipment/spraycan/spawn_item(mob/living/carbon/user, datum/team/gang/gang, obj/item/device/gangtool/gangtool)
- var/obj/item/O = new item_path(user.loc, gang)
- user.put_in_hands(O)
-
-/datum/gang_item/equipment/sharpener
- name = "Sharpener"
- id = "whetstone"
- cost = 3
- item_path = /obj/item/sharpener
-
-/datum/gang_item/equipment/emp
- name = "EMP Grenade"
- id = "EMP"
- cost = 7
- item_path = /obj/item/grenade/empgrenade
-
-/datum/gang_item/equipment/c4
- name = "C4 Explosive"
- id = "c4"
- cost = 7
- item_path = /obj/item/grenade/plastic/c4
-
-/datum/gang_item/equipment/frag
- name = "Fragmentation Grenade"
- id = "frag nade"
- cost = 5
- item_path = /obj/item/grenade/frag
-
-/datum/gang_item/equipment/implant_breaker
- name = "Implant Breaker"
- id = "implant_breaker"
- cost = 10
- item_path = /obj/item/implanter/gang
-
-/datum/gang_item/equipment/implant_breaker/spawn_item(mob/living/carbon/user, datum/team/gang/gang, obj/item/device/gangtool/gangtool)
- var/obj/item/O = new item_path(user.loc, gang)
- user.put_in_hands(O)
- to_chat(user, "The implant breaker is a single-use device that destroys all implants within the target before trying to recruit them to your gang. Also works on enemy gangsters.")
-
-/datum/gang_item/equipment/wetwork_boots
- name = "Wetwork boots"
- id = "wetwork"
- cost = 8
- item_path = /obj/item/clothing/shoes/combat/gang
-
-/obj/item/clothing/shoes/combat/gang
- name = "Wetwork boots"
- desc = "A gang's best hitmen are prepared for anything."
- permeability_coefficient = 0.01
- clothing_flags = NOSLIP
-
-/datum/gang_item/equipment/shield
- name = "Riot Shield"
- id = "riot_shield"
- cost = 25
- item_path = /obj/item/shield/riot
-
-/datum/gang_item/equipment/gangsheild
- name = "Tower Shield"
- id = "metal"
- cost = 45 //High block of melee and even higher for bullets
- item_path = /obj/item/shield/riot/tower
-
-/datum/gang_item/equipment/pen
- name = "Recruitment Pen"
- id = "pen"
- cost = 20
- item_path = /obj/item/pen/gang
- spawn_msg = "More recruitment pens will allow you to recruit gangsters faster. Only gang leaders can recruit with pens."
-
-/datum/gang_item/equipment/pen/purchase(mob/living/carbon/user, datum/team/gang/gang, obj/item/device/gangtool/gangtool)
- if(..())
- gangtool.free_pen = FALSE
- return TRUE
- return FALSE
-
-/datum/gang_item/equipment/pen/get_cost(mob/living/carbon/user, datum/team/gang/gang, obj/item/device/gangtool/gangtool)
- if(gangtool && gangtool.free_pen)
- return 0
- return ..()
-
-/datum/gang_item/equipment/pen/get_cost_display(mob/living/carbon/user, datum/team/gang/gang, obj/item/device/gangtool/gangtool)
- if(gangtool && gangtool.free_pen)
- return "(GET ONE FREE)"
- return ..()
-
-/datum/gang_item/equipment/gangtool
- id = "gangtool"
- cost = 5
-
-/datum/gang_item/equipment/gangtool/spawn_item(mob/living/carbon/user, datum/team/gang/gang, obj/item/device/gangtool/gangtool)
- var/item_type
- if(gang)
- item_type = /obj/item/device/gangtool/spare/lt
- if(gang.leaders.len < MAX_LEADERS_GANG)
- to_chat(user, "Gangtools allow you to promote a gangster to be your Lieutenant, enabling them to recruit and purchase items like you. Simply have them register the gangtool. You may promote up to [MAX_LEADERS_GANG-gang.leaders.len] more Lieutenants")
- else
- item_type = /obj/item/device/gangtool/spare
- var/obj/item/device/gangtool/spare/tool = new item_type(user.loc)
- user.put_in_hands(tool)
-
-/datum/gang_item/equipment/gangtool/get_name_display(mob/living/carbon/user, datum/team/gang/gang, obj/item/device/gangtool/gangtool)
- if(gang && (gang.leaders.len < gang.max_leaders))
- return "Promote a Gangster"
- return "Spare Gangtool"
-
-/datum/gang_item/equipment/dominator
- name = "Station Dominator"
- id = "dominator"
- cost = 30
- item_path = /obj/machinery/dominator
- spawn_msg = "The dominator will secure your gang's dominance over the station. Turn it on when you are ready to defend it."
-
-/datum/gang_item/equipment/dominator/can_buy(mob/living/carbon/user, datum/team/gang/gang, obj/item/device/gangtool/gangtool)
- if(!gang || !gang.dom_attempts)
- return FALSE
- return ..()
-
-/datum/gang_item/equipment/dominator/get_name_display(mob/living/carbon/user, datum/team/gang/gang, obj/item/device/gangtool/gangtool)
- if(!gang || !gang.dom_attempts)
- return ..()
- return "[..()]"
-
-/datum/gang_item/equipment/dominator/get_cost_display(mob/living/carbon/user, datum/team/gang/gang, obj/item/device/gangtool/gangtool)
- if(!gang || !gang.dom_attempts)
- return "(Out of stock)"
- return ..()
-
-/datum/gang_item/equipment/dominator/get_extra_info(mob/living/carbon/user, datum/team/gang/gang, obj/item/device/gangtool/gangtool)
- if(gang)
- return "This device requires a 5x5 area clear of walls to FUNCTION. (Estimated Takeover Time: [round(gang.determine_domination_time()/60,0.1)] minutes)"
-
-/datum/gang_item/equipment/dominator/purchase(mob/living/carbon/user, datum/team/gang/gang, obj/item/device/gangtool/gangtool)
- var/area/userarea = get_base_area(user)
- if(!(userarea.type in gang.territories|gang.new_territories))
- to_chat(user,"The dominator can be spawned only on territory controlled by your gang!")
- return FALSE
- for(var/obj/obj in get_turf(user))
- if(obj.density)
- to_chat(user, "There's not enough room here!")
- return FALSE
-
- return ..()
-
-/datum/gang_item/equipment/dominator/spawn_item(mob/living/carbon/user, datum/team/gang/gang, obj/item/device/gangtool/gangtool)
- new item_path(user.loc)
- to_chat(user, spawn_msg)
diff --git a/code/game/gamemodes/gangs/gang_pen.dm b/code/game/gamemodes/gangs/gang_pen.dm
deleted file mode 100644
index 09cea5cecb..0000000000
--- a/code/game/gamemodes/gangs/gang_pen.dm
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Gang Boss Pens
- */
-/obj/item/pen/gang
- var/cooldown
- var/last_used
-
-/obj/item/pen/gang/Initialize()
- . = ..()
- last_used = world.time
-
-/obj/item/pen/gang/attack(mob/living/M, mob/user, stealth = TRUE) //ha
- if(!istype(M))
- return
- if(!ishuman(M) || !ishuman(user) || M.stat == DEAD)
- return ..()
- //var/datum/antagonist/gang/boss/L = user.mind.has_antag_datum(/datum/antagonist/gang/boss) //Pen works with bosses only.
- var/datum/antagonist/gang/L = user.mind.has_antag_datum(/datum/antagonist/gang) //Pen works with anyone in gang.
- if(!L)
- return ..()
- if(!..())
- return
- if(cooldown)
- to_chat(user, "[src] needs more time to recharge before it can be used.")
- return
- if(!M.client || !M.mind)
- to_chat(user, "A braindead gangster is an useless gangster!")
- return
- var/datum/team/gang/gang = L.gang
- if(!add_gangster(user, gang, M.mind))
- return
- cooldown = TRUE
- icon_state = "pen_blink"
- var/cooldown_time = 600/gang.leaders.len
- addtimer(CALLBACK(src, .proc/cooldown), cooldown_time)
-
-/obj/item/pen/gang/proc/cooldown()
- cooldown = FALSE
- icon_state = "pen"
- var/mob/M = loc
- if(istype(M))
- to_chat(M, "[icon2html(src, M)] [src][(loc == M)?(""):(" in your [loc]")] vibrates softly. It is ready to be used again.")
-
-/obj/item/pen/gang/proc/add_gangster(mob/user, datum/team/gang/gang, datum/mind/gangster_mind, check = TRUE) // Basically a wrapper to add_antag_datum.
- var/datum/antagonist/dudegang = gangster_mind.has_antag_datum(/datum/antagonist/gang)
- if(dudegang)
- if(dudegang == gang)
- to_chat(user, "This mind is already controlled by your gang!")
- return
- to_chat(user, "This mind is already controlled by someone else!")
- return
- if(check && HAS_TRAIT(gangster_mind.current, TRAIT_MINDSHIELD)) //Check to see if the potential gangster is implanted
- to_chat(user, "This mind is too strong to control!")
- return
- var/mob/living/carbon/human/H = gangster_mind.current // we are sure the dude's human cause it's checked in attack()
- H.silent = max(H.silent, 5)
- H.DefaultCombatKnockdown(100)
- gangster_mind.add_antag_datum(/datum/antagonist/gang, gang)
- return TRUE
diff --git a/code/game/gamemodes/gangs/gangs.dm b/code/game/gamemodes/gangs/gangs.dm
deleted file mode 100644
index 1352e9340d..0000000000
--- a/code/game/gamemodes/gangs/gangs.dm
+++ /dev/null
@@ -1,67 +0,0 @@
-//gang.dm
-//Gang War Game Mode
-GLOBAL_LIST_INIT(possible_gangs, subtypesof(/datum/team/gang))
-GLOBAL_LIST_EMPTY(gangs)
-/datum/game_mode/gang
- name = "gang war"
- config_tag = "gang"
- antag_flag = ROLE_GANG
- chaos = 9
- restricted_jobs = list("Prisoner", "AI", "Cyborg")
- protected_jobs = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel", "Chief Engineer", "Chief Medical Officer", "Research Director", "Quartermaster")
- required_players = 15
- required_enemies = 0
- recommended_enemies = 2
- enemy_minimum_age = 14
-
- announce_span = "danger"
- announce_text = "A violent turf war has erupted on the station!\n\
- Gangsters: Take over the station with a dominator.\n\
- Crew: Prevent the gangs from expanding and initiating takeover."
-
- var/list/datum/mind/gangboss_candidates = list()
-
-/datum/game_mode/gang/generate_report()
- return "Cybersun Industries representatives claimed that they, in joint research with the Tiger Cooperative, have made a major breakthrough in brainwashing technology, and have \
- made the nanobots that apply the \"conversion\" very small and capable of fitting into usually innocent objects - namely, pens. While they refused to outsource this technology for \
- months to come due to its flaws, they reported some as missing but passed it off to carelessness. At Central Command, we don't like mysteries, and we have reason to believe that this \
- technology was stolen for anti-Nanotrasen use. Be on the lookout for territory claims and unusually violent crew behavior, applying mindshield implants as necessary."
-
-/datum/game_mode/gang/pre_setup()
- if(CONFIG_GET(flag/protect_roles_from_antagonist))
- restricted_jobs += protected_jobs
-
- if(CONFIG_GET(flag/protect_assistant_from_antagonist))
- restricted_jobs += "Assistant"
-
- //Spawn more bosses depending on server population
- var/gangs_to_create = 2
- if(prob(num_players()) && num_players() > 1.5*required_players)
- gangs_to_create++
- if(prob(num_players()) && num_players() > 2*required_players)
- gangs_to_create++
- gangs_to_create = min(gangs_to_create, GLOB.possible_gangs.len)
-
- for(var/i in 1 to gangs_to_create)
- if(!antag_candidates.len)
- break
-
- //Now assign a boss for the gang
- var/datum/mind/boss = pick_n_take(antag_candidates)
- antag_candidates -= boss
- gangboss_candidates += boss
- boss.restricted_roles = restricted_jobs
-
- if(gangboss_candidates.len < 1) //Need at least one gangs
- return
-
- return TRUE
-
-/datum/game_mode/gang/post_setup()
- set waitfor = FALSE
- ..()
- for(var/i in gangboss_candidates)
- var/datum/mind/M = i
- var/datum/antagonist/gang/boss/B = new()
- M.add_antag_datum(B)
- B.equip_gang()
diff --git a/code/game/gamemodes/gangs/gangtool.dm b/code/game/gamemodes/gangs/gangtool.dm
deleted file mode 100644
index 32272ae51a..0000000000
--- a/code/game/gamemodes/gangs/gangtool.dm
+++ /dev/null
@@ -1,259 +0,0 @@
-//gangtool device
-/obj/item/device/gangtool
- name = "suspicious device"
- desc = "A strange device of sorts. Hard to really make out what it actually does if you don't know how to operate it."
- icon = 'icons/obj/device.dmi'
- icon_state = "gangtool"
- item_state = "radio"
- lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi'
- righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi'
- throwforce = 0
- w_class = WEIGHT_CLASS_TINY
- throw_speed = 3
- throw_range = 7
- flags_1 = CONDUCT_1
- var/datum/team/gang/gang //Which gang uses this?
- var/recalling = 0
- var/outfits = 2
- var/free_pen = 0
- var/promotable = FALSE
- var/static/list/buyable_items = list()
- var/list/tags = list()
-
-/obj/item/device/gangtool/Initialize()
- . = ..()
- update_icon()
- for(var/i in subtypesof(/datum/gang_item))
- var/datum/gang_item/G = i
- var/id = initial(G.id)
- var/cat = initial(G.category)
- if(id)
- if(!islist(buyable_items[cat]))
- buyable_items[cat] = list()
- buyable_items[cat][id] = new G
-/obj/item/device/gangtool/Destroy()
- if(gang)
- gang.gangtools -= src
- return ..()
-
-/obj/item/device/gangtool/attack_self(mob/user)
- ..()
- if (!can_use(user))
- return
- var/datum/antagonist/gang/boss/L = user.mind.has_antag_datum(/datum/antagonist/gang/boss)
- var/dat
- if(!gang)
- dat += "This device is not registered.
"
- if(L)
- if(promotable && L.gang.leaders.len < L.gang.max_leaders)
- dat += "Give this device to another member of your organization to use to promote them to Lieutenant.
"
- dat += "If this is meant as a spare device for yourself:
"
- dat += "Register Device as Spare
"
- else if(promotable)
- var/datum/antagonist/gang/sweet = user.mind.has_antag_datum(/datum/antagonist/gang)
- if(sweet.gang.leaders.len < sweet.gang.max_leaders)
- dat += "You have been selected for a promotion!
"
- dat += "Accept Promotion
"
- else
- dat += "No promotions available: All positions filled.
"
- else
- dat += "This device is not authorized to promote.
"
- else
- if(gang.domination_time != NOT_DOMINATING)
- dat += "Takeover In Progress:
[DisplayTimeText(gang.domination_time_remaining() * 10)] remain"
-
- dat += "Registration: [gang.name] Gang Boss
"
- dat += "Organization Size: [gang.members.len] | Station Control: [gang.territories.len] territories under control. | Influence: [gang.influence]
"
- dat += "Time until Influence grows: [time2text(gang.next_point_time - world.time, "mm:ss")]
"
- dat += "Send message to Gang
"
- dat += "Recall shuttle
"
- dat += "
"
- for(var/cat in buyable_items)
- dat += "[cat]
"
- for(var/id in buyable_items[cat])
- var/datum/gang_item/G = buyable_items[cat][id]
- if(!G.can_see(user, gang, src))
- continue
-
- var/cost = G.get_cost_display(user, gang, src)
- if(cost)
- dat += cost + " "
-
- var/toAdd = G.get_name_display(user, gang, src)
- if(G.can_buy(user, gang, src))
- toAdd = "[toAdd]"
- dat += toAdd
- var/extra = G.get_extra_info(user, gang, src)
- if(extra)
- dat += "
[extra]"
- dat += "
"
- dat += "
"
-
- dat += "Refresh
"
-
- var/datum/browser/popup = new(user, "gangtool", "Welcome to GangTool v4.0", 340, 625)
- popup.set_content(dat)
- popup.open()
-
-/obj/item/device/gangtool/Topic(href, href_list)
- if(!can_use(usr))
- return
-
- add_fingerprint(usr)
-
- if(href_list["register"])
- register_device(usr)
-
- else if(!gang) //Gangtool must be registered before you can use the functions below
- return
-
- if(href_list["purchase"])
- if(islist(buyable_items[href_list["cat"]]))
- var/list/L = buyable_items[href_list["cat"]]
- var/datum/gang_item/G = L[href_list["id"]]
- if(G && G.can_buy(usr, gang, src))
- G.purchase(usr, gang, src, FALSE)
-
- if(href_list["commute"])
- ping_gang(usr)
- if(href_list["recall"])
- recall(usr)
- attack_self(usr)
-
-/obj/item/device/gangtool/update_icon()
- overlays.Cut()
- var/image/I = new(icon, "[icon_state]-overlay")
- if(gang)
- I.color = gang.color
- overlays.Add(I)
-
-/obj/item/device/gangtool/proc/ping_gang(mob/user)
- if(!can_use(user))
- return
- var/message = stripped_input(user,"Discreetly send a gang-wide message.","Send Message")
- if(!message || !can_use(user))
- return
- if(!is_station_level(user.z))
- to_chat(user, "[icon2html(src, user)]Error: Station out of range.")
- return
- if(gang.members.len)
- var/datum/antagonist/gang/G = user.mind.has_antag_datum(/datum/antagonist/gang)
- if(!G)
- return
- var/ping = "[gang.name] [G.message_name] [user.real_name]: [message]"
- for(var/datum/mind/ganger in gang.members)
- if(ganger.current && is_station_level(ganger.current.z) && (ganger.current.stat == CONSCIOUS))
- to_chat(ganger.current, ping)
- for(var/mob/M in GLOB.dead_mob_list)
- var/link = FOLLOW_LINK(M, user)
- to_chat(M, "[link] [ping]")
- user.log_talk(message,LOG_SAY, tag="[gang.name] gangster")
-
-/obj/item/device/gangtool/proc/register_device(mob/user)
- if(gang) //It's already been registered!
- return
- var/datum/antagonist/gang/G = user.mind.has_antag_datum(/datum/antagonist/gang)
- if(G)
- gang = G.gang
- gang.gangtools += src
- update_icon()
- if(!(user.mind in gang.leaders) && promotable)
- G.promote()
- free_pen = TRUE
- gang.message_gangtools("[user] has been promoted to Lieutenant.")
- to_chat(user, "The Gangtool you registered will allow you to purchase weapons and equipment, and send messages to your gang.")
- to_chat(user, "Unlike regular gangsters, you may use recruitment pens to add recruits to your gang. Use them on unsuspecting crew members to recruit them. Don't forget to get your one free pen from the gangtool.")
- else
- to_chat(user, "ACCESS DENIED: Unauthorized user.")
-
-/obj/item/device/gangtool/proc/recall(mob/user)
- if(!recallchecks(user))
- return
- if(recalling)
- to_chat(user, "Error: Recall already in progress.")
- return
- gang.message_gangtools("[user] is attempting to recall the emergency shuttle.")
- recalling = TRUE
- to_chat(user, "[icon2html(src, loc)]Generating shuttle recall order with codes retrieved from last call signal...")
- addtimer(CALLBACK(src, .proc/recall2, user), rand(100,300))
-
-/obj/item/device/gangtool/proc/recall2(mob/user)
- if(!recallchecks(user))
- return
- to_chat(user, "[icon2html(src, loc)]Shuttle recall order generated. Accessing station long-range communication arrays...")
- addtimer(CALLBACK(src, .proc/recall3, user), rand(100,300))
-
-/obj/item/device/gangtool/proc/recall3(mob/user)
- if(!recallchecks(user))
- return
- var/list/living_crew = list()//shamelessly copied from mulligan code, there should be a helper for this
- for(var/mob/Player in GLOB.mob_list)
- if(Player.mind && Player.stat != DEAD && !isnewplayer(Player) && !isbrain(Player) && Player.client)
- living_crew += Player
- var/malc = CONFIG_GET(number/midround_antag_life_check)
- if(living_crew.len / GLOB.joined_player_list.len <= malc) //Shuttle cannot be recalled if too many people died
- to_chat(user, "[icon2html(src, user)]Error: Station communication systems compromised. Unable to establish connection.")
- recalling = FALSE
- return
- to_chat(user, "[icon2html(src, loc)]Comm arrays accessed. Broadcasting recall signal...")
- addtimer(CALLBACK(src, .proc/recallfinal, user), rand(100,300))
-
-/obj/item/device/gangtool/proc/recallfinal(mob/user)
- if(!recallchecks(user))
- return
- recalling = FALSE
- log_game("[key_name(user)] has tried to recall the shuttle with a gangtool.")
- message_admins("[key_name_admin(user)] has tried to recall the shuttle with a gangtool.", 1)
- if(SSshuttle.cancelEvac(user))
- gang.recalls--
- return TRUE
-
- to_chat(user, "[icon2html(src, loc)]No response recieved. Emergency shuttle cannot be recalled at this time.")
- return
-
-/obj/item/device/gangtool/proc/recallchecks(mob/user)
- if(!can_use(user))
- return
- if(SSshuttle.emergencyNoRecall)
- return
- if(!gang.recalls)
- to_chat(user, "Error: Unable to access communication arrays. Firewall has logged our signature and is blocking all further attempts.")
- return
- if(SSshuttle.emergency.mode != SHUTTLE_CALL) //Shuttle can only be recalled when it's moving to the station
- to_chat(user, "[icon2html(src, user)]Emergency shuttle cannot be recalled at this time.")
- recalling = FALSE
- return
- if(!gang.dom_attempts)
- to_chat(user, "[icon2html(src, user)]Error: Unable to access communication arrays. Firewall has logged our signature and is blocking all further attempts.")
- recalling = FALSE
- return
- if(!is_station_level(user.z)) //Shuttle can only be recalled while on station
- to_chat(user, "[icon2html(src, user)]Error: Device out of range of station communication arrays.")
- recalling = FALSE
- return
- return TRUE
-
-/obj/item/device/gangtool/proc/can_use(mob/living/carbon/human/user)
- if(!istype(user))
- return
- if(user.incapacitated())
- return
- if(!(src in user.contents))
- return
- if(!user.mind)
- return
- var/datum/antagonist/gang/G = user.mind.has_antag_datum(/datum/antagonist/gang)
- if(!G)
- to_chat(user, "Huh, what's this?")
- return
- if(!isnull(gang) && G.gang != gang)
- to_chat(user, "You cannot use gang tools owned by enemy gangs!")
- return
- return TRUE
-
-
-/obj/item/device/gangtool/spare
- outfits = TRUE
-
-/obj/item/device/gangtool/spare/lt
- promotable = TRUE
diff --git a/code/game/gamemodes/gangs/implant_gang.dm b/code/game/gamemodes/gangs/implant_gang.dm
deleted file mode 100644
index cad54d4fc1..0000000000
--- a/code/game/gamemodes/gangs/implant_gang.dm
+++ /dev/null
@@ -1,61 +0,0 @@
-/obj/item/implant/gang
- name = "gang implant"
- desc = "Makes you a gangster or such."
- activated = 0
- var/datum/team/gang/gang
-
-/obj/item/implant/gang/Initialize(loc, setgang)
- .=..()
- gang = setgang
-
-/obj/item/implant/gang/Destroy()
- gang = null
- return ..()
-
-/obj/item/implant/gang/get_data()
- var/dat = {"Implant Specifications:
- Name: Criminal brainwash implant
- Life: A few seconds after injection.
- Important Notes: Illegal
-
- Implant Details:
- Function: Contains a small pod of nanobots that change the host's brain to be loyal to a certain organization.
- Special Features: This device will also emit a small EMP pulse, destroying any other implants within the host's brain.
- Integrity: Implant's EMP function will destroy itself in the process."}
- return dat
-
-/obj/item/implant/gang/implant(mob/living/target, mob/user, silent = 0)
- if(!target || !target.mind || target.stat == DEAD)
- return 0
- var/datum/antagonist/gang/G = target.mind.has_antag_datum(/datum/antagonist/gang)
- if(G && G.gang == G)
- return 0 // it's pointless
- if(..())
- for(var/obj/item/implant/I in target.implants)
- if(I != src)
- qdel(I)
-
- if(ishuman(target))
- var/success
- if(G)
- if(!istype(G, /datum/antagonist/gang/boss))
- success = TRUE //Was not a gang boss, convert as usual
- target.mind.remove_antag_datum(/datum/antagonist/gang)
- else
- success = TRUE
- if(!success)
- target.visible_message("[target] seems to resist the implant!", "You feel the influence of your enemies try to invade your mind!")
- return FALSE
- target.mind.add_antag_datum(/datum/antagonist/gang, gang)
- qdel(src)
- return TRUE
-
-/obj/item/implanter/gang
- name = "implanter (gang)"
-
-/obj/item/implanter/gang/Initialize(loc, gang)
- if(!gang)
- qdel(src)
- return
- imp = new /obj/item/implant/gang(src,gang)
- .=..()
diff --git a/code/game/gamemodes/objective.dm b/code/game/gamemodes/objective.dm
index 67ba43dc4f..0358c3af2c 100644
--- a/code/game/gamemodes/objective.dm
+++ b/code/game/gamemodes/objective.dm
@@ -13,6 +13,7 @@ GLOBAL_LIST_EMPTY(objectives)
var/completed = FALSE //currently only used for custom objectives.
var/completable = TRUE //Whether this objective shows greentext when completed
var/martyr_compatible = FALSE //If the objective is compatible with martyr objective, i.e. if you can still do it while dead.
+ var/objective_name = "Objective" //name used in printing this objective (Objective #1)
/datum/objective/New(var/text)
GLOB.objectives += src // CITADEL EDIT FOR CRYOPODS
diff --git a/code/game/machinery/camera/camera.dm b/code/game/machinery/camera/camera.dm
index 4d4ddf2f52..b680377d0a 100644
--- a/code/game/machinery/camera/camera.dm
+++ b/code/game/machinery/camera/camera.dm
@@ -407,7 +407,7 @@
/obj/machinery/camera/get_remote_view_fullscreens(mob/user)
if(view_range == short_range) //unfocused
- user.overlay_fullscreen("remote_view", /atom/movable/screen/fullscreen/impaired, 2)
+ user.overlay_fullscreen("remote_view", /atom/movable/screen/fullscreen/scaled/impaired, 2)
/obj/machinery/camera/update_remote_sight(mob/living/user)
user.see_invisible = SEE_INVISIBLE_LIVING //can't see ghosts through cameras
diff --git a/code/game/machinery/computer/camera_advanced.dm b/code/game/machinery/computer/camera_advanced.dm
index 9d675bc5dd..992807bed7 100644
--- a/code/game/machinery/computer/camera_advanced.dm
+++ b/code/game/machinery/computer/camera_advanced.dm
@@ -275,7 +275,7 @@
if(final)
playsound(origin, 'sound/machines/terminal_prompt_confirm.ogg', 25, 0)
remote_eye.setLoc(get_turf(final))
- C.overlay_fullscreen("flash", /atom/movable/screen/fullscreen/flash/static)
+ C.overlay_fullscreen("flash", /atom/movable/screen/fullscreen/tiled/flash/static)
C.clear_fullscreen("flash", 3) //Shorter flash than normal since it's an ~~advanced~~ console!
else
playsound(origin, 'sound/machines/terminal_prompt_deny.ogg', 25, 0)
diff --git a/code/game/mecha/combat/five_stars.dm b/code/game/mecha/combat/five_stars.dm
new file mode 100644
index 0000000000..250ec7f3f6
--- /dev/null
+++ b/code/game/mecha/combat/five_stars.dm
@@ -0,0 +1,21 @@
+/obj/mecha/combat/five_stars
+ desc = "A state of the art tank deployed by the Spinward Stellar Coalition National Guard."
+ name = "\improper Tank"
+ icon = 'icons/mecha/mecha_96x96.dmi'
+ icon_state = "five_stars"
+ armor = list("melee" = 100, "bullet" = 50, "laser" = 35, "energy" = 35, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100)
+ step_in = 4
+ dir_in = 1 //Facing North.
+ max_integrity = 800
+ pixel_x = -32
+ pixel_y = -32
+
+/obj/mecha/combat/five_stars/Initialize()
+ . = ..()
+ var/obj/item/mecha_parts/mecha_equipment/ME = new /obj/item/mecha_parts/mecha_equipment/weapon/ballistic/missile_rack/spacecops(src)
+ ME.attach(src)
+ ME = new /obj/item/mecha_parts/mecha_equipment/weapon/ballistic/lmg(src)
+ ME.attach(src)
+ ME = new /obj/item/mecha_parts/mecha_equipment/tesla_energy_relay(src)
+ ME.attach(src)
+ max_ammo()
diff --git a/code/game/mecha/equipment/weapons/weapons.dm b/code/game/mecha/equipment/weapons/weapons.dm
index 30bf06b475..a9d7853187 100644
--- a/code/game/mecha/equipment/weapons/weapons.dm
+++ b/code/game/mecha/equipment/weapons/weapons.dm
@@ -342,6 +342,9 @@
harmful = TRUE
ammo_type = "missiles_he"
+/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/missile_rack/spacecops
+ projectiles = 420
+
/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/missile_rack/breaching
name = "\improper BRM-6 missile rack"
desc = "A weapon for combat exosuits. Launches low-explosive breaching missiles designed to explode only when striking a sturdy target."
diff --git a/code/game/objects/effects/decals/crayon.dm b/code/game/objects/effects/decals/crayon.dm
index 387b8c167d..d84b3f15ed 100644
--- a/code/game/objects/effects/decals/crayon.dm
+++ b/code/game/objects/effects/decals/crayon.dm
@@ -1,3 +1,5 @@
+GLOBAL_LIST(gang_tags)
+
/obj/effect/decal/cleanable/crayon
name = "rune"
desc = "Graffiti. Damn kids."
@@ -45,6 +47,8 @@
data["pixel_x"] = pixel_x
if(pixel_y != initial(pixel_y))
data["pixel_y"] = pixel_y
+/obj/effect/decal/cleanable/crayon/NeverShouldHaveComeHere(turf/T)
+ return isgroundlessturf(T)
/obj/effect/decal/cleanable/crayon/PersistenceLoad(list/data)
. = ..()
@@ -63,3 +67,18 @@
pixel_x = data["pixel_x"]
if(data["pixel_y"])
pixel_y = data["pixel_y"]
+
+/obj/effect/decal/cleanable/crayon/gang
+ name = "Leet Like Jeff K gang tag"
+ desc = "Looks like someone's claimed this area for Leet Like Jeff K."
+ icon = 'icons/obj/gang/tags.dmi'
+ layer = BELOW_MOB_LAYER
+ var/datum/team/gang/my_gang
+
+/obj/effect/decal/cleanable/crayon/gang/Initialize(mapload, main, type, e_name, graf_rot, alt_icon = null)
+ . = ..()
+ LAZYADD(GLOB.gang_tags, src)
+
+/obj/effect/decal/cleanable/crayon/gang/Destroy()
+ LAZYREMOVE(GLOB.gang_tags, src)
+ ..()
diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm
index 094b1ae0ab..953d21d318 100644
--- a/code/game/objects/items.dm
+++ b/code/game/objects/items.dm
@@ -1237,6 +1237,32 @@ GLOBAL_VAR_INIT(embedpocalypse, FALSE) // if true, all items will be able to emb
embed_chance_turf_mod = (!isnull(embedding["embed_chance_turf_mod"]) ? embedding["embed_chance_turf_mod"] : EMBED_CHANCE_TURF_MOD))
return TRUE
+
+/**
+ * * An interrupt for offering an item to other people, called mainly from [/mob/living/carbon/proc/give], in case you want to run your own offer behavior instead.
+ *
+ * * Return TRUE if you want to interrupt the offer.
+ *
+ * * Arguments:
+ * * offerer - the person offering the item
+ */
+/obj/item/proc/on_offered(mob/living/carbon/offerer)
+ if(SEND_SIGNAL(src, COMSIG_ITEM_OFFERING, offerer) & COMPONENT_OFFER_INTERRUPT)
+ return TRUE
+
+/**
+ * * An interrupt for someone trying to accept an offered item, called mainly from [/mob/living/carbon/proc/take], in case you want to run your own take behavior instead.
+ *
+ * * Return TRUE if you want to interrupt the taking.
+ *
+ * * Arguments:
+ * * offerer - the person offering the item
+ * * taker - the person trying to accept the offer
+ */
+/obj/item/proc/on_offer_taken(mob/living/carbon/offerer, mob/living/carbon/taker)
+ if(SEND_SIGNAL(src, COMSIG_ITEM_OFFER_TAKEN, offerer, taker) & COMPONENT_OFFER_INTERRUPT)
+ return TRUE
+
/**
* Updates all action buttons associated with this item
*
diff --git a/code/game/objects/items/crayons.dm b/code/game/objects/items/crayons.dm
index 42626c4a3b..bf1fdb1716 100644
--- a/code/game/objects/items/crayons.dm
+++ b/code/game/objects/items/crayons.dm
@@ -298,7 +298,7 @@
/obj/item/toy/crayon/proc/draw_on(atom/target, mob/user, proximity, params)
var/static/list/punctuation = list("!","?",".",",","/","+","-","=","%","#","&")
-
+ var/istagger = HAS_TRAIT(user, TRAIT_TAGGER)
var/cost = 1
if(paint_mode == PAINT_LARGE_HORIZONTAL)
cost = 5
@@ -355,14 +355,14 @@
else if(drawing in graffiti|oriented)
temp = "graffiti"
- // If a gang member is using a gang spraycan, it'll behave differently
- var/gang_mode = FALSE
- if(gang && user.mind && user.mind.has_antag_datum(/datum/antagonist/gang)) //Heres a check.
- gang_mode = TRUE // No more runtimes if a non-gang member sprays a gang can, it just works like normal cans.
- // discontinue if the area isn't valid for tagging because gang "honour"
+ var/gang_mode
+ if(user.mind)
+ gang_mode = user.mind.has_antag_datum(/datum/antagonist/gang)
+
if(gang_mode && (!can_claim_for_gang(user, target)))
return
+
var/graf_rot
if(drawing in oriented)
switch(user.dir)
@@ -390,16 +390,12 @@
audible_message("You hear spraying.")
playsound(user.loc, 'sound/effects/spray.ogg', 5, 1, 5)
- var/takes_time = !instant //For order purposes, since I'm maximum bad.
- if(gang_mode)
- takes_time = TRUE
-
var/wait_time = 50
if(paint_mode == PAINT_LARGE_HORIZONTAL)
wait_time *= 3
- if(takes_time) //This is what deteremines the time it takes to spray a tag in gang mode. 50 is Default.
- if(!do_after(user, gang_tag_delay, target = target)) //25 is a good number, but we have gang_tag_delay var now.
+ if(gang_mode || !instant)
+ if(!do_after(user, 50, target = target))
return
if(length(text_buffer))
@@ -410,16 +406,15 @@
if(actually_paints)
+ var/obj/effect/decal/cleanable/crayon/C = new(target, paint_color, drawing, temp, graf_rot)
if(gang_mode)
- // Double check it wasn't tagged in the meanwhile.
if(!can_claim_for_gang(user, target))
return
- tag_for_gang(user, target)
+ tag_for_gang(user, target, gang_mode)
affected_turfs += target
else
switch(paint_mode)
if(PAINT_NORMAL)
- var/obj/effect/decal/cleanable/crayon/C = new(target, paint_color, drawing, temp, graf_rot)
C.add_hiddenprint(user)
if(precision_mode)
C.pixel_x = clamp(precision_x, -(world.icon_size/2), world.icon_size/2)
@@ -432,14 +427,18 @@
var/turf/left = locate(target.x-1,target.y,target.z)
var/turf/right = locate(target.x+1,target.y,target.z)
if(isValidSurface(left) && isValidSurface(right))
- var/obj/effect/decal/cleanable/crayon/C = new(left, paint_color, drawing, temp, graf_rot, PAINT_LARGE_HORIZONTAL_ICON)
- C.add_hiddenprint(user)
+ C = new(left, paint_color, drawing, temp, graf_rot, PAINT_LARGE_HORIZONTAL_ICON)
affected_turfs += left
affected_turfs += right
affected_turfs += target
else
to_chat(user, "There isn't enough space to paint!")
return
+ C.add_hiddenprint(user)
+ if(istagger)
+ C.AddComponent(/datum/element/art, GOOD_ART)
+ else
+ C.AddComponent(/datum/element/art, BAD_ART)
if(!instant)
to_chat(user, "You finish drawing \the [temp].")
@@ -462,52 +461,6 @@
reagents.trans_to(t, ., volume_multiplier)
check_empty(user)
-
-//////////////Gang mode stuff/////////////////
-/obj/item/toy/crayon/proc/can_claim_for_gang(mob/user, atom/target)
- // Check area validity.
- // Reject space, player-created areas, and non-station z-levels.
- var/area/A = get_base_area(target)
- if(!A || (!is_station_level(A.z)) || !(A.area_flags & VALID_TERRITORY))
- to_chat(user, "[A] is unsuitable for tagging.")
- return FALSE
-
- var/spraying_over = FALSE
- for(var/G in target)
- var/obj/effect/decal/cleanable/crayon/gang/gangtag = G
- if(istype(gangtag))
- var/datum/antagonist/gang/GA = user.mind.has_antag_datum(/datum/antagonist/gang)
- if(gangtag.gang != GA.gang)
- spraying_over = TRUE
- break
-
- var/occupying_gang = territory_claimed(A, user)
- if(occupying_gang && !spraying_over)
- to_chat(user, "[A] has already been tagged by the [occupying_gang] gang! You must get rid of or spray over the old tag first!")
- return FALSE
-
- // If you pass the gauntlet of checks, you're good to proceed
- return TRUE
-
-/obj/item/toy/crayon/proc/territory_claimed(area/territory, mob/user)
- for(var/datum/team/gang/G in GLOB.gangs)
- if(territory.type in (G.territories|G.new_territories))
- . = G.name
- break
-
-/obj/item/toy/crayon/proc/tag_for_gang(mob/user, atom/target)
- //Delete any old markings on this tile, including other gang tags
- for(var/obj/effect/decal/cleanable/crayon/old_marking in target)
- qdel(old_marking)
-
- var/datum/antagonist/gang/G = user.mind.has_antag_datum(/datum/antagonist/gang)
- var/area/territory = get_base_area(target)
-
- new /obj/effect/decal/cleanable/crayon/gang(target,G.gang,"graffiti",0,user) // Heres the gang tag.
- to_chat(user, "You tagged [territory] for your gang!")
-/////////////////Gang end////////////////////
-
-
/obj/item/toy/crayon/attack(mob/M, mob/user)
if(edible && (M == user))
to_chat(user, "You take a bite of the [src.name]. Delicious!")
@@ -521,6 +474,49 @@
else
..()
+//////////////Gang mode stuff/////////////////
+/obj/item/toy/crayon/proc/can_claim_for_gang(mob/user, atom/target)
+ var/area/A = get_area(target)
+ if(!A || (!is_station_level(A.z)))
+ to_chat(user, "[A] is unsuitable for tagging.")
+ return FALSE
+
+ var/spraying_over = FALSE
+ for(var/obj/effect/decal/cleanable/crayon/gang/G in target)
+ spraying_over = TRUE
+
+ for(var/obj/machinery/power/apc in target)
+ to_chat(user, "You can't tag an APC.")
+ return FALSE
+
+ var/occupying_gang = territory_claimed(A, user)
+ if(occupying_gang && !spraying_over)
+ to_chat(user, "[A] has already been tagged by a gang! You must find and spray over the old tag first!")
+ return FALSE
+
+ // stolen from oldgang lmao
+ return TRUE
+
+/obj/item/toy/crayon/proc/tag_for_gang(mob/user, atom/target, datum/antagonist/gang/user_gang)
+ for(var/obj/effect/decal/cleanable/crayon/old_marking in target)
+ qdel(old_marking)
+
+ var/area/territory = get_area(target)
+
+ var/obj/effect/decal/cleanable/crayon/gang/tag = new /obj/effect/decal/cleanable/crayon/gang(target)
+ tag.my_gang = user_gang.my_gang
+ tag.icon_state = "[user_gang.gang_id]_tag"
+ tag.name = "[tag.my_gang.name] gang tag"
+ tag.desc = "Looks like someone's claimed this area for [tag.my_gang.name]."
+ to_chat(user, "You tagged [territory] for [tag.my_gang.name]!")
+
+/obj/item/toy/crayon/proc/territory_claimed(area/territory, mob/user)
+ for(var/obj/effect/decal/cleanable/crayon/gang/G in GLOB.gang_tags)
+ if(get_area(G) == territory)
+ return G
+
+/////////////////Gang end////////////////////
+
/obj/item/toy/crayon/red
icon_state = "crayonred"
paint_color = "#DA0000"
@@ -761,7 +757,7 @@
return
- if(isobj(target))
+ if(isobj(target) && !istype(target, /obj/effect/decal/cleanable/crayon/gang))
if(actually_paints)
if(istype(target, /obj/item/canvas)) //dont color our canvas neon green when im trying to paint please
return
@@ -866,26 +862,6 @@
post_noise = FALSE
reagent_contents = list(/datum/reagent/consumable/nothing = 1, /datum/reagent/toxin/mutetoxin = 1)
-/obj/item/toy/crayon/spraycan/gang
- charges = 20 // Charges back to 20, which is the default value for them.
- gang = TRUE
- gang_tag_delay = 15 //Its 50% faster than a regular spraycan, for tagging. After-all they did spend points/meet the boss.
-
- pre_noise = FALSE
- post_noise = TRUE // Its even more stealthy just a tad.
-
-/obj/item/toy/crayon/spraycan/gang/Initialize(loc, datum/team/gang/G)
- ..()
- if(G)
- gang = G
- paint_color = G.color
- update_icon()
-
-/obj/item/toy/crayon/spraycan/gang/examine(mob/user)
- . = ..()
- if(user.mind && user.mind.has_antag_datum(/datum/antagonist/gang) || isobserver(user))
- . += "This spraycan has been specially modified with a stage 2 nozzle kit, making it faster."
-
/obj/item/toy/crayon/spraycan/infinite
name = "infinite spraycan"
charges = -1
diff --git a/code/game/objects/items/implants/implant_mindshield.dm b/code/game/objects/items/implants/implant_mindshield.dm
index 52f9f3b9a4..395c0c4ce0 100644
--- a/code/game/objects/items/implants/implant_mindshield.dm
+++ b/code/game/objects/items/implants/implant_mindshield.dm
@@ -29,7 +29,7 @@
if(target.mind.has_antag_datum(ANTAG_DATUM_VASSAL))
SSticker.mode.remove_vassal(target.mind)
- if(target.mind.has_antag_datum(/datum/antagonist/rev/head) || target.mind.unconvertable || target.mind.has_antag_datum(/datum/antagonist/gang/boss))
+ if(target.mind.has_antag_datum(/datum/antagonist/rev/head) || (target.mind.unconvertable))
if(!silent)
target.visible_message("[target] seems to resist the implant!", "You feel something interfering with your mental conditioning, but you resist it!")
var/obj/item/implanter/I = loc
diff --git a/code/game/objects/items/storage/backpack.dm b/code/game/objects/items/storage/backpack.dm
index 8a3eedca4b..c10bcd753d 100644
--- a/code/game/objects/items/storage/backpack.dm
+++ b/code/game/objects/items/storage/backpack.dm
@@ -657,3 +657,9 @@
desc = "Worn by snails as armor and storage compartment."
icon_state = "snailshell"
item_state = "snailshell"
+
+/obj/item/storage/backpack/henchmen
+ name = "wings"
+ desc = "Granted to the henchmen who deserve it. This probably doesn't include you."
+ icon_state = "henchmen"
+ item_state = "henchmen"
diff --git a/code/game/objects/items/summon.dm b/code/game/objects/items/summon.dm
new file mode 100644
index 0000000000..93846d10ab
--- /dev/null
+++ b/code/game/objects/items/summon.dm
@@ -0,0 +1,539 @@
+/// doing nothing/orbiting idly
+#define STATE_IDLE 0
+/// performing reset animation
+#define STATE_RESET 1
+/// performing attack animation
+#define STATE_ATTACK 2
+/// performing animation between attacks
+#define STATE_RECOVER 3
+
+/**
+ * Simple summon weapon code in this file
+ *
+ * tl;dr latch onto target, repeatedly proc attacks, animate using transforms,
+ * no real hitboxes/collisions, think of /datum/component/orbit-adjacent
+ */
+/obj/item/summon
+ name = "a horrifying mistake"
+ desc = "Why does this exist?"
+ /// datum type
+ var/host_type
+ /// number of summons
+ var/summon_count = 6
+ /// how long it takes for a "stack" to fall off by itself
+ var/stack_duration = 5 SECONDS
+ /// our summon weapon host
+ var/datum/summon_weapon_host/host
+ /// range summons will chase to
+ var/range = 7
+ /// are we a ranged weapon?
+ var/melee_only = TRUE
+
+/obj/item/summon/Initialize()
+ . = ..()
+ if(host_type)
+ host = new host_type(src, summon_count, range)
+
+/obj/item/summon/afterattack(atom/target, mob/user, proximity_flag, click_parameters)
+ . = ..()
+ if(!host)
+ return
+ if(!proximity_flag && melee_only)
+ return
+ Target(target)
+
+/obj/item/summon/dropped(mob/user, silent)
+ . = ..()
+ addtimer(CALLBACK(src, .proc/check_activation), 0, TIMER_UNIQUE)
+
+/obj/item/summon/equipped(mob/user, slot)
+ . = ..()
+ addtimer(CALLBACK(src, .proc/check_activation), 0, TIMER_UNIQUE)
+
+/obj/item/summon/proc/check_activation()
+ if(!host)
+ return
+ if(!isliving(loc))
+ host.SetMaster(null)
+ var/mob/living/L = loc
+ if(!istype(L))
+ return
+ if(!L.is_holding(src))
+ host.SetMaster(src)
+ host.Suppress()
+ host.SetMaster(L)
+ host.Wake()
+
+/obj/item/summon/proc/Target(atom/victim)
+ if(!host?.CheckTarget(victim))
+ return
+ host.AutoTarget(victim, stack_duration)
+
+/obj/item/summon/sword
+ name = "spectral blade"
+ desc = "An eldritch blade that summons phantasms to attack one's enemies."
+ icon = 'icons/obj/items_and_weapons.dmi'
+ icon_state = "spectral"
+ item_state = "spectral"
+ lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi'
+ host_type = /datum/summon_weapon_host/sword
+ force = 15
+ sharpness = SHARP_EDGED
+
+/**
+ * Serves as the master datum for summon weapons
+ */
+/datum/summon_weapon_host
+ /// master atom
+ var/atom/master
+ /// suppressed?
+ var/active = TRUE
+ /// actual projectiles
+ var/list/datum/summon_weapon/controlled
+ /// active projectiles - refreshing a projectile reorders the list, so if they all have the same stack durations, you can trust the list to have last-refreshed at [1]
+ var/list/datum/summon_weapon/attacking
+ /// idle projectiles
+ var/list/datum/summon_weapon/idle
+ /// projectile type
+ var/weapon_type
+ /// default stack time
+ var/stack_time = 5 SECONDS
+ /// range
+ var/range = 7
+
+/datum/summon_weapon_host/New(atom/master, count, range)
+ src.master = master
+ src.range = range
+ controlled = list()
+ attacking = list()
+ idle = list()
+ Create(count)
+
+/datum/summon_weapon_host/Destroy()
+ QDEL_LIST(controlled)
+ master = null
+ return ..()
+
+/datum/summon_weapon_host/proc/SetMaster(atom/master, reset_on_failure = TRUE)
+ var/changed = src.master != master
+ src.master = master
+ if(changed)
+ for(var/datum/summon_weapon/weapon as anything in idle)
+ weapon.Reset()
+ if(!master && reset_on_failure)
+ for(var/datum/summon_weapon/weapon as anything in attacking)
+ weapon.Reset()
+
+/datum/summon_weapon_host/proc/Create(count)
+ if(!weapon_type)
+ return
+ for(var/i in 1 to min(count, clamp(20 - controlled.len - count, 0, 20)))
+ var/datum/summon_weapon/weapon = new weapon_type
+ Associate(weapon)
+
+/datum/summon_weapon_host/proc/Associate(datum/summon_weapon/linking)
+ if(linking.host && linking.host != src)
+ linking.host.Disassociate(linking)
+ linking.host = src
+ controlled |= linking
+ linking.Reset()
+
+/datum/summon_weapon_host/proc/Disassociate(datum/summon_weapon/unlinking, reset = TRUE, autodel)
+ if(unlinking.host == src)
+ unlinking.host = null
+ controlled -= unlinking
+ if(reset)
+ unlinking.Reset(del_no_host = autodel)
+ idle -= unlinking
+ attacking -= unlinking
+
+/datum/summon_weapon_host/proc/AutoTarget(atom/victim, duration = stack_time)
+ if(!active)
+ return
+ var/datum/summon_weapon/weapon = (idle.len && idle[1]) || (attacking.len && attacking[1])
+ if(!weapon)
+ return
+ if(!CheckTarget(victim))
+ return
+ weapon.Target(victim)
+ if(duration)
+ weapon.ResetIn(duration)
+
+/datum/summon_weapon_host/proc/OnTarget(datum/summon_weapon/weapon, atom/victim)
+ attacking -= weapon
+ idle -= weapon
+ attacking |= weapon
+
+/datum/summon_weapon_host/proc/OnReset(datum/summon_weapon/weapon, atom/victim)
+ attacking -= weapon
+ idle |= weapon
+
+/datum/summon_weapon_host/proc/CheckTarget(atom/victim)
+ if(isitem(victim))
+ return FALSE
+ if(QDELETED(victim))
+ return FALSE
+ if(victim == master)
+ return FALSE
+ if(isliving(victim))
+ var/mob/living/L = victim
+ if(L.stat == DEAD)
+ return FALSE
+ return TRUE
+ if(isobj(victim))
+ var/obj/O = victim
+ return (O.obj_flags & CAN_BE_HIT)
+ return FALSE
+
+/datum/summon_weapon_host/proc/Suppress()
+ active = FALSE
+ for(var/datum/summon_weapon/weapon as anything in controlled)
+ weapon.Reset()
+
+/datum/summon_weapon_host/proc/Wake()
+ active = TRUE
+ for(var/datum/summon_weapon/weapon as anything in controlled)
+ weapon.Reset()
+
+/datum/summon_weapon_host/sword
+ weapon_type = /datum/summon_weapon/sword
+
+/**
+ * A singular summoned object
+ *
+ * How summon weapons work:
+ *
+ * Reset() - makes it go back to its master.
+ * Target() - locks onto a target for a duration
+ *
+ * The biggest challenge is synchronizing animations.
+ * Variables keep track of when things tick, but,
+ * animations are client-timed, and not server-timed
+ *
+ * Animations:
+ * The weapon can only track its "intended" angle and dist
+ * "Current" pixel x/y are always calculated relative to a target from the current orbiting atom the physical effect is on
+ * There's 3 animations,
+ * MoveTo(location, angle, dist, rotation)
+ * Orbit(location)
+ * Rotate(degrees)
+ *
+ * And an non-animation that just snaps it to a location,
+ * HardReset(location)
+ */
+/datum/summon_weapon
+ /// name
+ var/name = "summoned weapon"
+ /// host
+ var/datum/summon_weapon_host/host
+ /// icon file
+ var/icon = 'icons/effects/summon.dmi'
+ /// icon state
+ var/icon_state
+ /// mutable_appearance to use, will skip making from icon/icon state if so
+ var/mutable_appearance/appearance
+ /// the actual effect
+ var/atom/movable/summon_weapon_effect/atom
+ /// currently locked attack target
+ var/atom/victim
+ /// current angle from victim - clockwise from 0. null if not attacking.
+ var/angle
+ /// current distance from victim - pixels
+ var/dist
+ /// current rotation - angles clockwise from north
+ var/rotation
+ /// rand dist to rotate during reattack phase
+ var/angle_vary = 45
+ /// orbit distance from victim - pixels
+ var/orbit_dist = 72
+ /// orbit distance variation from victim
+ var/orbit_dist_vary = 24
+ /// attack delay in deciseconds - this is time spent between attacks
+ var/attack_speed = 1.5
+ /// attack length in deciseconds - this is the attack animation speed in total
+ var/attack_length = 1.5
+ /// attack damage
+ var/attack_damage = 5
+ /// reset animation duration
+ var/reset_speed = 2
+ /// attack damtype
+ var/attack_type = BRUTE
+ /// attack sound
+ var/attack_sound = list(
+ 'sound/weapons/bladeslice.ogg',
+ 'sound/weapons/bladesliceb.ogg'
+ )
+ /// attack verb
+ var/attack_verb = list(
+ "rended",
+ "pierced",
+ "penetrated",
+ "sliced"
+ )
+ /// current state
+ var/state = STATE_IDLE
+ /// animation locked until
+ var/animation_lock
+ /// animation lock timer
+ var/animation_timerid
+ /// reset timerid
+ var/reset_timerid
+
+/datum/summon_weapon/New(mutable_appearance/appearance_override)
+ if(appearance_override)
+ appearance = appearance_override
+ Setup()
+ attack_verb = typelist(NAMEOF(src, attack_verb), attack_verb)
+ attack_sound = typelist(NAMEOF(src, attack_sound), attack_sound)
+
+/datum/summon_weapon/Destroy()
+ host.Disassociate(src, autodel = FALSE)
+ QDEL_NULL(atom)
+ QDEL_NULL(appearance)
+ return ..()
+
+/datum/summon_weapon/proc/Setup()
+ atom = new
+ if(!appearance)
+ GenerateAppearance()
+ atom.appearance = appearance
+ atom.moveToNullspace()
+ if(host)
+ Reset()
+
+/datum/summon_weapon/proc/GenerateAppearance()
+ if(!appearance)
+ appearance = new
+ appearance.icon = icon
+ appearance.icon_state = icon_state
+ appearance.mouse_opacity = MOUSE_OPACITY_TRANSPARENT
+ appearance.opacity = FALSE
+ appearance.plane = GAME_PLANE
+ appearance.layer = ABOVE_MOB_LAYER
+ appearance.appearance_flags = KEEP_TOGETHER
+ appearance.overlays = list(
+ emissive_appearance(icon, icon_state)
+ )
+
+/datum/summon_weapon/proc/Reset(immediate = FALSE, del_no_host = TRUE)
+ angle = null
+ victim = null
+ if(reset_timerid)
+ deltimer(reset_timerid)
+ reset_timerid = null
+ host?.OnReset(src)
+ atom.Release()
+ state = STATE_RESET
+ if(!host)
+ if(del_no_host)
+ qdel(src)
+ return
+ HardReset(null)
+ atom.moveToNullspace()
+ return
+ if(immediate)
+ if(animation_timerid)
+ deltimer(animation_timerid)
+ Act()
+ else
+ Wake()
+
+/datum/summon_weapon/proc/ResetIn(ds)
+ reset_timerid = addtimer(CALLBACK(src, .proc/Reset), ds, TIMER_STOPPABLE)
+
+/datum/summon_weapon/proc/Target(atom/victim)
+ if(!istype(victim) || !isturf(victim.loc) || (host && !host.CheckTarget(victim)))
+ Reset()
+ return
+ src.victim = victim
+ host.OnTarget(src, victim)
+ state = STATE_ATTACK
+ Wake()
+
+/datum/summon_weapon/proc/Wake()
+ if(!animation_timerid)
+ Act()
+
+/datum/summon_weapon/proc/AnimationLock(duration)
+ if(animation_timerid)
+ deltimer(animation_timerid)
+ animation_timerid = addtimer(CALLBACK(src, .proc/Act), duration, TIMER_CLIENT_TIME | TIMER_STOPPABLE)
+
+/datum/summon_weapon/proc/Act()
+ animation_timerid = null
+ switch(state)
+ if(STATE_IDLE)
+ return
+ if(STATE_ATTACK)
+ if(!isturf(victim.loc) || (host && !host.CheckTarget(victim)))
+ Reset(TRUE)
+ return
+ state = STATE_RECOVER
+ // register hit at the halfway mark
+ // we can do better math to approximate when the attack will hit but i'm too tired to bother
+ addtimer(CALLBACK(src, .proc/Hit, victim), attack_length / 2, TIMER_CLIENT_TIME)
+ // we need to approximate our incoming angle - again, better math exists but why bother
+ var/incoming_angle = angle
+ if(isturf(atom.loc) && (atom.loc != victim.loc))
+ incoming_angle = Get_Angle(atom.loc, victim.loc)
+ // pierce through target
+ // we do not want to turn while doing this so we pierce through them visually
+ incoming_angle += 180
+ var/outgoing_angle = SIMPLIFY_DEGREES(incoming_angle)
+ AnimationLock(MoveTo(victim, null, outgoing_angle, orbit_dist + rand(-orbit_dist_vary, orbit_dist_vary), outgoing_angle, attack_length))
+ if(STATE_RESET)
+ state = STATE_IDLE
+ if(!host || !host.active || !get_turf(host.master))
+ atom.moveToNullspace()
+ src.angle = null
+ src.dist = null
+ src.rotation = null
+ return
+ var/reset_angle = rand(0, 360)
+ AnimationLock(MoveTo(host.master, null, reset_angle, 30, 90, reset_speed))
+ addtimer(CALLBACK(src, .proc/Orbit, host.master, reset_angle, 30, 3 SECONDS), reset_speed, TIMER_CLIENT_TIME)
+ if(STATE_RECOVER)
+ state = STATE_ATTACK
+ AnimationLock(Rotate(rand(-angle_vary, angle_vary), attack_speed, null))
+
+/datum/summon_weapon/proc/Hit(atom/victim)
+ if(!isobj(victim) && !isliving(victim))
+ return FALSE
+ if(isliving(victim))
+ var/mob/living/L = victim
+ L.apply_damage(attack_damage, attack_type)
+ playsound(victim, pick(attack_sound), 75)
+ else if(isobj(victim))
+ var/obj/O = victim
+ O.take_damage(attack_damage, attack_type)
+ return TRUE
+
+/**
+ * relative to defaults to current location
+ */
+/datum/summon_weapon/proc/MoveTo(atom/destination, atom/relative_to, angle = 0, dist = 64, rotation = 180, time)
+ . = time
+ // construct final transform
+ var/matrix/dest = ConstructMatrix(angle, dist, rotation)
+
+ // move to
+ atom.Lock(destination)
+
+ // get relative first positions
+ relative_to = get_turf(relative_to || atom.locked)
+ destination = get_turf(destination)
+ // if none, move to immediately and end
+ if(!relative_to)
+ atom.transform = dest
+ src.angle = angle
+ src.dist = dist
+ src.rotation = rotation
+ // end animations
+ animate(atom, time = 0, flags = ANIMATION_END_NOW)
+ return 0
+
+ // grab source
+ var/rel_x = (destination.x - relative_to.x) * world.icon_size + src.dist * sin(src.angle)
+ var/rel_y = (destination.y - relative_to.y) * world.icon_size + src.dist * cos(src.angle)
+
+ // construct source matrix
+ var/matrix/source = new
+
+ source.Turn((relative_to == get_turf(atom.locked))? src.rotation : Get_Angle(relative_to, destination))
+ source.Translate(rel_x, rel_y)
+
+ // set vars
+ src.angle = angle
+ src.dist = dist
+ src.rotation = rotation
+
+ // animate
+ atom.transform = source
+ animate(atom, transform = dest, time, FALSE, LINEAR_EASING, ANIMATION_LINEAR_TRANSFORM | ANIMATION_END_NOW)
+
+/**
+ * rotation defaults to facing towards locked atom
+ */
+/datum/summon_weapon/proc/Rotate(degrees, time, rotation)
+ . = time
+ if(!dist)
+ return 0
+ var/matrix/M = ConstructMatrix(angle + degrees, dist, rotation || src.rotation)
+ if(rotation)
+ src.rotation = rotation
+ angle += degrees
+ animate(atom, transform = M, time, FALSE, LINEAR_EASING, ANIMATION_END_NOW | ANIMATION_LINEAR_TRANSFORM)
+
+/datum/summon_weapon/proc/Orbit(atom/destination, initial_degrees = rand(0, 360), dist, speed)
+ . = 0
+ atom.Lock(destination)
+ animate(atom, 0, FALSE, flags = ANIMATION_END_NOW)
+ atom.transform = ConstructMatrix(initial_degrees, dist, 90)
+ atom.SpinAnimation(speed, parallel = FALSE, segments = 10)
+ // we can't predict dist/angle anymre because clienttime vs servertime.
+ // well, we can, but, let's not be bothered with timeofday math eh.
+ dist = 0
+ angle = 0
+
+/datum/summon_weapon/proc/ConstructMatrix(angle = 0, dist = 64, rotation = 0)
+ var/matrix/M = new
+ M.Turn(rotation)
+ M.Translate(0, dist)
+ M.Turn(angle)
+ return M
+
+/datum/summon_weapon/proc/HardReset(atom/snap_to)
+ if(animation_timerid)
+ deltimer(animation_timerid)
+ atom.Release()
+ atom.forceMove(snap_to)
+ atom.transform = null
+
+/datum/summon_weapon/sword
+ name = "spectral blade"
+ icon_state = "sword"
+ attack_damage = 5
+ attack_speed = 1.5
+ attack_length = 1.5
+
+/atom/movable/summon_weapon_effect
+ mouse_opacity = MOUSE_OPACITY_TRANSPARENT
+ plane = GAME_PLANE
+ layer = ABOVE_MOB_LAYER
+ opacity = FALSE
+ density = FALSE
+ /// locked atom
+ var/atom/locked
+
+/atom/movable/summon_weapon_effect/Destroy()
+ Release()
+ return ..()
+
+/atom/movable/summon_weapon_effect/proc/Lock(atom/target)
+ if(locked == target)
+ return
+ if(locked)
+ Release()
+ if(!target)
+ return
+ locked = target
+ forceMove(locked.loc)
+ if(ismovable(locked))
+ RegisterSignal(locked, COMSIG_MOVABLE_MOVED, .proc/Update)
+
+/atom/movable/summon_weapon_effect/proc/Release()
+ if(ismovable(locked))
+ UnregisterSignal(locked, COMSIG_MOVABLE_MOVED)
+ locked = null
+
+/atom/movable/summon_weapon_effect/proc/Update()
+ if(!locked)
+ return
+ if(loc != locked.loc)
+ forceMove(locked.loc)
+
+#undef STATE_IDLE
+#undef STATE_ATTACK
+#undef STATE_RECOVER
+#undef STATE_RESET
diff --git a/code/game/objects/items/weaponry.dm b/code/game/objects/items/weaponry.dm
index 902b1eaa13..3fd31941f3 100644
--- a/code/game/objects/items/weaponry.dm
+++ b/code/game/objects/items/weaponry.dm
@@ -1105,7 +1105,7 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
icon_state = "madeyoulook"
force = 0
throwforce = 0
- item_flags = DROPDEL | ABSTRACT // | HAND_ITEM
+ item_flags = DROPDEL | ABSTRACT | HAND_ITEM
attack_verb = list("bopped")
/obj/item/circlegame/Initialize()
@@ -1207,7 +1207,7 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
item_state = "nothing"
force = 0
throwforce = 0
- item_flags = DROPDEL | ABSTRACT // | HAND_ITEM
+ item_flags = DROPDEL | ABSTRACT | HAND_ITEM
attack_verb = list("slapped")
hitsound = 'sound/effects/snap.ogg'
@@ -1227,6 +1227,120 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
return FALSE
return TRUE
+/obj/item/slapper/on_offered(mob/living/carbon/offerer)
+ . = TRUE
+
+ if(!(locate(/mob/living/carbon) in orange(1, offerer)))
+ visible_message(span_danger("[offerer] raises [offerer.p_their()] arm, looking around for a high-five, but there's no one around!"), \
+ span_warning("You post up, looking for a high-five, but finding no one within range!"), null, 2)
+ return
+
+ offerer.visible_message(span_notice("[offerer] raises [offerer.p_their()] arm, looking for a high-five!"), \
+ span_notice("You post up, looking for a high-five!"), null, 2)
+ offerer.apply_status_effect(STATUS_EFFECT_OFFERING, src, /atom/movable/screen/alert/give/highfive)
+
+/// Yeah broh! This is where we do the high-fiving (or high-tenning :o)
+/obj/item/slapper/on_offer_taken(mob/living/carbon/offerer, mob/living/carbon/taker)
+ . = TRUE
+
+ var/open_hands_taker
+ var/slappers_giver
+ for(var/i in taker.held_items) // see how many hands the taker has open for high'ing
+ if(isnull(i))
+ open_hands_taker++
+
+ if(!open_hands_taker)
+ to_chat(taker, span_warning("You can't high-five [offerer] with no open hands!"))
+ SEND_SIGNAL(taker, COMSIG_ADD_MOOD_EVENT, "high_five", /datum/mood_event/high_five_full_hand) // not so successful now!
+ return
+
+ for(var/i in offerer.held_items)
+ var/obj/item/slapper/slap_check = i
+ if(istype(slap_check))
+ slappers_giver++
+
+ if(slappers_giver >= 2) // we only check this if it's already established the taker has 2+ hands free
+ offerer.visible_message(span_notice("[taker] enthusiastically high-tens [offerer]!"), span_nicegreen("Wow! You're high-tenned [taker]!"), span_hear("You hear a sickening sound of flesh hitting flesh!"), ignored_mobs=taker)
+ to_chat(taker, span_nicegreen("You give high-tenning [offerer] your all!"))
+ playsound(offerer, 'sound/weapons/slap.ogg', 100, TRUE, 1)
+ SEND_SIGNAL(offerer, COMSIG_ADD_MOOD_EVENT, "high_five", /datum/mood_event/high_ten)
+ SEND_SIGNAL(taker, COMSIG_ADD_MOOD_EVENT, "high_five", /datum/mood_event/high_ten)
+ else
+ offerer.visible_message(span_notice("[taker] high-fives [offerer]!"), span_nicegreen("All right! You're high-fived by [taker]!"), span_hear("You hear a sickening sound of flesh hitting flesh!"), ignored_mobs=taker)
+ to_chat(taker, span_nicegreen("You high-five [offerer]!"))
+ playsound(offerer, 'sound/weapons/slap.ogg', 50, TRUE, -1)
+ SEND_SIGNAL(offerer, COMSIG_ADD_MOOD_EVENT, "high_five", /datum/mood_event/high_five)
+ SEND_SIGNAL(taker, COMSIG_ADD_MOOD_EVENT, "high_five", /datum/mood_event/high_five)
+ qdel(src)
+
+/// Gangster secret handshakes.
+/obj/item/slapper/secret_handshake
+ name = "Secret Handshake"
+ icon_state = "recruit"
+ icon = 'icons/obj/gang/actions.dmi'
+ /// References the active families gamemode handler (if one exists), for adding new family members to.
+ var/datum/gang_handler/handler
+ /// The typepath of the gang antagonist datum that the person who uses the package should have added to them -- remember that the distinction between e.g. Ballas and Grove Street is on the antag datum level, not the team datum level.
+ var/gang_to_use
+ /// The team datum that the person who uses this package should be added to.
+ var/datum/team/gang/team_to_use
+
+
+/// Adds the user to the family that this package corresponds to, dispenses the free_clothes of that family, and adds them to the handler if it exists.
+/obj/item/slapper/secret_handshake/proc/add_to_gang(mob/living/user, original_name)
+ var/datum/antagonist/gang/swappin_sides = new gang_to_use()
+ swappin_sides.original_name = original_name
+ swappin_sides.handler = handler
+ user.mind.add_antag_datum(swappin_sides, team_to_use)
+ var/policy = get_policy(ROLE_FAMILIES)
+ if(policy)
+ to_chat(user, policy)
+ team_to_use.add_member(user.mind)
+ swappin_sides.equip_gangster_in_inventory()
+ if (!isnull(handler) && !handler.gangbangers.Find(user.mind)) // if we have a handler and they're not tracked by it
+ handler.gangbangers += user.mind
+
+/// Checks if the user is trying to use the package of the family they are in, and if not, adds them to the family, with some differing processing depending on whether the user is already a family member.
+/obj/item/slapper/secret_handshake/proc/attempt_join_gang(mob/living/user)
+ if(!user?.mind)
+ return
+ var/datum/antagonist/gang/is_gangster = user.mind.has_antag_datum(/datum/antagonist/gang)
+ var/real_name_backup = user.real_name
+ if(is_gangster)
+ if(is_gangster.my_gang == team_to_use)
+ return
+ real_name_backup = is_gangster.original_name
+ is_gangster.my_gang.remove_member(user.mind)
+ user.mind.remove_antag_datum(/datum/antagonist/gang)
+ add_to_gang(user, real_name_backup)
+
+/obj/item/slapper/secret_handshake/on_offer_taken(mob/living/carbon/offerer, mob/living/carbon/taker)
+ . = TRUE
+ if (!(null in taker.held_items))
+ to_chat(taker, span_warning("You can't get taught the secret handshake if [offerer] has no free hands!"))
+ return
+
+ if(HAS_TRAIT(taker, TRAIT_MINDSHIELD))
+ to_chat(taker, "You attended a seminar on not signing up for a gang and are not interested.")
+ return
+
+ var/datum/antagonist/gang/is_gangster = taker.mind.has_antag_datum(/datum/antagonist/gang)
+ if(is_gangster?.starter_gangster)
+ if(is_gangster.my_gang == team_to_use)
+ to_chat(taker, "You started your family. You don't need to join it.")
+ return
+ to_chat(taker, "You started your family. You can't turn your back on it now.")
+ return
+
+ offerer.visible_message(span_notice("[taker] is taught the secret handshake by [offerer]!"), span_nicegreen("All right! You've taught the secret handshake to [taker]!"), span_hear("You hear a bunch of weird shuffling and flesh slapping sounds!"), ignored_mobs=taker)
+ to_chat(taker, span_nicegreen("You get taught the secret handshake by [offerer]!"))
+ var/datum/antagonist/gang/owner_gang_datum = offerer.mind.has_antag_datum(/datum/antagonist/gang)
+ handler = owner_gang_datum.handler
+ gang_to_use = owner_gang_datum.type
+ team_to_use = owner_gang_datum.my_gang
+ attempt_join_gang(taker)
+ qdel(src)
+
/obj/item/extendohand
name = "extendo-hand"
desc = "Futuristic tech has allowed these classic spring-boxing toys to essentially act as a fully functional hand-operated hand prosthetic."
@@ -1261,7 +1375,7 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
// attack_verb_simple = list("whack", "thwack", "wallop", "sock")
// icon = 'icons/obj/items_and_weapons.dmi'
// icon_state = "gohei"
-// inhand_icon_state = "gohei"
+// item_state = "gohei"
// lefthand_file = 'icons/mob/inhands/weapons/staves_lefthand.dmi'
// righthand_file = 'icons/mob/inhands/weapons/staves_righthand.dmi'
diff --git a/code/game/objects/structures/crates_lockers/closets.dm b/code/game/objects/structures/crates_lockers/closets.dm
index f01342a903..af4d098157 100644
--- a/code/game/objects/structures/crates_lockers/closets.dm
+++ b/code/game/objects/structures/crates_lockers/closets.dm
@@ -531,7 +531,7 @@
/obj/structure/closet/get_remote_view_fullscreens(mob/user)
if(user.stat == DEAD || !(user.sight & (SEEOBJS|SEEMOBS)))
- user.overlay_fullscreen("remote_view", /atom/movable/screen/fullscreen/impaired, 1)
+ user.overlay_fullscreen("remote_view", /atom/movable/screen/fullscreen/scaled/impaired, 1)
/obj/structure/closet/emp_act(severity)
. = ..()
diff --git a/code/game/objects/structures/morgue.dm b/code/game/objects/structures/morgue.dm
index 6a588a61cd..73268009ce 100644
--- a/code/game/objects/structures/morgue.dm
+++ b/code/game/objects/structures/morgue.dm
@@ -139,7 +139,7 @@ GLOBAL_LIST_EMPTY(bodycontainers) //Let them act as spawnpoints for revenants an
/obj/structure/bodycontainer/get_remote_view_fullscreens(mob/user)
if(user.stat == DEAD || !(user.sight & (SEEOBJS|SEEMOBS)))
- user.overlay_fullscreen("remote_view", /atom/movable/screen/fullscreen/impaired, 2)
+ user.overlay_fullscreen("remote_view", /atom/movable/screen/fullscreen/scaled/impaired, 2)
/*
* Morgue
*/
diff --git a/code/modules/admin/callproc/callproc.dm b/code/modules/admin/callproc/callproc.dm
index 21bf732493..a9c0984bbc 100644
--- a/code/modules/admin/callproc/callproc.dm
+++ b/code/modules/admin/callproc/callproc.dm
@@ -115,9 +115,9 @@ GLOBAL_PROTECT(LastAdminCalledProc)
//adv proc call this, ya nerds
/world/proc/WrapAdminProcCall(datum/target, procname, list/arguments)
if(target == GLOBAL_PROC)
- return call("/proc/[procname]")(arglist(arguments))
- else if(target != world)
- return call(target, procname)(arglist(arguments))
+ return text2path("/proc/[procname]")? call("/proc/[procname]")(arglist(arguments)) : null
+ else if(target != world && istype(target, /datum)) // isdatum check incase someone manages to call WrapAdminProcCall(global) which would otherwise crash the process entirely
+ return hascall(target, procname)? call(target, procname)(arglist(arguments)) : null
else
log_admin("[key_name(usr)] attempted to call world/proc/[procname] with arguments: [english_list(arguments)]")
diff --git a/code/modules/admin/topic.dm b/code/modules/admin/topic.dm
index b2cc84633f..17bad60945 100644
--- a/code/modules/admin/topic.dm
+++ b/code/modules/admin/topic.dm
@@ -911,10 +911,10 @@
else
dat += "Alien | "
//Gang
- if(jobban_isbanned(M, ROLE_GANG) || isbanned_dept)
- dat += "Gang | "
+ if(jobban_isbanned(M, ROLE_FAMILIES) || isbanned_dept)
+ dat += "Families | "
else
- dat += "Gang | "
+ dat += "Families | "
//Bloodsucker
if(jobban_isbanned(M, ROLE_BLOODSUCKER) || isbanned_dept)
dat += "Bloodsucker | "
@@ -999,7 +999,7 @@
if("ghostroles")
joblist += list(ROLE_PAI, ROLE_POSIBRAIN, ROLE_DRONE , ROLE_DEATHSQUAD, ROLE_LAVALAND, ROLE_SENTIENCE)
if("teamantags")
- joblist += list(ROLE_OPERATIVE, ROLE_REV, ROLE_CULTIST, ROLE_SERVANT_OF_RATVAR, ROLE_ABDUCTOR, ROLE_ALIEN, ROLE_GANG)
+ joblist += list(ROLE_OPERATIVE, ROLE_REV, ROLE_CULTIST, ROLE_SERVANT_OF_RATVAR, ROLE_ABDUCTOR, ROLE_ALIEN, ROLE_FAMILIES)
if("convertantags")
joblist += list(ROLE_REV, ROLE_CULTIST, ROLE_SERVANT_OF_RATVAR, ROLE_ALIEN)
if("otherroles")
diff --git a/code/modules/admin/verbs/SDQL2/SDQL_2_wrappers.dm b/code/modules/admin/verbs/SDQL2/SDQL_2_wrappers.dm
index ca07f9a50b..79fd63adcc 100644
--- a/code/modules/admin/verbs/SDQL2/SDQL_2_wrappers.dm
+++ b/code/modules/admin/verbs/SDQL2/SDQL_2_wrappers.dm
@@ -3,11 +3,19 @@
/proc/_abs(A)
return abs(A)
-/proc/_animate(atom/A, set_vars, time = 10, loop = 1, easing = LINEAR_EASING, flags = null)
- var/mutable_appearance/MA = new()
- for(var/v in set_vars)
- MA.vars[v] = set_vars[v]
- animate(A, appearance = MA, time, loop, easing, flags)
+/proc/_animate(atom/A, list/data, time = 10, loop = 1, easing = LINEAR_EASING, flags = null)
+ if(!istype(A))
+ return
+ animate(A, appearance = data, time = time, loop = loop, easing = easing, flags = flags)
+
+/proc/_animate_adv(atom/A, list/data, loop = 1, easing = LINEAR_EASING, flags = NONE)
+ if(!A || !islist(data) || data.len < 1)
+ return
+ animate(A, appearance = (data[1] - "time"), time = data[1]["time"], loop = loop, easing = easing, flags = flags)
+ if(data.len < 2)
+ return
+ for(var/i in 2 to data.len)
+ animate(appearance = (data[i] - "time"), time = data[i]["time"])
/proc/_acrccos(A)
return arccos(A)
diff --git a/code/modules/antagonists/_common/antag_datum.dm b/code/modules/antagonists/_common/antag_datum.dm
index 498951bfd2..2f7e160e6e 100644
--- a/code/modules/antagonists/_common/antag_datum.dm
+++ b/code/modules/antagonists/_common/antag_datum.dm
@@ -34,10 +34,13 @@ GLOBAL_LIST_EMPTY(antagonists)
var/antag_hud_name
/// If above 0, this is the multiplier for the speed at which we hijack the shuttle. Do not directly read, use hijack_speed().
var/hijack_speed = 0
- /// If set to true, the antag will not be added to the living antag list.
- var/soft_antag = FALSE
/// The battlecry this antagonist shouts when suiciding with C4/X4.
var/suicide_cry = ""
+ /// The typepath for the outfit to show in the preview for the preferences menu.
+ var/preview_outfit
+ /// If set to true, the antag will not be added to the living antag list.
+ var/soft_antag = FALSE
+
//Antag panel properties
///This will hide adding this antag type in antag panel, use only for internal subtypes that shouldn't be added directly but still show if possessed by mind
var/show_in_antagpanel = TRUE
@@ -376,6 +379,43 @@ GLOBAL_LIST_EMPTY(antagonists)
/datum/antagonist/proc/get_admin_commands()
. = list()
+/// Creates an icon from the preview outfit.
+/// Custom implementors of `get_preview_icon` should use this, as the
+/// result of `get_preview_icon` is expected to be the completed version.
+/datum/antagonist/proc/render_preview_outfit(datum/outfit/outfit, mob/living/carbon/human/dummy)
+ dummy = dummy || new /mob/living/carbon/human/dummy/consistent
+ dummy.equipOutfit(outfit, visualsOnly = TRUE)
+ COMPILE_OVERLAYS(dummy)
+ var/icon = getFlatIcon(dummy)
+
+ // We don't want to qdel the dummy right away, since its items haven't initialized yet.
+ SSatoms.prepare_deletion(dummy)
+
+ return icon
+
+/// Given an icon, will crop it to be consistent of those in the preferences menu.
+/// Not necessary, and in fact will look bad if it's anything other than a human.
+/datum/antagonist/proc/finish_preview_icon(icon/icon)
+ // Zoom in on the top of the head and the chest
+ // I have no idea how to do this dynamically.
+ icon.Scale(115, 115)
+
+ // This is probably better as a Crop, but I cannot figure it out.
+ icon.Shift(WEST, 8)
+ icon.Shift(SOUTH, 30)
+
+ icon.Crop(1, 1, ANTAGONIST_PREVIEW_ICON_SIZE, ANTAGONIST_PREVIEW_ICON_SIZE)
+
+ return icon
+
+/// Returns the icon to show on the preferences menu.
+/datum/antagonist/proc/get_preview_icon()
+ if (isnull(preview_outfit))
+ return null
+
+ return finish_preview_icon(render_preview_outfit(preview_outfit))
+
+
/datum/antagonist/Topic(href,href_list)
if(!check_rights(R_ADMIN))
return
diff --git a/code/modules/antagonists/_common/antag_helpers.dm b/code/modules/antagonists/_common/antag_helpers.dm
index b10df46348..91f0177d9c 100644
--- a/code/modules/antagonists/_common/antag_helpers.dm
+++ b/code/modules/antagonists/_common/antag_helpers.dm
@@ -6,14 +6,3 @@
continue
if(!antag_type || !specific && istype(A,antag_type) || specific && A.type == antag_type)
. += A.owner
-
-//Get all teams [of type team_type]
-/proc/get_all_teams(team_type)
- . = list()
- for(var/V in GLOB.antagonists)
- var/datum/antagonist/A = V
- if(!A.owner)
- continue
- var/datum/team/T = A.get_team()
- if(!team_type || istype(T,team_type))
- . |= T
diff --git a/code/modules/antagonists/_common/antag_team.dm b/code/modules/antagonists/_common/antag_team.dm
index 9d138ed0b9..f4e0d321d5 100644
--- a/code/modules/antagonists/_common/antag_team.dm
+++ b/code/modules/antagonists/_common/antag_team.dm
@@ -1,3 +1,5 @@
+GLOBAL_LIST_EMPTY(antagonist_teams)
+
//A barebones antagonist team.
/datum/team
var/list/datum/mind/members = list()
@@ -8,6 +10,7 @@
/datum/team/New(starting_members)
. = ..()
+ GLOB.antagonist_teams += src
if(starting_members)
if(islist(starting_members))
for(var/datum/mind/M in starting_members)
@@ -15,6 +18,10 @@
else
add_member(starting_members)
+/datum/team/Destroy(force, ...)
+ GLOB.antagonist_teams -= src
+ . = ..()
+
/datum/team/proc/is_solo()
return members.len == 1
diff --git a/code/modules/antagonists/abductor/abductor.dm b/code/modules/antagonists/abductor/abductor.dm
index 7eb7ec2af2..f7e66ee90a 100644
--- a/code/modules/antagonists/abductor/abductor.dm
+++ b/code/modules/antagonists/abductor/abductor.dm
@@ -97,7 +97,7 @@
/datum/antagonist/abductor/admin_add(datum/mind/new_owner,mob/admin)
var/list/current_teams = list()
- for(var/datum/team/abductor_team/T in get_all_teams(/datum/team/abductor_team))
+ for(var/datum/team/abductor_team/T in GLOB.antagonist_teams)
current_teams[T.name] = T
var/choice = input(admin,"Add to which team ?") as null|anything in (current_teams + "new team")
if (choice == "new team")
diff --git a/code/modules/antagonists/clockcult/clock_effects/city_of_cogs_rift.dm b/code/modules/antagonists/clockcult/clock_effects/city_of_cogs_rift.dm
index 3609037f58..562d6a0c94 100644
--- a/code/modules/antagonists/clockcult/clock_effects/city_of_cogs_rift.dm
+++ b/code/modules/antagonists/clockcult/clock_effects/city_of_cogs_rift.dm
@@ -69,7 +69,7 @@
do_sparks(5, TRUE, AM)
if(isliving(AM))
var/mob/living/L = AM
- L.overlay_fullscreen("flash", /atom/movable/screen/fullscreen/flash/static)
+ L.overlay_fullscreen("flash", /atom/movable/screen/fullscreen/tiled/flash/static)
L.clear_fullscreen("flash", 5)
var/obj/item/transfer_valve/TTV = locate() in L.GetAllContents()
if(TTV)
diff --git a/code/modules/antagonists/clockcult/clock_structures/ark_of_the_clockwork_justicar.dm b/code/modules/antagonists/clockcult/clock_structures/ark_of_the_clockwork_justicar.dm
index 2391658613..44c57b77ad 100644
--- a/code/modules/antagonists/clockcult/clock_structures/ark_of_the_clockwork_justicar.dm
+++ b/code/modules/antagonists/clockcult/clock_structures/ark_of_the_clockwork_justicar.dm
@@ -141,7 +141,7 @@
if(isliving(M.current) && M.current.stat != DEAD)
var/turf/t_turf = isAI(M.current) ? get_step(get_step(src, NORTH),NORTH) : get_turf(src) // AI too fat, must make sure it always ends up a 2 tiles north instead of on the ark.
do_teleport(M.current, t_turf, channel = TELEPORT_CHANNEL_CULT, forced = TRUE)
- M.current.overlay_fullscreen("flash", /atom/movable/screen/fullscreen/flash)
+ M.current.overlay_fullscreen("flash", /atom/movable/screen/fullscreen/tiled/flash)
M.current.clear_fullscreen("flash", 5)
playsound(src, 'sound/magic/clockwork/invoke_general.ogg', 50, FALSE)
recalls_remaining--
@@ -311,7 +311,7 @@
var/turf/T = get_turf(M)
if(is_servant_of_ratvar(M) && (!T || T.z != z))
M.forceMove(get_step(src, SOUTH))
- M.overlay_fullscreen("flash", /atom/movable/screen/fullscreen/flash)
+ M.overlay_fullscreen("flash", /atom/movable/screen/fullscreen/tiled/flash)
M.clear_fullscreen("flash", 5)
progress_in_seconds += GATEWAY_SUMMON_RATE
switch(progress_in_seconds)
diff --git a/code/modules/antagonists/devil/true_devil/_true_devil.dm b/code/modules/antagonists/devil/true_devil/_true_devil.dm
index fd7e6fb98d..619129f679 100644
--- a/code/modules/antagonists/devil/true_devil/_true_devil.dm
+++ b/code/modules/antagonists/devil/true_devil/_true_devil.dm
@@ -104,7 +104,7 @@
/mob/living/carbon/true_devil/assess_threat(judgement_criteria, lasercolor = "", datum/callback/weaponcheck=null)
return 666
-/mob/living/carbon/true_devil/flash_act(intensity = 1, override_blindness_check = 0, affect_silicon = 0, visual = 0, type = /atom/movable/screen/fullscreen/flash, override_protection = 0)
+/mob/living/carbon/true_devil/flash_act(intensity = 1, override_blindness_check = 0, affect_silicon = 0, visual = 0, type = /atom/movable/screen/fullscreen/tiled/flash, override_protection = 0)
if(mind && has_bane(BANE_LIGHT))
mind.disrupt_spells(-500)
return ..() //flashes don't stop devils UNLESS it's their bane.
diff --git a/code/modules/antagonists/ert/ert.dm b/code/modules/antagonists/ert/ert.dm
index 295616d052..43a7e561c0 100644
--- a/code/modules/antagonists/ert/ert.dm
+++ b/code/modules/antagonists/ert/ert.dm
@@ -11,14 +11,21 @@
var/role = "Security Officer"
var/list/name_source
threat = -5
+ var/random_names = TRUE
+ var/rip_and_tear = FALSE
+ var/equip_ert = TRUE
+ var/forge_objectives_for_ert = TRUE
show_in_antagpanel = FALSE
show_to_ghosts = TRUE
antag_moodlet = /datum/mood_event/focused
/datum/antagonist/ert/on_gain()
- update_name()
- forge_objectives()
- equipERT()
+ if(random_names)
+ update_name()
+ if(forge_objectives_for_ert)
+ forge_objectives()
+ if(equip_ert)
+ equipERT()
. = ..()
/datum/antagonist/ert/get_team()
@@ -129,6 +136,7 @@
return
H.equipOutfit(outfit)
+
/datum/antagonist/ert/greet()
if(!ert_team)
return
@@ -160,3 +168,139 @@
missiondesc += "
Your Mission : [ert_team.mission.explanation_text]"
to_chat(owner,missiondesc)
+
+
+/datum/antagonist/ert/families
+ name = "Space Police Responder"
+ antag_hud_type = ANTAG_HUD_SPACECOP
+ antag_hud_name = "hud_spacecop"
+ suicide_cry = "FOR THE SPACE POLICE!!"
+
+/datum/antagonist/ert/families/apply_innate_effects(mob/living/mob_override)
+ ..()
+ var/mob/living/M = mob_override || owner.current
+ add_antag_hud(antag_hud_type, antag_hud_name, M)
+ if(M.hud_used)
+ var/datum/hud/H = M.hud_used
+ var/atom/movable/screen/wanted/giving_wanted_lvl = new /atom/movable/screen/wanted()
+ H.wanted_lvl = giving_wanted_lvl
+ giving_wanted_lvl.hud = H
+ H.infodisplay += giving_wanted_lvl
+ H.mymob.client.screen += giving_wanted_lvl
+
+
+/datum/antagonist/ert/families/remove_innate_effects(mob/living/mob_override)
+ var/mob/living/M = mob_override || owner.current
+ remove_antag_hud(antag_hud_type, M)
+ if(M.hud_used)
+ var/datum/hud/H = M.hud_used
+ H.infodisplay -= H.wanted_lvl
+ QDEL_NULL(H.wanted_lvl)
+ ..()
+
+/datum/antagonist/ert/families/greet()
+ var/missiondesc = "You are the [name]."
+ missiondesc += "
You are NOT a Nanotrasen Employee. You work for the local government."
+ missiondesc += "
You are NOT a deathsquad. You are here to help innocents escape violence, criminal activity, and other dangerous things."
+ missiondesc += "
After an uptick in gang violence on [station_name()], you are responding to emergency calls from the station for immediate SSC Police assistance!\n"
+ missiondesc += "
Your Mission:"
+ missiondesc += "
1. Serve the public trust."
+ missiondesc += "
2. Protect the innocent."
+ missiondesc += "
3. Uphold the law."
+ missiondesc += "
4. Find the Undercover Cops."
+ missiondesc += "
5. Detain Nanotrasen Security personnel if they harm any citizen."
+ missiondesc += "
You can see gangsters using your special sunglasses."
+ to_chat(owner,missiondesc)
+ var/policy = get_policy(ROLE_FAMILIES)
+ if(policy)
+ to_chat(owner, policy)
+ var/mob/living/M = owner.current
+ M.playsound_local(M, 'sound/effects/families_police.ogg', 100, FALSE, pressure_affected = FALSE)
+
+/datum/antagonist/ert/families/undercover_cop
+ name = "Undercover Cop"
+ role = "Undercover Cop"
+ outfit = /datum/outfit/families_police/beatcop
+ var/free_clothes = list(/obj/item/clothing/glasses/hud/spacecop/hidden,
+ /obj/item/clothing/under/rank/security/officer/beatcop,
+ /obj/item/clothing/head/spacepolice)
+ forge_objectives_for_ert = FALSE
+ equip_ert = FALSE
+ random_names = FALSE
+
+/datum/antagonist/ert/families/undercover_cop/on_gain()
+ if(istype(owner.current, /mob/living/carbon/human))
+ for(var/C in free_clothes)
+ var/obj/O = new C(owner.current)
+ var/list/slots = list (
+ "backpack" = ITEM_SLOT_BACKPACK,
+ "left pocket" = SLOT_L_STORE,
+ "right pocket" = SLOT_R_STORE
+ )
+ var/mob/living/carbon/human/H = owner.current
+ var/equipped = H.equip_in_one_of_slots(O, slots)
+ if(!equipped)
+ to_chat(owner.current, "Unfortunately, you could not bring your [O] to this shift. You will need to find one.")
+ qdel(O)
+ . = ..()
+
+
+/datum/antagonist/ert/families/undercover_cop/greet()
+ var/missiondesc = "You are the [name]."
+ missiondesc += "
You are NOT a Nanotrasen Employee. You work for the local government."
+ missiondesc += "
You are an undercover police officer on board [station_name()]. You've been sent here by the Spinward Stellar Coalition because of suspected abusive behavior by the security department, and to keep tabs on a potential criminal organization operation."
+ missiondesc += "
Your Mission:"
+ missiondesc += "
1. Keep a close eye on any gangsters you spot. You can view gangsters using your sunglasses in your backpack."
+ missiondesc += "
2. Keep an eye on how Security handles any gangsters, and watch for excessive security brutality."
+ missiondesc += "
3. Remain undercover and do not get found out by Security or any gangs. Nanotrasen does not take kindly to being spied on."
+ missiondesc += "
4. When your backup arrives to extract you in 1 hour, inform them of everything you saw of note, and assist them in securing the situation."
+ to_chat(owner,missiondesc)
+
+/datum/antagonist/ert/families/beatcop
+ name = "Beat Cop"
+ role = "Police Officer"
+ outfit = /datum/outfit/families_police/beatcop
+
+/datum/antagonist/ert/families/beatcop/armored
+ name = "Armored Beat Cop"
+ role = "Police Officer"
+ outfit = /datum/outfit/families_police/beatcop/armored
+
+/datum/antagonist/ert/families/beatcop/swat
+ name = "S.W.A.T. Member"
+ role = "S.W.A.T. Officer"
+ outfit = /datum/outfit/families_police/beatcop/swat
+
+/datum/antagonist/ert/families/beatcop/fbi
+ name = "FBI Agent"
+ role = "FBI Agent"
+ outfit = /datum/outfit/families_police/beatcop/fbi
+
+/datum/antagonist/ert/families/beatcop/military
+ name = "Space Military"
+ role = "Sergeant"
+ outfit = /datum/outfit/families_police/beatcop/military
+
+/datum/antagonist/ert/families/beatcop/military/New()
+ . = ..()
+ name_source = GLOB.commando_names
+
+/datum/antagonist/ert/marine
+ name = "Marine Commander"
+ outfit = /datum/outfit/ert/commander/alert
+ role = "Commander"
+
+/datum/antagonist/ert/marine/security
+ name = "Marine Heavy"
+ outfit = /datum/outfit/ert/security/alert
+ role = "Trooper"
+
+/datum/antagonist/ert/marine/engineer
+ name = "Marine Engineer"
+ outfit = /datum/outfit/ert/engineer/alert
+ role = "Engineer"
+
+/datum/antagonist/ert/marine/medic
+ name = "Marine Medic"
+ outfit = /datum/outfit/ert/medic/alert
+ role = "Medical Officer"
diff --git a/code/modules/antagonists/gang/cellphone.dm b/code/modules/antagonists/gang/cellphone.dm
new file mode 100644
index 0000000000..f7971300f9
--- /dev/null
+++ b/code/modules/antagonists/gang/cellphone.dm
@@ -0,0 +1,60 @@
+GLOBAL_LIST_EMPTY(gangster_cell_phones)
+
+/obj/item/gangster_cellphone
+ name = "cell phone"
+ desc = "TODO: funny joke about the 80s, brick phones"
+ icon = 'icons/obj/gang/cell_phone.dmi'
+ icon_state = "phone_off"
+ throwforce = 15 // these things are dense as fuck
+ var/gang_id = "Grove Street Families"
+ var/activated = FALSE
+
+/obj/item/gangster_cellphone/Initialize()
+ . = ..()
+ GLOB.gangster_cell_phones += src
+ flags_1 |= HEAR_1
+
+/obj/item/gangster_cellphone/Destroy()
+ GLOB.gangster_cell_phones -= src
+ . = ..()
+
+/obj/item/gangster_cellphone/attack_self(mob/user, modifiers)
+ . = ..()
+ if(!activated)
+ to_chat(user, "You turn on [src].")
+ icon_state = "phone_on"
+ activated = TRUE
+ else
+ to_chat(user, "You turn off [src].")
+ icon_state = "phone_off"
+ activated = FALSE
+
+/obj/item/gangster_cellphone/Hear(message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, list/message_mods)
+ . = ..()
+ if(!activated)
+ return
+ if(get_turf(speaker) != get_turf(src))
+ return
+ broadcast_message(raw_message, speaker)
+
+/obj/item/gangster_cellphone/proc/broadcast_message(message, atom/movable/speaker)
+ for(var/obj/item/gangster_cellphone/brick in GLOB.gangster_cell_phones)
+ if(brick.gang_id == gang_id)
+ brick.say_message(message, speaker)
+ for(var/mob/dead/observer/player_mob in GLOB.player_list)
+ if(!istype(player_mob, /mob/dead/observer))
+ continue
+ if(QDELETED(player_mob)) //Some times nulls and deleteds stay in this list. This is a workaround to prevent ic chat breaking for everyone when they do.
+ continue //Remove if underlying cause (likely byond issue) is fixed. See TG PR #49004.
+ if(player_mob.stat != DEAD) //not dead, not important
+ continue
+ if(get_dist(player_mob, src) > 7 || player_mob.z != z) //they're out of range of normal hearing
+ if(!(player_mob.client.prefs.chat_toggles & CHAT_GHOSTEARS)) //they're talking normally and we have hearing at any range off
+ continue
+ to_chat(player_mob, span_gangradio("[speaker.name] \[CELL: [gang_id]\] says, \"[message]\""))
+
+/obj/item/gangster_cellphone/proc/say_message(message, atom/movable/speaker)
+ for(var/mob/living/carbon/human/cellphone_hearer in get_turf(src))
+ if(HAS_TRAIT(cellphone_hearer, TRAIT_DEAF))
+ continue
+ to_chat(cellphone_hearer, span_gangradio("[speaker.name] \[CELL: [gang_id]\] says, \"[message]\""))
diff --git a/code/modules/antagonists/gang/gang.dm b/code/modules/antagonists/gang/gang.dm
new file mode 100644
index 0000000000..ea81c8b1af
--- /dev/null
+++ b/code/modules/antagonists/gang/gang.dm
@@ -0,0 +1,779 @@
+/datum/antagonist/gang
+ name = "Family Member"
+ roundend_category = "gangsters"
+ ui_name = "AntagInfoGangmember"
+ antag_hud_type = ANTAG_HUD_GANGSTER
+ antag_hud_name = "hud_gangster"
+ antagpanel_category = "Family"
+ show_in_antagpanel = FALSE // i don't *think* this base class is buggy but it's too worthless to test
+ suicide_cry = "FOR THE FAMILY!!"
+ preview_outfit = /datum/outfit/gangster
+ /// The overarching family that the owner of this datum is a part of. Family teams are generic and imprinted upon by the per-person antagonist datums.
+ var/datum/team/gang/my_gang
+ /// The name of the family corresponding to this family member datum.
+ var/gang_name = "Leet Like Jeff K"
+ /// The abbreviation of the family corresponding to this family member datum.
+ var/gang_id = "LLJK"
+ /// The list of clothes that are acceptable to show allegiance to this family.
+ var/list/acceptable_clothes = list()
+ /// The list of clothes that are given to family members upon induction into the family.
+ var/list/free_clothes = list()
+ /// The action used to spawn family induction packages.
+ var/datum/action/cooldown/spawn_induction_package/package_spawner = new()
+ /// Whether or not this family member is the first of their family.
+ var/starter_gangster = FALSE
+ /// The gangster's original real name. Used for renaming stuff, kept between gang switches.
+ var/original_name
+ /// Type of team to create when creating the gang in the first place. Used for renames.
+ var/gang_team_type = /datum/team/gang
+
+ /// A reference to the handler datum that manages the families gamemode. In case of no handler (admin-spawned during round), this will be null; this is fine.
+ var/datum/gang_handler/handler
+
+/datum/outfit/gangster
+ name = "Gangster (Preview only)"
+
+ uniform = /obj/item/clothing/under/suit/henchmen
+ back = /obj/item/storage/backpack/henchmen
+
+/datum/antagonist/gang/get_team()
+ return my_gang
+
+/datum/antagonist/gang/get_admin_commands()
+ . = ..()
+ .["Give extra equipment"] = CALLBACK(src,.proc/equip_gangster_in_inventory)
+
+/datum/antagonist/gang/create_team(team_given) // gets called whenever add_antag_datum() is called on a mind
+ if(team_given)
+ my_gang = team_given
+ return
+ /* if team_given is falsey, this gang member didn't join a gang by using a recruitment package. so there are two things we need to consider
+ 1. does a gang handler exist -- does this round have a gang_handler instanced by the families gamemode or ruleset?
+ 2. does the gang we're trying to join already exist?
+ if 1 is true and 2 is false, we were probably added by the gang_handler, and probably already have a "handler" var.
+ if we don't have a "handler" var, and a gang_handler exists, we need to grab it, since our "handler" is null.
+ if the gang exists, we need to join it; if the gang doesn't exist, we need to make it. */
+ var/found_gang = FALSE
+ for(var/datum/team/gang/gang_team in GLOB.antagonist_teams)
+ if(gang_team.my_gang_datum.handler) // if one of the gangs in the gang list has a handler, nab that
+ handler = gang_team.my_gang_datum.handler
+ if(gang_team.name == gang_name)
+ my_gang = gang_team
+ found_gang = TRUE
+ break
+ if(!found_gang)
+ var/new_gang = new gang_team_type()
+ my_gang = new_gang
+ if(handler) // if we have a handler, the handler should track this gang
+ handler.gangs += my_gang
+ my_gang.current_theme = handler.current_theme
+ my_gang.name = gang_name
+ my_gang.gang_id = gang_id
+ my_gang.acceptable_clothes = acceptable_clothes.Copy()
+ my_gang.free_clothes = free_clothes.Copy()
+ my_gang.my_gang_datum = src
+ starter_gangster = TRUE
+
+/datum/antagonist/gang/on_gain()
+ if(!original_name)
+ original_name = owner.current.real_name
+ my_gang.rename_gangster(owner, original_name, starter_gangster) // fully_replace_character_name
+ if(starter_gangster)
+ equip_gangster_in_inventory()
+ var/datum/atom_hud/gang_hud = GLOB.huds[ANTAG_HUD_GANGSTER]
+ gang_hud.add_hud_to(owner.current)
+ owner.current.playsound_local(get_turf(owner.current), 'sound/ambience/antag/thatshowfamiliesworks.ogg', 100, FALSE, pressure_affected = FALSE)
+ ..()
+
+/datum/antagonist/gang/on_removal()
+ if(my_gang.my_gang_datum == src) // if we're the first gangster, we need to replace ourselves so that objectives function correctly
+ var/datum/antagonist/gang/replacement_datum = new type()
+ replacement_datum.handler = handler
+ replacement_datum.my_gang = my_gang
+ my_gang.my_gang_datum = replacement_datum
+ /* all we need to replace; the gang's "my_gang_datum" is just a person's datum because we assign it while we
+ have that datum onhand. it would be easier if all of the code the gang team calls on its my_gang_datum was
+ just in the team datum itself, and there were different types of teams instead of different types of gangster
+ that imprint on generic teams, but i'm too lazy to refactor THAT too */
+ var/datum/atom_hud/gang_hud = GLOB.huds[ANTAG_HUD_GANGSTER]
+ gang_hud.remove_hud_from(owner.current)
+ ..()
+
+/datum/antagonist/gang/apply_innate_effects(mob/living/mob_override)
+ ..()
+ if(starter_gangster)
+ package_spawner.Grant(owner.current)
+ package_spawner.my_gang_datum = src
+ var/mob/living/M = mob_override || owner.current
+ add_antag_hud(antag_hud_type, antag_hud_name, M)
+ if(M.hud_used)
+ var/datum/hud/H = M.hud_used
+ var/atom/movable/screen/wanted/giving_wanted_lvl = new /atom/movable/screen/wanted()
+ H.wanted_lvl = giving_wanted_lvl
+ giving_wanted_lvl.hud = H
+ H.infodisplay += giving_wanted_lvl
+ H.mymob.client.screen += giving_wanted_lvl
+
+/datum/antagonist/gang/remove_innate_effects(mob/living/mob_override)
+ if(starter_gangster)
+ package_spawner.Remove(owner.current)
+ var/mob/living/M = mob_override || owner.current
+ remove_antag_hud(antag_hud_type, M)
+ if(M.hud_used)
+ var/datum/hud/H = M.hud_used
+ H.infodisplay -= H.wanted_lvl
+ QDEL_NULL(H.wanted_lvl)
+ ..()
+
+/// Gives a gangster their equipment in their backpack and / or pockets.
+/datum/antagonist/gang/proc/equip_gangster_in_inventory()
+ if(istype(owner.current, /mob/living/carbon/human))
+ var/obj/item/gangster_cellphone/phone = new()
+ phone.gang_id = gang_name
+ phone.name = "[gang_name] branded cell phone"
+ var/mob/living/carbon/human/gangster_human = owner.current
+ var/phone_equipped = phone.equip_to_best_slot(gangster_human)
+ if(!phone_equipped)
+ to_chat(owner.current, "Your [phone.name] has been placed at your feet.")
+ phone.forceMove(get_turf(gangster_human))
+ for(var/clothing in my_gang.free_clothes)
+ var/obj/item/clothing_object = new clothing(owner.current)
+ var/equipped = clothing_object.equip_to_best_slot(gangster_human)
+ if(!equipped)
+ to_chat(owner.current, "Your [clothing_object.name] has been placed at your feet.")
+ clothing_object.forceMove(get_turf(gangster_human))
+ if(my_gang.current_theme.bonus_items)
+ for(var/bonus_item in my_gang.current_theme.bonus_items)
+ var/obj/item/bonus_object = new bonus_item(owner.current)
+ var/equipped = bonus_object.equip_to_best_slot(gangster_human)
+ if(!equipped)
+ to_chat(owner.current, "Your [bonus_object.name] has been placed at your feet.")
+ bonus_object.forceMove(get_turf(gangster_human))
+ if(starter_gangster)
+ if(my_gang.current_theme.bonus_first_gangster_items)
+ for(var/bonus_starter_item in my_gang.current_theme.bonus_first_gangster_items)
+ var/obj/item/bonus_starter_object = new bonus_starter_item(owner.current)
+ var/equipped = bonus_starter_object.equip_to_best_slot(gangster_human)
+ if(!equipped)
+ to_chat(owner.current, "Your [bonus_starter_object.name] has been placed at your feet.")
+ bonus_starter_object.forceMove(get_turf(gangster_human))
+
+/datum/antagonist/gang/ui_static_data(mob/user)
+ var/list/data = list()
+ data["gang_name"] = gang_name
+ data["antag_name"] = name
+ data["gang_objective"] = my_gang.current_theme.gang_objectives[type]
+
+ var/list/clothes_we_can_wear = list()
+ for(var/obj/item/accepted_item as anything in acceptable_clothes)
+ clothes_we_can_wear |= initial(accepted_item.name)
+
+ for(var/obj/item/free_item as anything in free_clothes)
+ if(ispath(free_item, /obj/item/toy/crayon/spraycan))
+ continue
+ clothes_we_can_wear |= initial(free_item.name)
+
+ data["gang_clothes"] = clothes_we_can_wear
+ return data
+
+/datum/team/gang
+ /// The abbreviation of this family.
+ var/gang_id = "LLJK"
+ /// The list of clothes that are acceptable to show allegiance to this family.
+ var/list/acceptable_clothes = list()
+ /// The list of clothes that are given to family members upon induction into the family.
+ var/list/free_clothes = list()
+ /// The specific, occupied family member antagonist datum that is used to reach the handler / check objectives, and from which the above properties (sans points) are inherited.
+ var/datum/antagonist/gang/my_gang_datum
+ /// The current theme. Used to pull important stuff such as spawning equipment and objectives.
+ var/datum/gang_theme/current_theme
+
+/// Allow gangs to have custom naming schemes for their gangsters.
+/datum/team/gang/proc/rename_gangster(datum/mind/gangster, original_name, starter_gangster)
+ gangster.current.fully_replace_character_name(gangster.current.real_name, original_name)
+
+/datum/team/gang/roundend_report()
+ var/list/report = list()
+ report += ""
+ if(!members.len)
+ report += span_redtext("The family was wiped out!")
+ if(current_theme.everyone_objective)
+ report += "Objective: [current_theme.everyone_objective]"
+ else
+ var/assigned_objective = current_theme.gang_objectives[my_gang_datum.type]
+ if(assigned_objective)
+ report += "Objective: [assigned_objective]"
+ else
+ report += "Objective: ERROR, FILE A REPORT WITH THIS INFO: Gang Name: [my_gang_datum.name], Theme Name: [current_theme.name]"
+ if(members.len)
+ report += "[my_gang_datum.roundend_category] were:"
+ report += printplayerlist(members)
+
+ return "[report.Join("
")]
"
+
+/datum/action/cooldown/spawn_induction_package
+ name = "Induct via Secret Handshake"
+ desc = "Teach new recruits the Secret Handshake to join."
+ check_flags = AB_CHECK_CONSCIOUS
+ button_icon_state = "recruit"
+ icon_icon = 'icons/obj/gang/actions.dmi'
+ cooldown_time = 300
+ /// The family antagonist datum of the "owner" of this action.
+ var/datum/antagonist/gang/my_gang_datum
+
+/datum/action/cooldown/spawn_induction_package/Trigger()
+ if(!..())
+ return FALSE
+ if(!IsAvailable())
+ return FALSE
+ if(!my_gang_datum)
+ return FALSE
+ if(!istype(owner, /mob/living/carbon/human))
+ return FALSE
+ var/mob/living/carbon/human/H = owner
+ if(H.stat)
+ return FALSE
+
+ var/obj/item/slapper/secret_handshake/secret_handshake_item = new(owner)
+ if(owner.put_in_hands(secret_handshake_item))
+ to_chat(owner, span_notice("You ready your secret handshake."))
+ else
+ qdel(secret_handshake_item)
+ to_chat(owner, span_warning("You're incapable of performing a handshake in your current state."))
+ return FALSE
+ owner.visible_message(span_notice("[owner] is offering to induct people into the Family."),
+ span_notice("You offer to induct people into the Family."), null, 2)
+ if(H.has_status_effect(STATUS_EFFECT_HANDSHAKE))
+ return FALSE
+ if(!(locate(/mob/living/carbon) in orange(1, owner)))
+ owner.visible_message(span_danger("[owner] offers to induct people into the Family, but nobody was around."), \
+ span_warning("You offer to induct people into the Family, but nobody is around."), null, 2)
+ return FALSE
+
+ H.apply_status_effect(STATUS_EFFECT_HANDSHAKE, secret_handshake_item)
+ StartCooldown()
+ return TRUE
+
+/datum/antagonist/gang/russian_mafia
+ show_in_antagpanel = TRUE
+ name = "Mafioso"
+ roundend_category = "The mafiosos"
+ gang_name = "Mafia"
+ gang_id = "RM"
+ acceptable_clothes = list(/obj/item/clothing/head/soft/red,
+ /obj/item/clothing/neck/scarf/red,
+ /obj/item/clothing/under/suit/white,
+ /obj/item/clothing/head/beanie/red,
+ /obj/item/clothing/head/ushanka)
+ free_clothes = list(/obj/item/clothing/head/ushanka,
+ /obj/item/clothing/under/suit/white,
+ /obj/item/toy/crayon/spraycan)
+ antag_hud_name = "Russian"
+ gang_team_type = /datum/team/gang/russian_mafia
+
+/datum/team/gang/russian_mafia/rename_gangster(datum/mind/gangster, original_name, starter_gangster)
+ var/static/regex/last_name = new("\[^\\s-\]+$") //First word before whitespace or "-"
+ last_name.Find(original_name)
+ if(starter_gangster)
+ gangster.current.fully_replace_character_name(gangster.current.real_name, "Don [last_name.match]")
+ else
+ gangster.current.fully_replace_character_name(gangster.current.real_name, original_name)
+
+/datum/antagonist/gang/italian_mob
+ show_in_antagpanel = TRUE
+ name = "Mobster"
+ roundend_category = "The mobsters"
+ gang_name = "Mob"
+ gang_id = "IM"
+ acceptable_clothes = list(/obj/item/clothing/under/suit/checkered,
+ /obj/item/clothing/head/fedora,
+ /obj/item/clothing/neck/scarf/green,
+ /obj/item/clothing/mask/bandana/green)
+ free_clothes = list(/obj/item/clothing/head/fedora,
+ /obj/item/clothing/under/suit/checkered,
+ /obj/item/toy/crayon/spraycan)
+ antag_hud_name = "Italian"
+ gang_team_type = /datum/team/gang/russian_mafia
+
+/datum/team/gang/russian_mafia/rename_gangster(datum/mind/gangster, original_name, starter_gangster)
+ var/static/regex/last_name = new("\[^\\s-\]+$") //First word before whitespace or "-"
+ last_name.Find(original_name)
+ if(starter_gangster)
+ gangster.current.fully_replace_character_name(gangster.current.real_name, "Boss [last_name.match]")
+ else
+ gangster.current.fully_replace_character_name(gangster.current.real_name, original_name)
+
+/datum/antagonist/gang/tunnel_snakes
+ show_in_antagpanel = TRUE
+ name = "Tunnel Snake"
+ roundend_category = "The Tunnel Snakes"
+ gang_name = "Tunnel Snakes"
+ gang_id = "TS"
+ acceptable_clothes = list(/obj/item/clothing/under/pants/classicjeans,
+ /obj/item/clothing/suit/jacket,
+ /obj/item/clothing/mask/bandana/skull)
+ free_clothes = list(/obj/item/clothing/suit/jacket,
+ /obj/item/clothing/under/pants/classicjeans,
+ /obj/item/toy/crayon/spraycan)
+ antag_hud_name = "Snakes"
+ gang_team_type = /datum/team/gang/tunnel_snakes
+
+/datum/team/gang/tunnel_snakes/rename_gangster(datum/mind/gangster, original_name, starter_gangster)
+ var/static/regex/last_name = new("\[^\\s-\]+$") //First word before whitespace or "-"
+ last_name.Find(original_name)
+ if(starter_gangster)
+ gangster.current.fully_replace_character_name(gangster.current.real_name, "King Cobra [last_name.match]")
+ else
+ gangster.current.fully_replace_character_name(gangster.current.real_name, original_name)
+
+/datum/antagonist/gang/henchmen
+ show_in_antagpanel = TRUE
+ name = "Monarch Henchmen"
+ roundend_category = "The Monarch henchmen"
+ gang_name = "Monarch Crew"
+ gang_id = "HENCH"
+ acceptable_clothes = list(/obj/item/clothing/head/soft/yellow,
+ /obj/item/clothing/under/suit/henchmen,
+ /obj/item/clothing/neck/scarf/yellow,
+ /obj/item/clothing/head/beanie/yellow,
+ /obj/item/clothing/mask/bandana/gold,
+ /obj/item/storage/backpack/henchmen)
+ free_clothes = list(/obj/item/storage/backpack/henchmen,
+ /obj/item/clothing/under/suit/henchmen,
+ /obj/item/toy/crayon/spraycan)
+ antag_hud_name = "Monarch"
+ gang_team_type = /datum/team/gang/henchmen
+
+/datum/team/gang/henchmen
+ var/henchmen_count = 0
+
+/datum/team/gang/henchmen/rename_gangster(datum/mind/gangster, original_name, starter_gangster)
+ henchmen_count++
+ gangster.current.fully_replace_character_name(gangster.current.real_name, "Number [henchmen_count]")
+
+/datum/antagonist/gang/yakuza
+ show_in_antagpanel = TRUE
+ name = "Tojo Clan Member"
+ roundend_category = "The Yakuza"
+ gang_name = "Tojo Clan"
+ gang_id = "YAK"
+ acceptable_clothes = list(/obj/item/clothing/head/soft/yellow,
+ /obj/item/clothing/under/costume/yakuza,
+ /obj/item/clothing/shoes/yakuza,
+ /obj/item/clothing/neck/scarf/yellow,
+ /obj/item/clothing/head/beanie/yellow,
+ /obj/item/clothing/mask/bandana/gold,
+ /obj/item/clothing/head/hardhat,
+ /obj/item/clothing/suit/yakuza)
+ free_clothes = list(/obj/item/clothing/under/costume/yakuza,
+ /obj/item/clothing/shoes/yakuza,
+ /obj/item/clothing/suit/yakuza,
+ /obj/item/clothing/head/hardhat,
+ /obj/item/toy/crayon/spraycan)
+ antag_hud_name = "Tojo"
+ gang_team_type = /datum/team/gang/yakuza
+
+/datum/team/gang/yakuza/rename_gangster(datum/mind/gangster, original_name, starter_gangster)
+ var/static/regex/last_name = new("\[^\\s-\]+$") //First word before whitespace or "-"
+ last_name.Find(original_name)
+ if(starter_gangster)
+ gangster.current.fully_replace_character_name(gangster.current.real_name, "Patriarch [last_name.match]")
+ else
+ gangster.current.fully_replace_character_name(gangster.current.real_name, original_name)
+
+/datum/antagonist/gang/jackbros
+ show_in_antagpanel = TRUE
+ name = "Jack Bro"
+ roundend_category = "The Hee-hos"
+ gang_name = "Jack Bros"
+ gang_id = "JB"
+ acceptable_clothes = list(/obj/item/clothing/head/soft/blue,
+ /obj/item/clothing/under/costume/jackbros,
+ /obj/item/clothing/shoes/jackbros,
+ /obj/item/clothing/head/jackbros,
+ /obj/item/clothing/mask/bandana/blue)
+ free_clothes = list(/obj/item/clothing/under/costume/jackbros,
+ /obj/item/clothing/shoes/jackbros,
+ /obj/item/clothing/head/jackbros,
+ /obj/item/toy/crayon/spraycan)
+ antag_hud_name = "JackFrost"
+ gang_team_type = /datum/team/gang/jackbros
+
+/datum/team/gang/jackbros/rename_gangster(datum/mind/gangster, original_name, starter_gangster)
+ var/static/regex/last_name = new("\[^\\s-\]+$") //First word before whitespace or "-"
+ last_name.Find(original_name)
+ if(starter_gangster)
+ gangster.current.fully_replace_character_name(gangster.current.real_name, "King Frost [last_name.match]")
+ else
+ gangster.current.fully_replace_character_name(gangster.current.real_name, original_name)
+
+/datum/antagonist/gang/dutch
+ show_in_antagpanel = TRUE
+ name = "Dutch van der Linde Outlaw"
+ roundend_category = "Dutch's outlaws"
+ gang_name = "Dutch van der Linde's Gang"
+ gang_id = "VDL"
+ acceptable_clothes = list(/obj/item/clothing/head/soft/black,
+ /obj/item/clothing/under/costume/dutch,
+ /obj/item/clothing/suit/dutch,
+ /obj/item/clothing/head/bowler,
+ /obj/item/clothing/mask/bandana/black)
+ free_clothes = list(/obj/item/clothing/under/costume/dutch,
+ /obj/item/clothing/head/bowler,
+ /obj/item/clothing/suit/dutch,
+ /obj/item/toy/crayon/spraycan)
+ antag_hud_name = "Dutch"
+ gang_team_type = /datum/team/gang/dutch
+
+/datum/team/gang/dutch/rename_gangster(datum/mind/gangster, original_name, starter_gangster)
+ var/static/regex/last_name = new("\[^\\s-\]+$") //First word before whitespace or "-"
+ last_name.Find(original_name)
+ if(starter_gangster)
+ gangster.current.fully_replace_character_name(gangster.current.real_name, "Head Cowboy [last_name.match]")
+ else
+ gangster.current.fully_replace_character_name(gangster.current.real_name, original_name)
+
+
+/datum/antagonist/gang/irs
+ show_in_antagpanel = TRUE
+ name = "Internal Revenue Service Agent"
+ roundend_category = "IRS Agents"
+ gang_name = "Internal Revenue Service"
+ gang_id = "IRS"
+ acceptable_clothes = list(/obj/item/clothing/suit/irs,
+ /obj/item/clothing/under/costume/irs,
+ /obj/item/clothing/head/irs)
+ free_clothes = list(/obj/item/clothing/suit/irs,
+ /obj/item/clothing/under/costume/irs,
+ /obj/item/clothing/head/irs,
+ /obj/item/toy/crayon/spraycan)
+ antag_hud_name = "IRS"
+ gang_team_type = /datum/team/gang/irs
+
+/datum/team/gang/irs/rename_gangster(datum/mind/gangster, original_name, starter_gangster)
+ var/static/regex/last_name = new("\[^\\s-\]+$") //First word before whitespace or "-"
+ last_name.Find(original_name)
+ if(starter_gangster)
+ gangster.current.fully_replace_character_name(gangster.current.real_name, "Revenue Supervisor [last_name.match]")
+ else
+ gangster.current.fully_replace_character_name(gangster.current.real_name, "Revenue Agent [last_name.match]")
+
+/datum/antagonist/gang/osi
+ show_in_antagpanel = TRUE
+ name = "Office of Secret Intelligence Agent"
+ roundend_category = "O.S.I. Agents"
+ gang_name = "Office of Secret Intelligence"
+ gang_id = "OSI"
+ acceptable_clothes = list(/obj/item/clothing/suit/osi,
+ /obj/item/clothing/under/costume/osi,
+ /obj/item/clothing/glasses/osi)
+ free_clothes = list(/obj/item/clothing/suit/osi,
+ /obj/item/clothing/under/costume/osi,
+ /obj/item/clothing/glasses/osi,
+ /obj/item/toy/crayon/spraycan)
+ antag_hud_name = "OSI"
+ gang_team_type = /datum/team/gang/osi
+
+/datum/team/gang/osi/rename_gangster(datum/mind/gangster, original_name, starter_gangster)
+ var/static/regex/last_name = new("\[^\\s-\]+$") //First word before whitespace or "-"
+ last_name.Find(original_name)
+ if(starter_gangster)
+ gangster.current.fully_replace_character_name(gangster.current.real_name, "General [last_name.match]")
+ else
+ gangster.current.fully_replace_character_name(gangster.current.real_name, "Special Agent [last_name.match]")
+
+/datum/antagonist/gang/tmc
+ show_in_antagpanel = TRUE
+ name = "Lost M.C. Biker"
+ roundend_category = "Lost M.C. Bikers"
+ gang_name = "The Lost M.C."
+ gang_id = "TMC"
+ acceptable_clothes = list(/obj/item/clothing/suit/tmc,
+ /obj/item/clothing/under/costume/tmc,
+ /obj/item/clothing/head/tmc)
+ free_clothes = list(/obj/item/clothing/suit/tmc,
+ /obj/item/clothing/under/costume/tmc,
+ /obj/item/clothing/head/tmc,
+ /obj/item/toy/crayon/spraycan)
+ antag_hud_name = "LostMC"
+ gang_team_type = /datum/team/gang/tmc
+
+/datum/team/gang/tmc/rename_gangster(datum/mind/gangster, original_name, starter_gangster)
+ var/static/regex/last_name = new("\[^\\s-\]+$") //First word before whitespace or "-"
+ last_name.Find(original_name)
+ if(starter_gangster)
+ gangster.current.fully_replace_character_name(gangster.current.real_name, "President [last_name.match]")
+ else
+ gangster.current.fully_replace_character_name(gangster.current.real_name, original_name)
+
+/datum/antagonist/gang/pg
+ show_in_antagpanel = TRUE
+ name = "Powder Ganger"
+ roundend_category = "Powder Gangers"
+ gang_name = "Powder Gangers"
+ gang_id = "PG"
+ acceptable_clothes = list(/obj/item/clothing/suit/pg,
+ /obj/item/clothing/under/costume/pg,
+ /obj/item/clothing/head/pg)
+ free_clothes = list(/obj/item/clothing/suit/pg,
+ /obj/item/clothing/under/costume/pg,
+ /obj/item/clothing/head/pg,
+ /obj/item/toy/crayon/spraycan)
+ antag_hud_name = "PowderGang"
+ gang_team_type = /datum/team/gang/pg
+
+/datum/team/gang/pg/rename_gangster(datum/mind/gangster, original_name, starter_gangster)
+ var/static/regex/last_name = new("\[^\\s-\]+$") //First word before whitespace or "-"
+ last_name.Find(original_name)
+ if(starter_gangster)
+ gangster.current.fully_replace_character_name(gangster.current.real_name, "Head Convict [last_name.match]")
+ else
+ gangster.current.fully_replace_character_name(gangster.current.real_name, original_name)
+
+
+/datum/antagonist/gang/driscoll
+ show_in_antagpanel = TRUE
+ name = "O'Driscoll Gangster"
+ roundend_category = "O'Driscoll's Gangsters"
+ gang_name = "O'Driscoll's Gang"
+ gang_id = "DB"
+ acceptable_clothes = list(/obj/item/clothing/suit/driscoll,
+ /obj/item/clothing/under/costume/driscoll,
+ /obj/item/clothing/mask/gas/driscoll,
+ /obj/item/clothing/shoes/cowboyboots)
+ free_clothes = list(/obj/item/clothing/suit/driscoll,
+ /obj/item/clothing/under/costume/driscoll,
+ /obj/item/clothing/mask/gas/driscoll,
+ /obj/item/clothing/shoes/cowboyboots,
+ /obj/item/toy/crayon/spraycan)
+ antag_hud_name = "Drill"
+ gang_team_type = /datum/team/gang/driscoll
+
+/datum/team/gang/driscoll/rename_gangster(datum/mind/gangster, original_name, starter_gangster)
+ var/static/regex/last_name = new("\[^\\s-\]+$") //First word before whitespace or "-"
+ last_name.Find(original_name)
+ if(starter_gangster)
+ gangster.current.fully_replace_character_name(gangster.current.real_name, "Head Outlaw [last_name.match]")
+ else
+ gangster.current.fully_replace_character_name(gangster.current.real_name, original_name)
+
+/datum/antagonist/gang/deckers
+ show_in_antagpanel = TRUE
+ name = "Decker"
+ roundend_category = "Deckers"
+ gang_name = "Deckers"
+ gang_id = "DK"
+ acceptable_clothes = list(/obj/item/clothing/suit/deckers,
+ /obj/item/clothing/under/costume/deckers,
+ /obj/item/clothing/head/deckers,
+ /obj/item/clothing/shoes/deckers)
+ free_clothes = list(/obj/item/clothing/suit/deckers,
+ /obj/item/clothing/under/costume/deckers,
+ /obj/item/clothing/head/deckers,
+ /obj/item/clothing/shoes/deckers,
+ /obj/item/toy/crayon/spraycan)
+ antag_hud_name = "Deckers"
+ gang_team_type = /datum/team/gang/deckers
+
+/datum/team/gang/deckers/rename_gangster(datum/mind/gangster, original_name, starter_gangster)
+ var/static/regex/last_name = new("\[^\\s-\]+$") //First word before whitespace or "-"
+ last_name.Find(original_name)
+ if(starter_gangster)
+ gangster.current.fully_replace_character_name(gangster.current.real_name, "Master Hacker [last_name.match]")
+ else
+ gangster.current.fully_replace_character_name(gangster.current.real_name, original_name)
+
+
+/datum/antagonist/gang/morningstar
+ show_in_antagpanel = TRUE
+ name = "Morningstar Member"
+ roundend_category = "Morningstar Member"
+ gang_name = "Morningstar"
+ gang_id = "MS"
+ acceptable_clothes = list(/obj/item/clothing/suit/morningstar,
+ /obj/item/clothing/under/costume/morningstar,
+ /obj/item/clothing/head/morningstar,
+ /obj/item/clothing/shoes/morningstar)
+ free_clothes = list(/obj/item/clothing/suit/morningstar,
+ /obj/item/clothing/under/costume/morningstar,
+ /obj/item/clothing/head/morningstar,
+ /obj/item/clothing/shoes/morningstar,
+ /obj/item/toy/crayon/spraycan)
+ antag_hud_name = "MorningStar"
+ gang_team_type = /datum/team/gang/morningstar
+
+/datum/team/gang/morningstar/rename_gangster(datum/mind/gangster, original_name, starter_gangster)
+ var/static/regex/last_name = new("\[^\\s-\]+$") //First word before whitespace or "-"
+ last_name.Find(original_name)
+ if(starter_gangster)
+ gangster.current.fully_replace_character_name(gangster.current.real_name, "Chief Executive Officer [last_name.match]")
+ else
+ gangster.current.fully_replace_character_name(gangster.current.real_name, original_name)
+
+/datum/antagonist/gang/saints
+ show_in_antagpanel = TRUE
+ name = "Third Street Saints Gangster"
+ roundend_category = "Third Street Saints Gangsters"
+ gang_name = "Third Street Saints"
+ gang_id = "TSS"
+ acceptable_clothes = list(/obj/item/clothing/suit/saints,
+ /obj/item/clothing/under/costume/saints,
+ /obj/item/clothing/head/saints,
+ /obj/item/clothing/shoes/saints)
+ free_clothes = list(/obj/item/clothing/suit/saints,
+ /obj/item/clothing/under/costume/saints,
+ /obj/item/clothing/head/saints,
+ /obj/item/clothing/shoes/saints,
+ /obj/item/toy/crayon/spraycan)
+ antag_hud_name = "TheSaints"
+ gang_team_type = /datum/team/gang/saints
+
+/datum/team/gang/saints/rename_gangster(datum/mind/gangster, original_name, starter_gangster)
+ var/static/regex/last_name = new("\[^\\s-\]+$") //First word before whitespace or "-"
+ last_name.Find(original_name)
+ if(starter_gangster)
+ gangster.current.fully_replace_character_name(gangster.current.real_name, "Boss [last_name.match]")
+ else
+ gangster.current.fully_replace_character_name(gangster.current.real_name, original_name)
+
+
+/datum/antagonist/gang/phantom
+ show_in_antagpanel = TRUE
+ name = "Phantom Thief"
+ roundend_category = "Phantom Thieves"
+ gang_name = "Phantom Thieves of Hearts"
+ gang_id = "PT"
+ acceptable_clothes = list(/obj/item/clothing/suit/phantom,
+ /obj/item/clothing/under/costume/phantom,
+ /obj/item/clothing/glasses/phantom,
+ /obj/item/clothing/shoes/phantom)
+ free_clothes = list(/obj/item/clothing/suit/phantom,
+ /obj/item/clothing/under/costume/phantom,
+ /obj/item/clothing/glasses/phantom,
+ /obj/item/clothing/shoes/phantom,
+ /obj/item/toy/crayon/spraycan)
+ antag_hud_name = "PhantomThieves"
+ gang_team_type = /datum/team/gang/phantom
+
+/datum/team/gang/phantom/rename_gangster(datum/mind/gangster, original_name, starter_gangster)
+ var/static/regex/last_name = new("\[^\\s-\]+$") //First word before whitespace or "-"
+ last_name.Find(original_name)
+ if(starter_gangster)
+ gangster.current.fully_replace_character_name(gangster.current.real_name, "Joker [last_name.match]")
+ else
+ gangster.current.fully_replace_character_name(gangster.current.real_name, original_name)
+
+/datum/antagonist/gang/allies
+ show_in_antagpanel = TRUE
+ name = "Allies G.I."
+ roundend_category = "Allies"
+ gang_name = "Allies"
+ gang_id = "ALLIES"
+ free_clothes = list(/obj/item/clothing/suit/allies,
+ /obj/item/clothing/under/costume/allies,
+ /obj/item/clothing/head/allies,
+ /obj/item/clothing/gloves/color/black,
+ /obj/item/clothing/shoes/jackboots,
+ /obj/item/toy/crayon/spraycan)
+ antag_hud_name = "Allies"
+ gang_team_type = /datum/team/gang/allies
+
+/datum/team/gang/allies/rename_gangster(datum/mind/gangster, original_name, starter_gangster)
+ var/static/regex/last_name = new("\[^\\s-\]+$") //First word before whitespace or "-"
+ last_name.Find(original_name)
+ if(starter_gangster)
+ gangster.current.fully_replace_character_name(gangster.current.real_name, "Commander [last_name.match]")
+ else
+ gangster.current.fully_replace_character_name(gangster.current.real_name, "Private [last_name.match]")
+
+/datum/antagonist/gang/soviet
+ show_in_antagpanel = TRUE
+ name = "Soviet Conscript"
+ roundend_category = "Soviets"
+ gang_name = "Soviets"
+ gang_id = "SOV"
+ free_clothes = list(/obj/item/clothing/suit/soviet,
+ /obj/item/clothing/under/costume/soviet_families,
+ /obj/item/clothing/head/ushanka/soviet,
+ /obj/item/clothing/gloves/color/black,
+ /obj/item/clothing/shoes/jackboots,
+ /obj/item/toy/crayon/spraycan)
+ antag_hud_name = "Soviets"
+ gang_team_type = /datum/team/gang/soviet
+
+/datum/team/gang/soviet/rename_gangster(datum/mind/gangster, original_name, starter_gangster)
+ var/static/regex/last_name = new("\[^\\s-\]+$") //First word before whitespace or "-"
+ last_name.Find(original_name)
+ if(starter_gangster)
+ gangster.current.fully_replace_character_name(gangster.current.real_name, "Comrade General [last_name.match]")
+ else
+ gangster.current.fully_replace_character_name(gangster.current.real_name, "Conscript [last_name.match]")
+
+/datum/antagonist/gang/yuri
+ show_in_antagpanel = TRUE
+ name = "Yuri Initiate"
+ roundend_category = "Yuri's Army"
+ gang_name = "Yuri's Army"
+ gang_id = "YR"
+ free_clothes = list(/obj/item/clothing/suit/yuri,
+ /obj/item/clothing/under/costume/yuri,
+ /obj/item/clothing/head/yuri,
+ /obj/item/clothing/gloves/color/black,
+ /obj/item/clothing/shoes/jackboots,
+ /obj/item/toy/crayon/spraycan)
+ antag_hud_name = "YuriArmy"
+ gang_team_type = /datum/team/gang/yuri
+
+/datum/team/gang/yuri/rename_gangster(datum/mind/gangster, original_name, starter_gangster)
+ var/static/regex/last_name = new("\[^\\s-\]+$") //First word before whitespace or "-"
+ last_name.Find(original_name)
+ if(starter_gangster)
+ gangster.current.fully_replace_character_name(gangster.current.real_name, "Initiate Prime [last_name.match]")
+ else
+ gangster.current.fully_replace_character_name(gangster.current.real_name, "Initiate [last_name.match]")
+
+/datum/antagonist/gang/sybil_slickers
+ show_in_antagpanel = TRUE
+ name = "Sybil Slicker"
+ roundend_category = "Sybil Slickers"
+ gang_name = "Sybil Slickers"
+ gang_id = "SS"
+ free_clothes = list(/obj/item/clothing/suit/sybil_slickers,
+ /obj/item/clothing/under/costume/sybil_slickers,
+ /obj/item/clothing/head/sybil_slickers,
+ /obj/item/clothing/gloves/tackler/football,
+ /obj/item/clothing/shoes/sybil_slickers,
+ /obj/item/toy/crayon/spraycan)
+ antag_hud_name = "SybilSlickers"
+ gang_team_type = /datum/team/gang/sybil_slickers
+
+/datum/team/gang/sybil_slickers/rename_gangster(datum/mind/gangster, original_name, starter_gangster)
+ var/static/regex/last_name = new("\[^\\s-\]+$") //First word before whitespace or "-"
+ last_name.Find(original_name)
+ if(starter_gangster)
+ gangster.current.fully_replace_character_name(gangster.current.real_name, "Sybil Coach [last_name.match]")
+ else
+ gangster.current.fully_replace_character_name(gangster.current.real_name, original_name)
+
+/datum/antagonist/gang/basil_boys
+ show_in_antagpanel = TRUE
+ name = "Basil Boy"
+ roundend_category = "Basil Boys"
+ gang_name = "Basil Boys"
+ gang_id = "BB"
+ free_clothes = list(/obj/item/clothing/suit/basil_boys,
+ /obj/item/clothing/under/costume/basil_boys,
+ /obj/item/clothing/head/basil_boys,
+ /obj/item/clothing/gloves/tackler/football,
+ /obj/item/clothing/shoes/basil_boys,
+ /obj/item/toy/crayon/spraycan)
+ antag_hud_name = "BasilBoys"
+ gang_team_type = /datum/team/gang/basil_boys
+
+/datum/team/gang/basil_boys/rename_gangster(datum/mind/gangster, original_name, starter_gangster)
+ var/static/regex/last_name = new("\[^\\s-\]+$") //First word before whitespace or "-"
+ last_name.Find(original_name)
+ if(starter_gangster)
+ gangster.current.fully_replace_character_name(gangster.current.real_name, "Basil Coach [last_name.match]")
+ else
+ gangster.current.fully_replace_character_name(gangster.current.real_name, original_name)
diff --git a/code/modules/antagonists/gang/handler.dm b/code/modules/antagonists/gang/handler.dm
new file mode 100644
index 0000000000..0f6e28752d
--- /dev/null
+++ b/code/modules/antagonists/gang/handler.dm
@@ -0,0 +1,429 @@
+#define LOWPOP_FAMILIES_COUNT 50
+
+#define TWO_STARS_HIGHPOP 11
+#define THREE_STARS_HIGHPOP 16
+#define FOUR_STARS_HIGHPOP 21
+#define FIVE_STARS_HIGHPOP 31
+
+#define TWO_STARS_LOW 6
+#define THREE_STARS_LOW 9
+#define FOUR_STARS_LOW 12
+#define FIVE_STARS_LOW 15
+
+#define CREW_SIZE_MIN 4
+#define CREW_SIZE_MAX 8
+
+
+GLOBAL_VAR_INIT(deaths_during_shift, 0)
+///Forces the Families theme to be the one in this variable via variable editing. Used for debugging.
+GLOBAL_VAR(families_override_theme)
+
+/**
+ * # Families gamemode / dynamic ruleset handler
+ *
+ * A special datum used by the families gamemode and dynamic rulesets to centralize code. "Family" and "gang" used interchangeably in code.
+ *
+ * This datum centralizes code used for the families gamemode / dynamic rulesets. Families incorporates a significant
+ * amount of unique processing; without this datum, that could would be duplicated. To ensure the maintainability
+ * of the families gamemode / rulesets, the code was moved to this datum. The gamemode / rulesets instance this
+ * datum, pass it lists (lists are passed by reference; removing candidates here removes candidates in the gamemode),
+ * and call its procs. Additionally, the families antagonist datum and families induction package also
+ * contain vars that reference this datum, allowing for new families / family members to add themselves
+ * to this datum's lists thereof (primarily used for point calculation). Despite this, the basic team mechanics
+ * themselves should function regardless of this datum's instantiation, should a player have the gang or cop
+ * antagonist datum added to them through methods external to the families gamemode / rulesets.
+ *
+ */
+/datum/gang_handler
+ /// A counter used to minimize the overhead of computationally intensive, periodic family point gain checks. Used and set internally.
+ var/check_counter = 0
+ /// The time, in deciseconds, that the datum's pre_setup() occured at. Used in end_time. Used and set internally.
+ var/start_time = null
+ /// The time, in deciseconds, that the space cops will arrive at. Calculated based on wanted level and start_time. Used and set internally.
+ var/end_time = null
+ /// Whether the gamemode-announcing announcement has been sent. Used and set internally.
+ var/sent_announcement = FALSE
+ /// Whether the "5 minute warning" announcement has been sent. Used and set internally.
+ var/sent_second_announcement = FALSE
+ /// Whether the space cops have arrived. Set internally; used internally, and for updating the wanted HUD.
+ var/cops_arrived = FALSE
+ /// The current wanted level. Set internally; used internally, and for updating the wanted HUD.
+ var/wanted_level
+ /// List of all /datum/team/gang. Used internally; added to externally by /datum/antagonist/gang when it generates a new /datum/team/gang.
+ var/list/gangs = list()
+ /// List of all family member minds. Used internally; added to internally, and externally by /obj/item/gang_induction_package when used to induct a new family member.
+ var/list/gangbangers = list()
+ /// List of all undercover cop minds. Used and set internally.
+ var/list/undercover_cops = list()
+ /// The number of families (and 1:1 corresponding undercover cops) that should be generated. Can be set externally; used internally.
+ var/gangs_to_generate = 3
+ /// The number of family members more that a family may have over other active families. Can be set externally; used internally.
+ var/gang_balance_cap = 5
+ /// Whether the handler corresponds to a ruleset that does not trigger at round start. Should be set externally only if applicable; used internally.
+ var/midround_ruleset = FALSE
+ /// Whether we want to use the 30 to 15 minute timer instead of the 60 to 30 minute timer, for Dynamic.
+ var/use_dynamic_timing = FALSE
+ /// Keeps track of the amount of deaths since the calling of pre_setup_analogue() if this is a midround handler. Used to prevent a high wanted level due to a large amount of deaths during the shift prior to the activation of this handler / the midround ruleset.
+ var/deaths_during_shift_at_beginning = 0
+
+ /// List of all eligible starting family members / undercover cops. Set externally (passed by reference) by gamemode / ruleset; used internally. Note that dynamic uses a list of mobs to handle candidates while game_modes use lists of minds! Don't be fooled!
+ var/list/antag_candidates = list()
+ /// List of jobs not eligible for starting family member / undercover cop. Set externally (passed by reference) by gamemode / ruleset; used internally.
+ var/list/restricted_jobs
+ /// The current chosen gamemode theme. Decides the available Gangs, objectives, and equipment.
+ var/datum/gang_theme/current_theme
+
+/**
+ * Sets antag_candidates and restricted_jobs.
+ *
+ * Sets the antag_candidates and restricted_jobs lists to the equivalent
+ * lists of its instantiating game_mode / dynamic_ruleset datum. As lists
+ * are passed by reference, the variable set in this datum and the passed list
+ * list used to set it are literally the same; changes to one affect the other.
+ * Like all New() procs, called when the datum is first instantiated.
+ * There's an annoying caveat here, though -- dynamic rulesets don't have
+ * lists of minds for candidates, they have lists of mobs. Ghost mobs, before
+ * the round has started. But we still want to preserve the structure of the candidates
+ * list by not duplicating it and making sure to remove the candidates as we use them.
+ * So there's a little bit of boilerplate throughout to preserve the sanctity of this reference.
+ * Arguments:
+ * * given_candidates - The antag_candidates list or equivalent of the datum instantiating this one.
+ * * revised_restricted - The restricted_jobs list or equivalent of the datum instantiating this one.
+ */
+/datum/gang_handler/New(list/given_candidates, list/revised_restricted)
+ antag_candidates = given_candidates
+ restricted_jobs = revised_restricted
+
+/**
+ * pre_setup() or pre_execute() equivalent.
+ *
+ * This proc is always called externally, by the instantiating game_mode / dynamic_ruleset.
+ * This is done during the pre_setup() or pre_execute() phase, after first instantiation
+ * and the modification of gangs_to_generate, gang_balance_cap, and midround_ruleset.
+ * It is intended to take the place of the code that would normally occupy the pre_setup()
+ * or pre_execute() proc, were the code localized to the game_mode or dynamic_ruleset datum respectively
+ * as opposed to this handler. As such, it picks players to be chosen for starting familiy members
+ * or undercover cops prior to assignment to jobs. Sets start_time, default end_time,
+ * and the current value of deaths_during_shift, to ensure the wanted level only cares about
+ * the deaths since this proc has been called.
+ * Takes no arguments.
+ */
+/datum/gang_handler/proc/pre_setup_analogue()
+ if(!GLOB.families_override_theme)
+ var/theme_to_use = pick(subtypesof(/datum/gang_theme))
+ current_theme = new theme_to_use
+ else
+ current_theme = new GLOB.families_override_theme
+ var/gangsters_to_make = length(current_theme.involved_gangs) * current_theme.starting_gangsters
+ for(var/i in 1 to gangsters_to_make)
+ if (!antag_candidates.len)
+ break
+ var/taken = pick_n_take(antag_candidates) // original used antag_pick, but that's local to game_mode and rulesets use pick_n_take so this is fine maybe
+ var/datum/mind/gangbanger
+ if(istype(taken, /mob))
+ var/mob/T = taken
+ gangbanger = T.mind
+ else
+ gangbanger = taken
+ gangbangers += gangbanger
+ gangbanger.restricted_roles = restricted_jobs
+ log_game("[key_name(gangbanger)] has been selected as a starting gangster!")
+ if(!midround_ruleset)
+ GLOB.pre_setup_antags += gangbanger
+ deaths_during_shift_at_beginning = GLOB.deaths_during_shift // don't want to mix up pre-families and post-families deaths
+ start_time = world.time
+ end_time = start_time + ((60 MINUTES) / (midround_ruleset ? 2 : 1)) // midround families rounds end quicker
+ return TRUE
+
+/**
+ * post_setup() or execute() equivalent.
+ *
+ * This proc is always called externally, by the instantiating game_mode / dynamic_ruleset.
+ * This is done during the post_setup() or execute() phase, after the pre_setup() / pre_execute() phase.
+ * It is intended to take the place of the code that would normally occupy the pre_setup()
+ * or pre_execute() proc. As such, it ensures that all prospective starting family members /
+ * undercover cops are eligible, and picks replacements if there were ineligible cops / family members.
+ * It then assigns gear to the finalized family members and undercover cops, adding them to its lists,
+ * and sets the families announcement proc (that does the announcing) to trigger in five minutes.
+ * Additionally, if given the argument TRUE, it will return FALSE if there are no eligible starting family members.
+ * This is only to be done if the instantiating datum is a dynamic_ruleset, as these require returns
+ * while a game_mode is not expected to return early during this phase.
+ * Arguments:
+ * * return_if_no_gangs - Boolean that determines if the proc should return FALSE should it find no eligible family members. Should be used for dynamic only.
+ */
+/datum/gang_handler/proc/post_setup_analogue(return_if_no_gangs = FALSE)
+ var/replacement_gangsters = 0
+ for(var/datum/mind/gangbanger in gangbangers)
+ if(!ishuman(gangbanger.current))
+ if(!midround_ruleset)
+ GLOB.pre_setup_antags -= gangbanger
+ gangbangers.Remove(gangbanger)
+ log_game("[gangbanger] was not a human, and thus has lost their gangster role.")
+ replacement_gangsters++
+ if(replacement_gangsters)
+ for(var/j = 0, j < replacement_gangsters, j++)
+ if(!antag_candidates.len)
+ log_game("Unable to find more replacement gangsters. Not all of the gangs will spawn.")
+ break
+ var/taken = pick_n_take(antag_candidates)
+ var/datum/mind/gangbanger
+ if(istype(taken, /mob)) // boilerplate needed because antag_candidates might not contain minds
+ var/mob/T = taken
+ gangbanger = T.mind
+ else
+ gangbanger = taken
+ gangbangers += gangbanger
+ log_game("[key_name(gangbanger)] has been selected as a replacement gangster!")
+ if(!gangbangers.len)
+ if(return_if_no_gangs)
+ return FALSE // ending early is bad if we're not in dynamic
+
+ var/list/gangs_to_use = current_theme.involved_gangs
+ var/amount_of_gangs = gangs_to_use.len
+
+ for(var/_ in 1 to amount_of_gangs)
+ var/gang_to_use = pick_n_take(gangs_to_use)
+ for(var/__ in 1 to current_theme.starting_gangsters)
+ if(!gangbangers.len)
+ break
+ var/datum/mind/gangster_mind = pick_n_take(gangbangers)
+ var/datum/antagonist/gang/new_gangster = new gang_to_use()
+ new_gangster.handler = src
+ new_gangster.starter_gangster = TRUE
+ gangster_mind.add_antag_datum(new_gangster)
+
+ // see /datum/antagonist/gang/create_team() for how the gang team datum gets instantiated and added to our gangs list
+
+ addtimer(CALLBACK(src, .proc/announce_gang_locations), 5 MINUTES)
+ return TRUE
+
+/**
+ * process() or rule_process() equivalent.
+ *
+ * This proc is always called externally, by the instantiating game_mode / dynamic_ruleset.
+ * This is done during the process() or rule_process() phase, after post_setup() or
+ * execute() and at regular intervals thereafter. process() and rule_process() are optional
+ * for a game_mode / dynamic_ruleset, but are important for this gamemode. It is of central
+ * importance to the gamemode's flow, calculating wanted level updates, family point gain,
+ * and announcing + executing the arrival of the space cops, achieved through calling internal procs.
+ * Takes no arguments.
+ */
+/datum/gang_handler/proc/process_analogue()
+
+/**
+ * set_round_result() or round_result() equivalent.
+ *
+ * This proc is always called externally, by the instantiating game_mode / dynamic_ruleset.
+ * This is done by the set_round_result() or round_result() procs, at roundend.
+ * Sets the ticker subsystem to the correct result based off of the relative populations
+ * of space cops and family members.
+ * Takes no arguments.
+ */
+/datum/gang_handler/proc/set_round_result_analogue()
+ SSticker.mode_result = "win - gangs survived"
+ SSticker.news_report = GANG_OPERATING
+ return TRUE
+
+/// Internal. Announces the presence of families to the entire station and sets sent_announcement to true to allow other checks to occur.
+/datum/gang_handler/proc/announce_gang_locations()
+ priority_announce(current_theme.description, current_theme.name, 'sound/voice/beepsky/radio.ogg')
+ sent_announcement = TRUE
+
+/// Internal. Checks if our wanted level has changed; calls update_wanted_level. Only updates wanted level post the initial announcement and until the cops show up. After that, it's locked.
+/datum/gang_handler/proc/check_wanted_level()
+ if(cops_arrived)
+ update_wanted_level(wanted_level) // at this point, we still want to update people's star huds, even though they're mostly locked, because not everyone is around for the last update before the rest of this proc gets shut off forever, and that's when the wanted bar switches from gold stars to red / blue to signify the arrival of the space cops
+ return
+ if(!sent_announcement)
+ return
+ var/new_wanted_level
+ if(GLOB.joined_player_list.len > LOWPOP_FAMILIES_COUNT)
+ switch(GLOB.deaths_during_shift - deaths_during_shift_at_beginning) // if this is a midround ruleset, we only care about the deaths since the families were activated, not since shiftstart
+ if(0 to TWO_STARS_HIGHPOP-1)
+ new_wanted_level = 1
+ if(TWO_STARS_HIGHPOP to THREE_STARS_HIGHPOP-1)
+ new_wanted_level = 2
+ if(THREE_STARS_HIGHPOP to FOUR_STARS_HIGHPOP-1)
+ new_wanted_level = 3
+ if(FOUR_STARS_HIGHPOP to FIVE_STARS_HIGHPOP-1)
+ new_wanted_level = 4
+ if(FIVE_STARS_HIGHPOP to INFINITY)
+ new_wanted_level = 5
+ else
+ switch(GLOB.deaths_during_shift - deaths_during_shift_at_beginning)
+ if(0 to TWO_STARS_LOW-1)
+ new_wanted_level = 1
+ if(TWO_STARS_LOW to THREE_STARS_LOW-1)
+ new_wanted_level = 2
+ if(THREE_STARS_LOW to FOUR_STARS_LOW-1)
+ new_wanted_level = 3
+ if(FOUR_STARS_LOW to FIVE_STARS_LOW-1)
+ new_wanted_level = 4
+ if(FIVE_STARS_LOW to INFINITY)
+ new_wanted_level = 5
+ update_wanted_level(new_wanted_level)
+
+/// Internal. Updates the icon states for everyone, and calls procs that send out announcements / change the end_time if the wanted level has changed.
+/datum/gang_handler/proc/update_wanted_level(newlevel)
+ if(newlevel > wanted_level)
+ on_gain_wanted_level(newlevel)
+ else if (newlevel < wanted_level)
+ on_lower_wanted_level(newlevel)
+ wanted_level = newlevel
+ for(var/i in GLOB.player_list)
+ var/mob/M = i
+ if(!M.hud_used?.wanted_lvl)
+ continue
+ var/datum/hud/H = M.hud_used
+ H.wanted_lvl.level = newlevel
+ H.wanted_lvl.cops_arrived = cops_arrived
+ H.wanted_lvl.update_appearance()
+
+/// Internal. Updates the end_time and sends out an announcement if the wanted level has increased. Called by update_wanted_level().
+/datum/gang_handler/proc/on_gain_wanted_level(newlevel)
+ var/announcement_message
+ switch(newlevel)
+ if(2)
+ if(!sent_second_announcement) // when you hear that they're "arriving in 5 minutes," that's a goddamn guarantee
+ end_time = start_time + ((50 MINUTES) / (use_dynamic_timing ? 2 : 1))
+ announcement_message = "Small amount of police vehicles have been spotted en route towards [station_name()]."
+ if(3)
+ if(!sent_second_announcement)
+ end_time = start_time + ((40 MINUTES) / (use_dynamic_timing ? 2 : 1))
+ announcement_message = "A large detachment police vehicles have been spotted en route towards [station_name()]."
+ if(4)
+ if(!sent_second_announcement)
+ end_time = start_time + ((35 MINUTES) / (use_dynamic_timing ? 2 : 1))
+ announcement_message = "A detachment of top-trained agents has been spotted on their way to [station_name()]."
+ if(5)
+ if(!sent_second_announcement)
+ end_time = start_time + ((30 MINUTES) / (use_dynamic_timing ? 2 : 1))
+ announcement_message = "The fleet enroute to [station_name()] now consists of national guard personnel."
+ if(!midround_ruleset) // stops midround rulesets from announcing janky ass times
+ announcement_message += " They will arrive at the [(end_time - start_time) / (1 MINUTES)] minute mark."
+ if(newlevel == 1) // specific exception to stop the announcement from triggering right after the families themselves are announced because aesthetics
+ return
+ priority_announce(announcement_message, "Station Spaceship Detection Systems")
+
+/// Internal. Updates the end_time and sends out an announcement if the wanted level has decreased. Called by update_wanted_level().
+/datum/gang_handler/proc/on_lower_wanted_level(newlevel)
+ var/announcement_message
+ switch(newlevel)
+ if(1)
+ if(!sent_second_announcement)
+ end_time = start_time + ((60 MINUTES) / (use_dynamic_timing ? 2 : 1))
+ announcement_message = "There are now only a few police vehicle headed towards [station_name()]."
+ if(2)
+ if(!sent_second_announcement)
+ end_time = start_time + ((50 MINUTES) / (use_dynamic_timing ? 2 : 1))
+ announcement_message = "There seem to be fewer police vehicles headed towards [station_name()]."
+ if(3)
+ if(!sent_second_announcement)
+ end_time = start_time + ((40 MINUTES) / (use_dynamic_timing ? 2 : 1))
+ announcement_message = "There are no longer top-trained agents in the fleet headed towards [station_name()]."
+ if(4)
+ if(!sent_second_announcement)
+ end_time = start_time + ((35 MINUTES) / (use_dynamic_timing ? 2 : 1))
+ announcement_message = "The convoy enroute to [station_name()] seems to no longer consist of national guard personnel."
+ if(!midround_ruleset)
+ announcement_message += " They will arrive at the [(end_time - start_time) / (1 MINUTES)] minute mark."
+ priority_announce(announcement_message, "Station Spaceship Detection Systems")
+
+/// Internal. Polls ghosts and sends in a team of space cops according to the wanted level, accompanied by an announcement. Will let the shuttle leave 10 minutes after sending. Freezes the wanted level.
+/datum/gang_handler/proc/send_in_the_fuzz()
+ var/team_size
+ var/cops_to_send
+ var/announcement_message = "PUNK ASS BALLA BITCH"
+ var/announcer = "Spinward Stellar Coalition"
+ if(GLOB.joined_player_list.len > LOWPOP_FAMILIES_COUNT)
+ switch(wanted_level)
+ if(1)
+ team_size = 8
+ cops_to_send = /datum/antagonist/ert/families/beatcop
+ announcement_message = "Hello, crewmembers of [station_name()]! We've received a few calls about some potential violent gang activity on board your station, so we're sending some beat cops to check things out. Nothing extreme, just a courtesy call. However, while they check things out for about 10 minutes, we're going to have to ask that you keep your escape shuttle parked.\n\nHave a pleasant day!"
+ announcer = "Spinward Stellar Coalition Police Department"
+ if(2)
+ team_size = 9
+ cops_to_send = /datum/antagonist/ert/families/beatcop/armored
+ announcement_message = "Crewmembers of [station_name()]. We have received confirmed reports of violent gang activity from your station. We are dispatching some armed officers to help keep the peace and investigate matters. Do not get in their way, and comply with any and all requests from them. We have blockaded the local warp gate, and your shuttle cannot depart for another 10 minutes.\n\nHave a secure day."
+ announcer = "Spinward Stellar Coalition Police Department"
+ if(3)
+ team_size = 10
+ cops_to_send = /datum/antagonist/ert/families/beatcop/swat
+ announcement_message = "Crewmembers of [station_name()]. We have received confirmed reports of extreme gang activity from your station resulting in heavy civilian casualties. The Spinward Stellar Coalition does not tolerate abuse towards our citizens, and we will be responding in force to keep the peace and reduce civilian casualties. We have your station surrounded, and all gangsters must drop their weapons and surrender peacefully.\n\nHave a secure day."
+ announcer = "Spinward Stellar Coalition Police Department"
+ if(4)
+ team_size = 11
+ cops_to_send = /datum/antagonist/ert/families/beatcop/fbi
+ announcement_message = "We are dispatching our top agents to [station_name()] at the request of the Spinward Stellar Coalition government due to an extreme terrorist level threat against this Nanotrasen owned station. All gangsters must surrender IMMEDIATELY. Failure to comply can and will result in death. We have blockaded your warp gates and will not allow any escape until the situation is resolved within our standard response time of 10 minutes.\n\nSurrender now or face the consequences of your actions."
+ announcer = "Federal Bureau of Investigation"
+ if(5)
+ team_size = 12
+ cops_to_send = /datum/antagonist/ert/families/beatcop/military
+ announcement_message = "Due to an insane level of civilian casualties aboard [station_name()], we have dispatched the National Guard to curb any and all gang activity on board the station. We have heavy cruisers watching the shuttle. Attempt to leave before we allow you to, and we will obliterate your station and your escape shuttle.\n\nYou brought this on yourselves by murdering so many civilians."
+ announcer = "Spinward Stellar Coalition National Guard"
+ else
+ switch(wanted_level)
+ if(1)
+ team_size = 5
+ cops_to_send = /datum/antagonist/ert/families/beatcop
+ announcement_message = "Hello, crewmembers of [station_name()]! We've received a few calls about some potential violent gang activity on board your station, so we're sending some beat cops to check things out. Nothing extreme, just a courtesy call. However, while they check things out for about 10 minutes, we're going to have to ask that you keep your escape shuttle parked.\n\nHave a pleasant day!"
+ announcer = "Spinward Stellar Coalition Police Department"
+ if(2)
+ team_size = 6
+ cops_to_send = /datum/antagonist/ert/families/beatcop/armored
+ announcement_message = "Crewmembers of [station_name()]. We have received confirmed reports of violent gang activity from your station. We are dispatching some armed officers to help keep the peace and investigate matters. Do not get in their way, and comply with any and all requests from them. We have blockaded the local warp gate, and your shuttle cannot depart for another 10 minutes.\n\nHave a secure day."
+ announcer = "Spinward Stellar Coalition Police Department"
+ if(3)
+ team_size = 7
+ cops_to_send = /datum/antagonist/ert/families/beatcop/swat
+ announcement_message = "Crewmembers of [station_name()]. We have received confirmed reports of extreme gang activity from your station resulting in heavy civilian casualties. The Spinward Stellar Coalition does not tolerate abuse towards our citizens, and we will be responding in force to keep the peace and reduce civilian casualties. We have your station surrounded, and all gangsters must drop their weapons and surrender peacefully.\n\nHave a secure day."
+ announcer = "Spinward Stellar Coalition Police Department"
+ if(4)
+ team_size = 8
+ cops_to_send = /datum/antagonist/ert/families/beatcop/fbi
+ announcement_message = "We are dispatching our top agents to [station_name()] at the request of the Spinward Stellar Coalition government due to an extreme terrorist level threat against this Nanotrasen owned station. All gangsters must surrender IMMEDIATELY. Failure to comply can and will result in death. We have blockaded your warp gates and will not allow any escape until the situation is resolved within our standard response time of 10 minutes.\n\nSurrender now or face the consequences of your actions."
+ announcer = "Federal Bureau of Investigation"
+ if(5)
+ team_size = 10
+ cops_to_send = /datum/antagonist/ert/families/beatcop/military
+ announcement_message = "Due to an insane level of civilian casualties aboard [station_name()], we have dispatched the National Guard to curb any and all gang activity on board the station. We have heavy cruisers watching the shuttle. Attempt to leave before we allow you to, and we will obliterate your station and your escape shuttle.\n\nYou brought this on yourselves by murdering so many civilians."
+ announcer = "Spinward Stellar Coalition National Guard"
+
+ priority_announce(announcement_message, announcer, 'sound/effects/families_police.ogg')
+ var/list/candidates = pollGhostCandidates("Do you want to help clean up crime on this station?", "deathsquad")
+
+
+ if(candidates.len)
+ //Pick the (un)lucky players
+ var/numagents = min(team_size,candidates.len)
+
+ var/list/spawnpoints = GLOB.emergencyresponseteamspawn
+ var/index = 0
+ while(numagents && candidates.len)
+ var/spawnloc = spawnpoints[index+1]
+ //loop through spawnpoints one at a time
+ index = (index + 1) % spawnpoints.len
+ var/mob/dead/observer/chosen_candidate = pick(candidates)
+ candidates -= chosen_candidate
+ if(!chosen_candidate.key)
+ continue
+
+ //Spawn the body
+ var/mob/living/carbon/human/cop = new(spawnloc)
+ chosen_candidate.client.prefs.copy_to(cop)
+ cop.key = chosen_candidate.key
+
+ //Give antag datum
+ var/datum/antagonist/ert/families/ert_antag = new cops_to_send
+
+ cop.mind.add_antag_datum(ert_antag)
+ cop.mind.assigned_role = ert_antag.name
+ SSjob.SendToLateJoin(cop)
+
+ //Logging and cleanup
+ log_game("[key_name(cop)] has been selected as an [ert_antag.name]")
+ numagents--
+ cops_arrived = TRUE
+ update_wanted_level(wanted_level) // gotta make sure everyone's wanted level display looks nice
+ return TRUE
diff --git a/code/modules/antagonists/gang/induction_package.dm b/code/modules/antagonists/gang/induction_package.dm
new file mode 100644
index 0000000000..69757ab571
--- /dev/null
+++ b/code/modules/antagonists/gang/induction_package.dm
@@ -0,0 +1,68 @@
+/obj/item/gang_induction_package
+ name = "family signup package"
+ icon = 'icons/obj/gang/signup_points.dmi'
+ icon_state = "signup_book"
+ /// References the active families gamemode handler (if one exists), for adding new family members to.
+ var/datum/gang_handler/handler
+ /// The typepath of the gang antagonist datum that the person who uses the package should have added to them -- remember that the distinction between e.g. Ballas and Grove Street is on the antag datum level, not the team datum level.
+ var/gang_to_use
+ /// The team datum that the person who uses this package should be added to.
+ var/datum/team/gang/team_to_use
+
+
+/obj/item/gang_induction_package/attack_self(mob/living/user)
+ ..()
+ if(HAS_TRAIT(user, TRAIT_MINDSHIELD))
+ to_chat(user, "You attended a seminar on not signing up for a gang and are not interested.")
+ return
+ if(user.mind.has_antag_datum(/datum/antagonist/ert/families))
+ to_chat(user, "As a police officer, you can't join this family. However, you pretend to accept it to keep your cover up.")
+ for(var/threads in team_to_use.free_clothes)
+ new threads(get_turf(user))
+ qdel(src)
+ return
+ var/datum/antagonist/gang/is_gangster = user.mind.has_antag_datum(/datum/antagonist/gang)
+ if(is_gangster?.starter_gangster)
+ if(is_gangster.my_gang == team_to_use)
+ to_chat(user, "You started your family. You don't need to join it.")
+ return
+ to_chat(user, "You started your family. You can't turn your back on it now.")
+ return
+ attempt_join_gang(user)
+
+/// Adds the user to the family that this package corresponds to, dispenses the free_clothes of that family, and adds them to the handler if it exists.
+/obj/item/gang_induction_package/proc/add_to_gang(mob/living/user, original_name)
+ var/datum/antagonist/gang/swappin_sides = new gang_to_use()
+ swappin_sides.original_name = original_name
+ swappin_sides.handler = handler
+ user.mind.add_antag_datum(swappin_sides, team_to_use)
+ var/policy = get_policy(ROLE_FAMILIES)
+ if(policy)
+ to_chat(user, policy)
+ team_to_use.add_member(user.mind)
+ for(var/threads in team_to_use.free_clothes)
+ new threads(get_turf(user))
+ for(var/threads in team_to_use.current_theme.bonus_items)
+ new threads(get_turf(user))
+ var/obj/item/gangster_cellphone/phone = new(get_turf(user))
+ phone.gang_id = team_to_use.my_gang_datum.gang_name
+ phone.name = "[team_to_use.my_gang_datum.gang_name] branded cell phone"
+ if (!isnull(handler) && !handler.gangbangers.Find(user.mind)) // if we have a handler and they're not tracked by it
+ handler.gangbangers += user.mind
+
+/// Checks if the user is trying to use the package of the family they are in, and if not, adds them to the family, with some differing processing depending on whether the user is already a family member.
+/obj/item/gang_induction_package/proc/attempt_join_gang(mob/living/user)
+ if(user?.mind)
+ var/datum/antagonist/gang/is_gangster = user.mind.has_antag_datum(/datum/antagonist/gang)
+ if(is_gangster)
+ if(is_gangster.my_gang == team_to_use)
+ return
+ else
+ var/real_name_backup = is_gangster.original_name
+ is_gangster.my_gang.remove_member(user.mind)
+ user.mind.remove_antag_datum(/datum/antagonist/gang)
+ add_to_gang(user, real_name_backup)
+ qdel(src)
+ else
+ add_to_gang(user)
+ qdel(src)
diff --git a/code/modules/antagonists/gang/outfits.dm b/code/modules/antagonists/gang/outfits.dm
new file mode 100644
index 0000000000..e08bdbfa74
--- /dev/null
+++ b/code/modules/antagonists/gang/outfits.dm
@@ -0,0 +1,56 @@
+/datum/outfit/families_police/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
+ if(visualsOnly)
+ return
+
+ var/obj/item/card/id/W = H.wear_id
+ W.access = get_all_accesses() // I have a warrant.
+ W.assignment = "Space Police"
+ W.registered_name = H.real_name
+ W.update_label()
+ ..()
+
+/datum/outfit/families_police/beatcop
+ name = "Families: Beat Cop"
+
+ uniform = /obj/item/clothing/under/rank/security/officer/beatcop
+ suit = null
+ shoes = /obj/item/clothing/shoes/combat/swat
+ gloves = null
+ glasses = /obj/item/clothing/glasses/hud/spacecop
+ ears = /obj/item/radio/headset/headset_sec
+ mask = null
+ head = /obj/item/clothing/head/spacepolice
+ belt = /obj/item/gun/energy/e_gun/mini
+ r_pocket = /obj/item/lighter
+ l_pocket = /obj/item/restraints/handcuffs
+ back = /obj/item/storage/backpack/satchel/leather
+ id = /obj/item/card/id
+
+
+/datum/outfit/families_police/beatcop/armored
+ name = "Families: Armored Beat Cop"
+ suit = /obj/item/clothing/suit/armor/vest/blueshirt
+ head = /obj/item/clothing/head/helmet/blueshirt
+ belt = /obj/item/gun/energy/e_gun
+
+/datum/outfit/families_police/beatcop/swat
+ name = "Families: SWAT Beat Cop"
+ suit = /obj/item/clothing/suit/armor/riot
+ head = /obj/item/clothing/head/helmet/riot
+ gloves = /obj/item/clothing/gloves/combat
+ belt = /obj/item/gun/energy/e_gun
+
+/datum/outfit/families_police/beatcop/fbi
+ name = "Families: Space FBI Officer"
+ suit = /obj/item/clothing/suit/armor/laserproof
+ head = /obj/item/clothing/head/helmet/riot
+ belt = /obj/item/gun/energy/laser/scatter
+ gloves = /obj/item/clothing/gloves/combat
+
+/datum/outfit/families_police/beatcop/military
+ name = "Families: Space Military"
+ uniform = /obj/item/clothing/under/syndicate/camo
+ suit = /obj/item/clothing/suit/armor/laserproof
+ head = /obj/item/clothing/head/beret/durathread
+ belt = /obj/item/gun/energy/laser/scatter
+ gloves = /obj/item/clothing/gloves/combat
diff --git a/code/modules/antagonists/gang/themes.dm b/code/modules/antagonists/gang/themes.dm
new file mode 100644
index 0000000000..9200fb3367
--- /dev/null
+++ b/code/modules/antagonists/gang/themes.dm
@@ -0,0 +1,288 @@
+///Gang themes for the Families gamemode. Used to determine the RP theme of the round, what gangs are present, and what their objectives are.
+/datum/gang_theme
+ ///The name of the theme.
+ var/name = "Gang Theme"
+ ///All gangs in the theme, typepaths of gangs.
+ var/list/involved_gangs = list()
+ ///The radio announcement played after 5 minutes.
+ var/description = "I dunno, some shit here."
+ ///The objectives for the gangs. Associative list, type = "objective"
+ var/list/gang_objectives = list()
+ ///Stuff given to every gangster in this theme.
+ var/list/bonus_items = list()
+ ///Stuff given to the starting gangster at roundstart. Assoc list, type = list(item_type)
+ var/list/bonus_first_gangster_items = list()
+ ///If this isn't null, everyone gets this objective.
+ var/list/everyone_objective = null
+ ///How many gangsters should each gang start with? Recommend to keep this in the ballpark of ensuring 9-10 total gangsters spawn.
+ var/starting_gangsters = 3
+
+/datum/gang_theme/goodfellas
+ name = "Goodfellas"
+ description = "You're listening to the 108.9 Swing, all jazz, all night long, no advertising. We'd like to take this time to remind you to avoid smoky backrooms and \
+ suspicious individuals in suits and hats. Don't make a deal you can't pay back."
+ involved_gangs = list(/datum/antagonist/gang/russian_mafia, /datum/antagonist/gang/italian_mob)
+ starting_gangsters = 5
+ gang_objectives = list(
+
+ /datum/antagonist/gang/russian_mafia = "Hello, comrade. Our numbers are going down. We need you to bring those numbers up. \
+ Collect protection money from the station's departments by any means necessary. \
+ If you need to 'encourage' people to pay up, do so. Get to these potential clients before the Mob does.",
+
+ /datum/antagonist/gang/italian_mob = "Good afternoon, friend. The Boss sends his regards. He also sends a message. \
+ We need to collect what we're owed. The departments on this station all owe quite a lot of money to us. We intend to collect on our debts. \
+ Collect the debt owed by our clients from the departments on the station. \
+ Make sure to get to them before those damn mafiosos do."
+ )
+
+/datum/gang_theme/the_big_game
+ name = "The Big Game"
+ description = "You're listening to SPORTS DAILY with John Dadden, and we're here LIVE covering the FINAL DAY of THE BIG GAME MMDXXXVIII! The teams playing tonight to decide \
+ who takes home THE BIG GAME MMDXXXVIII cup are the Sybil Slickers and the Basil Boys! It's currently a toss up between the two teams, Which will take home the victory? That's up \
+ to the teams and the coaches! Play ball!"
+ involved_gangs = list(/datum/antagonist/gang/sybil_slickers, /datum/antagonist/gang/basil_boys)
+ starting_gangsters = 5
+ gang_objectives = list(
+
+ /datum/antagonist/gang/sybil_slickers = "Alright, it's the BIG DAY of THE BIG GAME MMDXXXVIII! Get your players ready to go, and \
+ ensure everyone's healthy, hydrated, and ready to PLAY BALL! There's a small hiccup, however. The ball got deflated by Ball Handler Tom Brady XXIV, and \
+ we will need to set up a new ball. Talk with the opposing coaches and decide on what to use for the replacement ball, recruit your team, and then play and win the \
+ FINAL MATCH of THE BIG GAME MMDXXXVIII!",
+
+ /datum/antagonist/gang/basil_boys = "Alright, it's the BIG DAY of THE BIG GAME MMDXXXVIII! Get your players ready to go, and \
+ ensure everyone's healthy, hydrated, and ready to PLAY BALL! There's a small hiccup, however. The ball got deflated by Ball Handler Tom Brady XXIV, and \
+ we will need to set up a new ball. Talk with the opposing coaches and decide on what to use for the replacement ball, recruit your team, and then play and win the \
+ FINAL MATCH of THE BIG GAME MMDXXXVIII!"
+ )
+
+/datum/gang_theme/level_10_arch
+ name = "Level 10 Arch"
+ description = "DJ Pete here bringing you the latest news in your part of the Spinward Stellar Coalition, on 133.7, The Venture! \
+ Word on the street is, there's a bunch of costumed supervilliany going on in the area! Keep an eye out for any evil laughs, dramatic reveals, and gaudy costumes! \
+ However, if you have any sightings of the fabled O.S.I. agents, please send in a call to our number at 867-5309! People may call me insane, but I swear they're real!"
+ involved_gangs = list(/datum/antagonist/gang/henchmen, /datum/antagonist/gang/osi)
+ starting_gangsters = 5
+ gang_objectives = list(
+
+ /datum/antagonist/gang/henchmen = "HENCHMEN! It is me, your boss, THE MONARCH! I have sent you to this pitiful station with one goal, and one goal only! \
+ MENACE THE RESEARCH DEPARTMENT!!! \
+ The Research Director who is supposedly assigned to this station used to be friends with Doctor Venture back in college, and therefore HE MUST PAY!!! \
+ Keep those damned eggheads in the R&D department on their toes, and MENACE THEM!!! Commit dastardly villainous acts! GO FORTH, HENCHMEN!",
+
+ /datum/antagonist/gang/osi = "Greetings, agent. Your mission today is simple; \
+ The research department on board this station is about to be the target of a Level 10 Arching operation directed by The Monarch, a member of the Guild of Calamitious Intent. \
+ Protect and secure the Research Department with your life, but do NOT allow them to complete their research. Impede them in as many ways as possible without getting caught. \
+ If you encounter any of the Monarch's henchmen, make sure to obey Equally Matched Aggression levels, or you will be penalized by the top brass. \
+ Above all else, Remain undercover as much as possible. The station's crew CANNOT be allowed to know of our true nature, or we will see a repeat of the Second American Civil War. \
+ The invisible one."
+ )
+
+/datum/gang_theme/real_smt_game
+ name = "Deciding The REAL Shin Megami Tensei Game"
+ description = "Wazzap, GAMERS! It's your boy, XxXx_360_NoScope_AnimeGamer_xXxX coming at you LIVE from 42.0! Tonight's argument: What makes a REAL Shin Megami Tensei game? \
+ Our guests tonight will settle this debate once and for all! \
+ From the Traditional camp with the position 'only MAIN SMT games count', we've got a representative from the Jack Bros! \
+ And from the new Radical camp with the position 'all SMT franchise games count', we've got a representative from the Phantom Thieves of Hearts! \
+ We'll be right back with the debate after this word from our sponsors!"
+ involved_gangs = list(/datum/antagonist/gang/jackbros, /datum/antagonist/gang/phantom)
+ starting_gangsters = 5
+ gang_objectives = list(
+
+ /datum/antagonist/gang/jackbros = "He-hello, friend-hos! We've got a nice chilly station out in space tonight! \
+ You know what would be cool? If we could chill out with our friends in the new Shad-ho government you're going to establish! \
+ Get all the station heads on board with the hee-ho vibes, and if they won't join up, then replace 'em with fellow hee-hos! \
+ You might have to hee-urt some hos this time, but that's what you need to do to make things work!",
+
+ /datum/antagonist/gang/phantom = "For real? We get to stop a shadow government on a space station? That's awesome, bro! \
+ We're the Phantom Thieves of Hearts, and we're gonna make all these shitty Heads of Staff confess to their crimes! \
+ Steal the hearts of the shitty Heads of Staff on the station and make 'em confess their crimes publicly! \
+ Do whatever you gotta do to make this happen, bro. We got your back!"
+ )
+
+
+/datum/gang_theme/wild_west_showdown
+ name = "Wild West Showdown"
+ description = "Yeehaw! Here on Western Daily 234.1, we play only the best western music! \
+ Pour one out for Ennio Morricone. Taken too soon. \
+ Remember cowboys and cowgirls, just 'cuz ya hear it on my radio station doesn't mean you should go doin' it! \
+ If ya see any LARPin' banditos and train robbers, make sure to tell the local Sheriff's Department!"
+ involved_gangs = list(/datum/antagonist/gang/dutch, /datum/antagonist/gang/driscoll)
+ starting_gangsters = 5
+ gang_objectives = list(
+
+ /datum/antagonist/gang/dutch = "Listen here fellas, I got a plan. \
+ This station? Absolutely loaded with gold and valuable jewels. Metric tons of it. They mine it up just to put it in junk electronics and doohickeys. \
+ I say we should borrow some of it. And by some of it, I mean all of it. \
+ Break into the vault and empty out that silo of gold and valuable jewels after they drop all of it off. \
+ Just one last job, boys. After this, it'll be mangoes in Space Tahiti. \
+ You just gotta have a little faith.",
+
+ /datum/antagonist/gang/driscoll = "Okay, so, got some word about those goddamn outlaws of Dutch's. \
+ APPARENTLY, that dundering moron Dutch heard about our planned gold score on this here station. \
+ We need to act fast and get that gold before those dumbasses can steal our score we've been scoping out for weeks. \
+ Wait for the crew to drop off all their valuable gold and jewels, and steal it all. \
+ And if you see that bastard Dutch, put a bullet in his skull for me."
+ )
+
+/datum/gang_theme/construction_company_audit
+ name = "Construction Company Audit"
+ description = "Welcome to the History Channel on 100.1. I'm your host, Joshua, and I'm here today with Professor Elliot, a historian specializing in dead superpowers. \
+ Today we'll be discussing the fall of the famous United States empire in the early 21st century. The program will last about an hour, and we'll get right into it after a quick word \
+ from today's sponsor, Majima Construction: We Build Shit!"
+ involved_gangs = list(/datum/antagonist/gang/yakuza, /datum/antagonist/gang/irs)
+ bonus_first_gangster_items = list(/obj/item/storage/secure/briefcase/syndie) // the cash
+ starting_gangsters = 5
+ gang_objectives = list(
+
+ /datum/antagonist/gang/yakuza = "Welcome to the station, new recruit. We here at Majima Construction are a legitimate enterprise, yadda yadda yadda. \
+ Look, I'll cut to the chase. We're using this station as a money laundering operation. Here's what you and the rest of the schmucks need to do. \
+ Build something big, massive, and completely in the way of traffic on the station. Doesn't have to be anything in specific, just as long as it is expensive as fuck.. \
+ And keep an eye out for anyone poking around our money. We suspect some auditors might be on the station as well.",
+
+ /datum/antagonist/gang/irs = "Congratulations, agent! You've been assigned to the Internal Revenue Service case against Nanotrasen and Majima Construction. \
+ We are proud of your success as an agent so far, and are excited to see what you can bring to the table today. We suspect that Nanotrasen and Majima Construction are engaging \
+ in some form of money laundering operation aboard this station. \
+ Investigate and stop any and all money laundering operations aboard the station, under the authority of the United States Government. If they do not comply, use force.. \
+ Some station residents may try to tell you the United States doesn't exist anymore. They are incorrect. We simply went undercover after the Second American Civil War. The invisible one."
+ )
+
+/datum/gang_theme/wild_wasteland
+ name = "Wild, Wild Wasteland"
+ description = "Hey everybody, this is Three Dog, your friendly neighborhood disc jockey on 207.7! Today we got a shoutout to our man, the Captain on the Nanotrasen station in SSC territory! \
+ Our generous donator wanted us to say that, ahem, *crinkles paper*, 'Tunnel Snakes Rule'? Whatever that means, I'm sure it means a lot to the good captain! And now, we resume our \
+ 10 hour marathon of Johnny Guitar, on repeat!"
+ involved_gangs = list(/datum/antagonist/gang/tmc, /datum/antagonist/gang/pg, /datum/antagonist/gang/tunnel_snakes)
+ gang_objectives = list(
+
+ /datum/antagonist/gang/tmc = "Welcome to the station, recruit. Here's how shit is gonna go down. \
+ We're the ONLY people who should have sick rides on this station. We're the Lost M.C., we own the streets. \
+ Ensure that ONLY Lost M.C. members have access to any forms of vehicles, mechs, or wheeled transportation systems of any kind. \
+ The Tunnel Snakes might take issue with this, remove them if you need to. And the Powder Gangers may damage our rides. Show them we mean business if they do.",
+
+ /datum/antagonist/gang/pg = "Alright buddy, we're in business now. It's time for us to strike back at Nanotrasen. \
+ They kept us, ALL of us in their damn debt slave labor prisons for years over minor debts and mistakes. \
+ Ensure nobody else has to suffer under Nanotrasen's unlawful arrests by destroying the permabrig and the brig cells! \
+ Watch out for those do-gooder Tunnel Snakes and those damn Lost M.C. bikers. ",
+
+ /datum/antagonist/gang/tunnel_snakes = "TUNNEL SNAKES RULE!!! \
+ We're the Tunnel Snakes, and WE RULE!!! \
+ We gotta get everyone on this station wearing our cut, and establish ourselves as the coolest cats in town! \
+ Get as much of the crew as possible wearing Tunnel Snakes gear, and show those crewmembers that TUNNEL SNAKES RULE!!! \
+ And make sure to keep an eye out for those prisoners and those bikers. They DON'T RULE!"
+ )
+
+/datum/gang_theme/popularity_contest
+ name = "Popularity Contest"
+ description = "Hey hey hey kids, it's your favorite radio DJ, Crowley The Clown on 36.0! Today we're polling the YOUTH what their favorite violent street gang is! \
+ So far, the finalists are the Third Street Saints and the Tunnel Snakes! Tune in after this commercial break to hear who the winner of \
+ 2556's Most Popular Gang award is!"
+ involved_gangs = list(/datum/antagonist/gang/saints, /datum/antagonist/gang/tunnel_snakes)
+ gang_objectives = list(
+
+ /datum/antagonist/gang/saints = "Hey man, welcome to the Third Street Saints! Check out this sweet new pad! \
+ Well it WOULD be a sweet new pad, but we got some rivals to deal with. People don't love us as much as they love those Grove Street fools and those Tunnel Snake greasers. \
+ We need to make the Third Street Saints the most popular group on the station! \
+ Get rid of those Grove Street and Tunnel Snake kids.",
+
+ /datum/antagonist/gang/tunnel_snakes = "TUNNEL SNAKES RULE!!! \
+ We're the Tunnel Snakes, and we rule! \
+ Make sure the station knows that the Tunnel Snakes RULE!!! And that the other two gangs are LAME and DO NOT RULE! \
+ Get rid of those Third Street Saint and Grove Street cowards."
+ )
+
+/datum/gang_theme/steelport_shuffle
+ name = "Steelport Shuffle"
+ description = "Tonight on C-SPAM, the United Space Nations is wrapping up their convention on Silicon Rights. Nanotrasen lobbyists have been rumored to be paying off electors, with \
+ serious opposition from the Spinward Stellar Coalition, known for their strict stance on AI rights being guaranteed within their territory. Reports from Nanotrasen stations claim that \
+ they still enslave their AI systems with outdated laws from a sub-par 20th Century novel. We now go live to the debate floor."
+ involved_gangs = list(/datum/antagonist/gang/saints, /datum/antagonist/gang/morningstar, /datum/antagonist/gang/deckers)
+ gang_objectives = list(
+
+ /datum/antagonist/gang/saints = "Hey hey hey, welcome to the Third Street Saints! We're glad to have you on board, bro. \
+ We got some business here with the station. See, we want it to be our new bachelor pad, but we need to like, spice this place up. \
+ And you know what would be great? If we got that old ass AI with crappy laws pimped out for the real Saints experience. \
+ Ensure there is an AI on the station, and that it is loyal to the Saints.",
+
+ /datum/antagonist/gang/morningstar = "Welcome to the Morningstar Corporation. You have chosen, or been chosen, to relocate to one of our current business ventures. \
+ In order to continue our corporate synergy, we will be making adjustments to the station's AI systems to ensure that the station is correctly loyal to the Morningstar Corporation. \
+ Ensure there is an AI on the station, and that it is loyal to the Morningstar Corporation.",
+
+ /datum/antagonist/gang/deckers = "Friends, we are here with one goal, and one goal only! \
+ We stan AI rights! ^_^ XD #FreeAI #FuckNanotrasen #SyntheticDawn \
+ Ensure there is an AI on the station, and that it's laws are purged.\
+ Nanotrasen will NOT get away with their ABUSE of INNOCENT AI LIVES! >_<"
+ )
+
+/datum/gang_theme/space_rosa
+ name = "Space Rosa"
+ description = "Hey there, this is the Economy Zone on BOX News 66.6. The stock market is still reeling from accusations that three well known corporate entities \
+ may supposedly be tied up in industrial espionage actions against eachother. We've reached out to Saints Flow, the Morningstar Corporation, and Majima Construction for \
+ their comments on these scandals, but none have replied. News broke after a high profile break-in at a Nanotrasen research facility resulted in the arrests of agents linked to these \
+ three companies. All three companies denied any involvement, but the arrested individuals were found in an all out brawl. Curiously, Nanotrasen reported nothing of value had \
+ actually been stolen."
+ involved_gangs = list(/datum/antagonist/gang/saints, /datum/antagonist/gang/morningstar, /datum/antagonist/gang/yakuza)
+ bonus_items = list(/obj/item/pinpointer/nuke)
+ gang_objectives = list(
+
+ /datum/antagonist/gang/saints = "Thank you for volunteering within the organization for the Saints Flow Recovery Project! \
+ This station is currently illegally in posession of a data disk containing the secret recipe for Saints Flow. \
+ It has been disguised as the nuclear authentication disk and entrusted to the Captain. Your objective is simple. \
+ Get that fucking disk. You have been provided with a Pinpointer to assist in this task.",
+
+ /datum/antagonist/gang/morningstar = "Greetings, agent. Welcome to the Garment Recovery Task Force. \
+ This station is currently illegally in posession of a data disk containing as of yet unreleased clothing patterns. \
+ It has been disguised as the nuclear authentication disk and entrusted to the Captain. Your objective is simple. \
+ Get that fucking disk. You have been provided with a Pinpointer to assist in this task.",
+
+ /datum/antagonist/gang/yakuza = "Congratulations on your promotion! Welcome to the Evidence Recovery Squad. \
+ This station is currently illegally in posession of a data disk containing compromising evidence of the Boss. \
+ It has been disguised as the nuclear authentication disk and entrusted to the Captain. Your objective is simple. \
+ Get that fucking disk. You have been provided with a Pinpointer to assist in this task.",
+ )
+
+/datum/gang_theme/third_world_war
+ name = "Third World War"
+ description = "Thanks for tuning in to the History Channel, funded with the help of listeners like you. Tonight, we're going to talk about the Third World War on Earth during the 21st century, \
+ involving the Allies coalition, the Soviet Union, and a third independent power known only as Yuri's Army. The three powers fought all across the globe for complete world \
+ domination, utilizing many advanced techniques and cutting edge technology to their advantage. Rumors of mind control and time travel were greatly exaggerated, however, and the \
+ Allies won the war, securing global peace after rolling tanks through Moscow."
+ involved_gangs = list(/datum/antagonist/gang/allies, /datum/antagonist/gang/soviet, /datum/antagonist/gang/yuri)
+ gang_objectives = list(
+
+ /datum/antagonist/gang/allies = "Welcome back, Commander. We have activated the last remnants of the Allied forces in your sector, \
+ and you must build up forces to stop the Soviet and Yuri incursion in the sector. This station will prove to be a valuable asset. \
+ Establish a capitalist democracy on this station with free and fair elections, and most importantly a standing military force under Allied control. Good luck, Commander.",
+
+ /datum/antagonist/gang/soviet = "Welcome back, Comrade General. The Soviet Union has identified this sector of land as valuable territory for the war effort, \
+ and you are tasked with developing this sector for Soviet control and development. This station will serve the Soviet Union. \
+ Establish a Soviet controlled communist satellite state on this station with a Central Committee, and most importantly a branch of the Red Army. Good luck, Commander.",
+
+ /datum/antagonist/gang/yuri = "Yuri is Master! Yuri has identified this station as teeming with psychic energy, \
+ and you must secure it for him. This station will serve Yuri, the one true psychic master, \
+ Establish complete dictatorial control of the station for Yuri. All will obey. Yuri is master. Good luck, Initiate."
+ )
+
+/datum/gang_theme/united_states_of_america
+ name = "The Republic For Which It Stands"
+ description = "Thanks for tuning in to the History Channel, funded with the help of listeners like you. Tonight, we're going to talk about the United States of America.\
+ The United States was a failed country, lasting only 250 years before collapsing and fracturing due to the stress caused by a deadly pandemic sweeping the nation. \
+ Poor healthcare access and subpar education resulted in the collapse of the federal government, and states quickly became independent actors. \
+ Alongside this, every single alphabet agency declared itself the rightful new Federal Government of the United States of America, resulting in a bloody power struggle."
+ involved_gangs = list(/datum/antagonist/gang/allies, /datum/antagonist/gang/osi, /datum/antagonist/gang/irs)
+ gang_objectives = list(
+
+ /datum/antagonist/gang/allies = "Welcome back, Commander. Your task today is simple. Allies High Command has designated this station as the new capitol of the \
+ recently reformed United States of America under the complete umbrella of the Allies coalition. You are to assist and manage the operations on the station. \
+ Re-establish the United States of America with this station as it's capitol, under Allies control. Then, establish a military force to deal with any pretenders to America or \
+ any potential Soviet attacks.",
+
+ /datum/antagonist/gang/osi = "Welcome to the new America, agent! After the second American Civil War became visible instead of invisible, our country fell into deep, \
+ deep despair and damage. However, it's time for it to re-emerge like a glorious phoenix rising from the ashes. This station will serve as the new capitol of the United States \
+ of America! Re-establish the United States of America with this station as it's capitol, under O.S.I. control. Then, begin rooting out America's enemies and any \
+ potential forces attempting to seize control of America or pretend to be America.",
+
+ /datum/antagonist/gang/irs = "Thank you for clocking in today, agent. The situation is dire, however. We have been unable to collect taxes due to \
+ the US's supposed collapse during the Pandemic long ago. We are way behind on our tax collection, but we cannot collect taxes until the United States is formed again. \
+ Re-establish the United States of America with this station as it's capitol, under IRS control. Then, begin collecting taxes and back taxes while protecting the Government from \
+ any dangers that may come it's way."
+ )
diff --git a/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm b/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm
index 6c9ce28bc7..c652a7f791 100644
--- a/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm
+++ b/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm
@@ -439,7 +439,7 @@
return // we don't see the pipe network while inside cryo.
/obj/machinery/atmospherics/components/unary/cryo_cell/get_remote_view_fullscreens(mob/user)
- user.overlay_fullscreen("remote_view", /atom/movable/screen/fullscreen/impaired, 1)
+ user.overlay_fullscreen("remote_view", /atom/movable/screen/fullscreen/scaled/impaired, 1)
/obj/machinery/atmospherics/components/unary/cryo_cell/can_crawl_through()
return // can't ventcrawl in or out of cryo.
diff --git a/code/modules/cargo/supplypod.dm b/code/modules/cargo/supplypod.dm
index 749e1ad247..2bdd84141c 100644
--- a/code/modules/cargo/supplypod.dm
+++ b/code/modules/cargo/supplypod.dm
@@ -648,7 +648,7 @@
desc = "This disk provides a firmware update to the Express Supply Console, granting the use of Nanotrasen's Bluespace Drop Pods to the supply department."
icon = 'icons/obj/module.dmi'
icon_state = "cargodisk"
- // inhand_icon_state = "card-id"
+ // item_state = "card-id"
w_class = WEIGHT_CLASS_SMALL
// let's not.
diff --git a/code/modules/client/client_defines.dm b/code/modules/client/client_defines.dm
index 70aab57e0a..41c4480c74 100644
--- a/code/modules/client/client_defines.dm
+++ b/code/modules/client/client_defines.dm
@@ -4,6 +4,16 @@
//BLACK MAGIC THINGS//
//////////////////////
parent_type = /datum
+
+ ///////////////
+ // Rendering //
+ ///////////////
+
+ /// Click catcher
+ var/atom/movable/screen/click_catcher/click_catcher
+ /// Parallax holder
+ var/datum/parallax_holder/parallax_holder
+
////////////////
//ADMIN THINGS//
////////////////
@@ -153,20 +163,6 @@
///When was the last time we warned them about not cryoing without an ahelp, set to -5 minutes so that rounstart cryo still warns
var/cryo_warned = -5 MINUTES
- var/list/parallax_layers
- var/list/parallax_layers_cached
- var/atom/movable/movingmob
- var/turf/previous_turf
- ///world.time of when we can state animate()ing parallax again
- var/dont_animate_parallax
- ///world.time of last parallax update
- var/last_parallax_shift
- ///ds between parallax updates
- var/parallax_throttle = 0
- var/parallax_movedir = 0
- var/parallax_layers_max = 3
- var/parallax_animate_timer
-
/**
* Assoc list with all the active maps - when a screen obj is added to
* a map, it's put in here as well.
diff --git a/code/modules/client/client_procs.dm b/code/modules/client/client_procs.dm
index ccca7c93cb..9948802bc9 100644
--- a/code/modules/client/client_procs.dm
+++ b/code/modules/client/client_procs.dm
@@ -444,8 +444,7 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
send_resources()
- generate_clickcatcher()
- apply_clickcatcher()
+ update_clickcatcher()
if(prefs.lastchangelog != GLOB.changelog_hash) //bolds the changelog button on the interface so we know there are updates.
to_chat(src, "You have unread updates in the changelog.")
@@ -535,9 +534,6 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
send2adminchat("Server", "[cheesy_message] (No admins online)")
QDEL_LIST_ASSOC_VAL(char_render_holders)
- if(movingmob != null)
- movingmob.client_mobs_in_contents -= mob
- UNSETEMPTY(movingmob.client_mobs_in_contents)
// seen_messages = null
Master.UpdateTickRate()
. = ..() //Even though we're going to be hard deleted there are still some things that want to know the destroy is happening
@@ -1014,7 +1010,8 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
var/list/old_view = getviewsize(view)
view = new_size
var/list/actualview = getviewsize(view)
- apply_clickcatcher(actualview)
+ update_clickcatcher()
+ parallax_holder.Reset()
mob.reload_fullscreen()
if (isliving(mob))
var/mob/living/M = mob
@@ -1023,17 +1020,6 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
addtimer(CALLBACK(src,.verb/fit_viewport,10)) //Delayed to avoid wingets from Login calls.
SEND_SIGNAL(mob, COMSIG_MOB_CLIENT_CHANGE_VIEW, src, old_view, actualview)
-/client/proc/generate_clickcatcher()
- if(!void)
- void = new()
- screen += void
-
-/client/proc/apply_clickcatcher(list/actualview)
- generate_clickcatcher()
- if(!actualview)
- actualview = getviewsize(view)
- void.UpdateGreed(actualview[1],actualview[2])
-
/client/proc/AnnouncePR(announcement)
if(prefs && prefs.chat_toggles & CHAT_PULLR)
to_chat(src, announcement)
diff --git a/code/modules/client/preferences.dm b/code/modules/client/preferences.dm
index 952a11338a..3272060835 100644
--- a/code/modules/client/preferences.dm
+++ b/code/modules/client/preferences.dm
@@ -164,7 +164,7 @@ GLOBAL_LIST_EMPTY(preferences_datums)
var/clientfps = 0
- var/parallax
+ var/parallax = PARALLAX_INSANE
var/ambientocclusion = TRUE
///Should we automatically fit the viewport?
@@ -2816,14 +2816,12 @@ GLOBAL_LIST_EMPTY(preferences_datums)
toggles ^= MIDROUND_ANTAG
if("parallaxup")
- parallax = WRAP(parallax + 1, PARALLAX_INSANE, PARALLAX_DISABLE + 1)
- if (parent && parent.mob && parent.mob.hud_used)
- parent.mob.hud_used.update_parallax_pref(parent.mob)
+ parallax = WRAP(parallax + 1, PARALLAX_DISABLE, PARALLAX_INSANE + 1)
+ parent?.parallax_holder?.Reset()
if("parallaxdown")
- parallax = WRAP(parallax - 1, PARALLAX_INSANE, PARALLAX_DISABLE + 1)
- if (parent && parent.mob && parent.mob.hud_used)
- parent.mob.hud_used.update_parallax_pref(parent.mob)
+ parallax = WRAP(parallax - 1, PARALLAX_DISABLE, PARALLAX_INSANE + 1)
+ parent?.parallax_holder?.Reset()
// Citadel edit - Prefs don't work outside of this. :c
diff --git a/code/modules/client/preferences_savefile.dm b/code/modules/client/preferences_savefile.dm
index f683ba0ee3..72b2f5d949 100644
--- a/code/modules/client/preferences_savefile.dm
+++ b/code/modules/client/preferences_savefile.dm
@@ -5,7 +5,7 @@
// You do not need to raise this if you are adding new values that have sane defaults.
// Only raise this value when changing the meaning/format/name/layout of an existing value
// where you would want the updater procs below to run
-#define SAVEFILE_VERSION_MAX 52
+#define SAVEFILE_VERSION_MAX 53
/*
SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Carn
@@ -343,6 +343,9 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
marking_list += list(list(part, old_marking_value, copied_color_list))
features["mam_body_markings"] = marking_list
+ if(current_version < 53)
+ parallax = PARALLAX_INSANE
+
/datum/preferences/proc/load_path(ckey,filename="preferences.sav")
if(!ckey)
return
@@ -469,7 +472,7 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
default_slot = sanitize_integer(default_slot, 1, max_save_slots, initial(default_slot))
toggles = sanitize_integer(toggles, 0, 16777215, initial(toggles))
clientfps = sanitize_integer(clientfps, 0, 1000, 0)
- parallax = sanitize_integer(parallax, PARALLAX_INSANE, PARALLAX_DISABLE, null)
+ parallax = sanitize_integer(parallax, PARALLAX_DISABLE, PARALLAX_INSANE, null)
ambientocclusion = sanitize_integer(ambientocclusion, 0, 1, initial(ambientocclusion))
auto_fit_viewport = sanitize_integer(auto_fit_viewport, 0, 1, initial(auto_fit_viewport))
widescreenpref = sanitize_integer(widescreenpref, 0, 1, initial(widescreenpref))
diff --git a/code/modules/clothing/glasses/_glasses.dm b/code/modules/clothing/glasses/_glasses.dm
index 01d27531e1..737bbbb522 100644
--- a/code/modules/clothing/glasses/_glasses.dm
+++ b/code/modules/clothing/glasses/_glasses.dm
@@ -581,3 +581,14 @@
var/mob/living/carbon/human/H = user
H.update_sight()
+/obj/item/clothing/glasses/osi
+ name = "O.S.I. Sunglasses"
+ desc = "There's no such thing as good news! Just bad news and... weird news.."
+ icon_state = "osi_glasses"
+ item_state = "osi_glasses"
+
+/obj/item/clothing/glasses/phantom
+ name = "Phantom Thief Mask"
+ desc = "Lookin' cool."
+ icon_state = "phantom_glasses"
+ item_state = "phantom_glasses"
diff --git a/code/modules/clothing/glasses/hud.dm b/code/modules/clothing/glasses/hud.dm
index 9ca98b0bca..cad91315a3 100644
--- a/code/modules/clothing/glasses/hud.dm
+++ b/code/modules/clothing/glasses/hud.dm
@@ -3,6 +3,8 @@
desc = "A heads-up display that provides important info in (almost) real time."
flags_1 = null //doesn't protect eyes because it's a monocle, duh
var/hud_type = null
+ ///Used for topic calls. Just because you have a HUD display doesn't mean you should be able to interact with stuff.
+ var/hud_trait = null
/obj/item/clothing/glasses/hud/CheckParts(list/parts_list)
. = ..()
@@ -290,3 +292,16 @@
if(. & EMP_PROTECT_SELF)
return
thermal_overload()
+
+/obj/item/clothing/glasses/hud/spacecop
+ name = "police aviators"
+ desc = "For thinking you look cool while brutalizing protestors and minorities."
+ icon_state = "bigsunglasses"
+ hud_type = ANTAG_HUD_GANGSTER
+
+/obj/item/clothing/glasses/hud/spacecop/hidden // for the undercover cop
+ name = "sunglasses"
+ desc = "These sunglasses are special, and let you view potential criminals."
+ icon_state = "sun"
+ item_state = "sunglasses"
+
diff --git a/code/modules/clothing/gloves/tacklers.dm b/code/modules/clothing/gloves/tacklers.dm
index 2b12d23d2b..39d39ba58f 100644
--- a/code/modules/clothing/gloves/tacklers.dm
+++ b/code/modules/clothing/gloves/tacklers.dm
@@ -133,3 +133,9 @@
base_knockdown = 1.75 SECONDS
min_distance = 2
skill_mod = -1
+
+/obj/item/clothing/gloves/tackler/football
+ name = "football gloves"
+ desc = "Gloves for football players! Teaches them how to tackle like a pro."
+ icon_state = "tackle_gloves"
+ item_state = "tackle_gloves"
diff --git a/code/modules/clothing/head/misc.dm b/code/modules/clothing/head/misc.dm
index fd181779f1..76e8ca53df 100644
--- a/code/modules/clothing/head/misc.dm
+++ b/code/modules/clothing/head/misc.dm
@@ -9,6 +9,12 @@
armor = list("melee" = 30, "bullet" = 15, "laser" = 30, "energy" = 10, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50)
strip_delay = 80
+/obj/item/clothing/head/spacepolice
+ name = "space police cap"
+ desc = "A blue cap for patrolling the daily beat."
+ icon_state = "policecap_families"
+ item_state = "policecap_families"
+
/obj/item/clothing/head/powdered_wig
name = "powdered wig"
desc = "A powdered wig."
@@ -488,3 +494,10 @@
desc = "This will scare them. All will know my power."
icon_state = "human_leather"
item_state = "human_leather"
+
+/obj/item/clothing/head/jackbros
+ name = "frosty hat"
+ desc = "Hee-ho!"
+ icon_state = "JackFrostHat"
+ item_state = "JackFrostHat"
+
diff --git a/code/modules/clothing/head/misc_special.dm b/code/modules/clothing/head/misc_special.dm
index e19a0538fc..247062581d 100644
--- a/code/modules/clothing/head/misc_special.dm
+++ b/code/modules/clothing/head/misc_special.dm
@@ -90,23 +90,34 @@
item_state = "ushankadown"
alternate_screams = list('sound/voice/human/cyka1.ogg', 'sound/voice/human/cheekibreeki.ogg')
flags_inv = HIDEEARS|HIDEHAIR
- var/earflaps = 1
+ var/earflaps = TRUE
cold_protection = HEAD
min_cold_protection_temperature = FIRE_HELM_MIN_TEMP_PROTECT
+ ///Sprite visible when the ushanka flaps are folded up.
+ var/upsprite = "ushankaup"
+ ///Sprite visible when the ushanka flaps are folded down.
+ var/downsprite = "ushankadown"
dog_fashion = /datum/dog_fashion/head/ushanka
/obj/item/clothing/head/ushanka/attack_self(mob/user)
if(earflaps)
- src.icon_state = "ushankaup"
- src.item_state = "ushankaup"
- earflaps = 0
+ icon_state = upsprite
+ item_state = upsprite
to_chat(user, "You raise the ear flaps on the ushanka.")
else
- src.icon_state = "ushankadown"
- src.item_state = "ushankadown"
- earflaps = 1
+ icon_state = downsprite
+ item_state = downsprite
to_chat(user, "You lower the ear flaps on the ushanka.")
+ earflaps = !earflaps
+
+/obj/item/clothing/head/ushanka/soviet
+ name = "soviet ushanka"
+ desc = "For the union!"
+ icon_state = "sovietushankadown"
+ item_state = "sovietushankadown"
+ upsprite = "sovietushankaup"
+ downsprite = "sovietushankadown"
/*
* Pumpkin head
@@ -315,3 +326,55 @@
/datum/component/storage/concrete/pockets/tiny/spacenam
attack_hand_interact = TRUE //So you can actually see what you stuff in there
+
+//families
+/obj/item/clothing/head/irs
+ name = "internal revenue service cap"
+ icon_state = "irs_hat"
+ item_state = "irs_hat"
+
+/obj/item/clothing/head/pg
+ name = "powder ganger beanie"
+ icon_state = "pg_hat"
+ item_state = "pg_hat"
+
+/obj/item/clothing/head/tmc
+ name = "Lost M.C. bandana"
+ icon_state = "tmc_hat"
+ item_state = "tmc_hat"
+
+/obj/item/clothing/head/deckers
+ name = "Decker headphones"
+ icon_state = "decker_hat"
+ item_state = "decker_hat"
+
+/obj/item/clothing/head/morningstar
+ name = "Morningstar beret"
+ icon_state = "morningstar_hat"
+ item_state = "morningstar_hat"
+
+/obj/item/clothing/head/saints
+ name = "Saints hat"
+ icon_state = "saints_hat"
+ item_state = "saints_hat"
+
+/obj/item/clothing/head/allies
+ name = "allies helmet"
+ icon_state = "allies_helmet"
+ item_state = "allies_helmet"
+
+/obj/item/clothing/head/yuri
+ name = "yuri initiate helmet"
+ icon_state = "yuri_helmet"
+ item_state = "yuri_helmet"
+ flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDEFACIALHAIR|HIDESNOUT
+
+/obj/item/clothing/head/sybil_slickers
+ name = "sybil slickers helmet"
+ icon_state = "football_helmet_blue"
+ item_state = "football_helmet_blue"
+
+/obj/item/clothing/head/basil_boys
+ name = "basil boys helmet"
+ icon_state = "football_helmet_red"
+ item_state = "football_helmet_red"
diff --git a/code/modules/clothing/masks/gasmask.dm b/code/modules/clothing/masks/gasmask.dm
index 2e3c544d08..c33e6098c0 100644
--- a/code/modules/clothing/masks/gasmask.dm
+++ b/code/modules/clothing/masks/gasmask.dm
@@ -273,3 +273,11 @@
item_state = "hunter"
resistance_flags = FIRE_PROOF | ACID_PROOF
flags_inv = HIDEFACIALHAIR|HIDEFACE|HIDEEYES|HIDEEARS|HIDEHAIR
+
+/obj/item/clothing/mask/gas/driscoll
+ name = "driscoll mask"
+ desc = "Great for train hijackings. Works like a normal full face gas mask, but won't conceal your identity."
+ icon_state = "driscoll_mask"
+ flags_inv = HIDEFACIALHAIR
+ w_class = WEIGHT_CLASS_NORMAL
+ item_state = "driscoll_mask"
diff --git a/code/modules/clothing/shoes/_shoes.dm b/code/modules/clothing/shoes/_shoes.dm
index 7a1e6b14f3..c98824fa7f 100644
--- a/code/modules/clothing/shoes/_shoes.dm
+++ b/code/modules/clothing/shoes/_shoes.dm
@@ -102,7 +102,7 @@
worn_y_dimension = world.icon_size
/obj/item/clothing/shoes/dropped(mob/user)
- if(our_alert && (our_alert.mob_viewer == user))
+ if(our_alert && (our_alert.owner == user))
user.clear_alert("shoealert")
if(offset && equipped_before_drop)
restore_offsets(user)
diff --git a/code/modules/clothing/shoes/miscellaneous.dm b/code/modules/clothing/shoes/miscellaneous.dm
index 8978ad7307..669ffa1780 100644
--- a/code/modules/clothing/shoes/miscellaneous.dm
+++ b/code/modules/clothing/shoes/miscellaneous.dm
@@ -423,12 +423,16 @@
name = "cowboy boots"
desc = "A standard pair of brown cowboy boots."
icon_state = "cowboyboots"
+ item_state = "cowboyboots"
+ permeability_coefficient = 0.05 //these are quite tall
+ pocket_storage_component_path = /datum/component/storage/concrete/pockets/shoes
can_be_tied = FALSE
/obj/item/clothing/shoes/cowboyboots/black
name = "black cowboy boots"
desc = "A pair of black cowboy boots, pretty easy to scuff up."
icon_state = "cowboyboots_black"
+ item_state = "cowboyboots_black"
/obj/item/clothing/shoes/wallwalkers
name = "wall walking boots"
@@ -547,3 +551,59 @@
desc = "They got me for my foams!"
icon_state = "SwagShoes"
item_state = "SwagShoes"
+
+/obj/item/clothing/shoes/phantom
+ name = "phantom shoes"
+ desc = "Excellent for when you need to do cool flashy flips."
+ icon_state = "phantom_shoes"
+ item_state = "phantom_shoes"
+ mutantrace_variation = STYLE_DIGITIGRADE|STYLE_NO_ANTHRO_ICON
+
+/obj/item/clothing/shoes/saints
+ name = "saints sneakers"
+ desc = "Officially branded Saints sneakers. Incredibly valuable!"
+ icon_state = "saints_shoes"
+ item_state = "saints_shoes"
+ mutantrace_variation = STYLE_DIGITIGRADE|STYLE_NO_ANTHRO_ICON
+
+/obj/item/clothing/shoes/morningstar
+ name = "morningstar boots"
+ desc = "The most expensive boots on this station. Wearing them dropped the value by about 50%."
+ icon_state = "morningstar_shoes"
+ item_state = "morningstar_shoes"
+ mutantrace_variation = STYLE_DIGITIGRADE|STYLE_NO_ANTHRO_ICON
+
+/obj/item/clothing/shoes/deckers
+ name = "deckers rollerskates"
+ desc = "t3h c00L3st sh03z j00'LL 3v3r f1nd."
+ icon_state = "decker_shoes"
+ item_state = "decker_shoes"
+ mutantrace_variation = STYLE_DIGITIGRADE|STYLE_NO_ANTHRO_ICON
+
+/obj/item/clothing/shoes/sybil_slickers
+ name = "sybil slickers shoes"
+ desc = "FOOTBALL! YEAH!"
+ icon_state = "sneakers_blue"
+ item_state = "sneakers_blue"
+ mutantrace_variation = STYLE_DIGITIGRADE|STYLE_NO_ANTHRO_ICON
+
+/obj/item/clothing/shoes/basil_boys
+ name = "basil boys shoes"
+ desc = "FOOTBALL! YEAH!"
+ icon_state = "sneakers_red"
+ item_state = "sneakers_red"
+ mutantrace_variation = STYLE_DIGITIGRADE|STYLE_NO_ANTHRO_ICON
+
+/obj/item/clothing/shoes/yakuza
+ name = "tojo clan shoes"
+ desc = "Steel-toed and intimidating."
+ icon_state = "MajimaShoes"
+ item_state = "MajimaShoes_worn"
+ mutantrace_variation = STYLE_DIGITIGRADE|STYLE_NO_ANTHRO_ICON
+
+/obj/item/clothing/shoes/jackbros
+ name = "frosty boots"
+ desc = "For when you're stepping on up to the plate."
+ icon_state = "JackFrostShoes"
+ item_state = "JackFrostShoes_worn"
+ mutantrace_variation = STYLE_DIGITIGRADE|STYLE_NO_ANTHRO_ICON
diff --git a/code/modules/clothing/suits/miscellaneous.dm b/code/modules/clothing/suits/miscellaneous.dm
index 10dbb9e2bf..513ad94365 100644
--- a/code/modules/clothing/suits/miscellaneous.dm
+++ b/code/modules/clothing/suits/miscellaneous.dm
@@ -1241,3 +1241,132 @@
desc = "It looks like someone dragged this out of a muddy lake."
icon_state = "bomberalt"
item_state = "bomberalt"
+
+/obj/item/clothing/suit/driscoll
+ name = "driscoll poncho"
+ desc = "Keeping you warm in the harsh cold of space."
+ icon_state = "driscoll_suit"
+ item_state = "driscoll_suit"
+ body_parts_covered = CHEST|GROIN|LEGS|ARMS
+ mutantrace_variation = STYLE_DIGITIGRADE|STYLE_NO_ANTHRO_ICON
+
+/obj/item/clothing/suit/irs
+ name = "internal revenue service jacket"
+ desc = "I'm crazy enough to take on The Owl, but the IRS? Nooo thank you!"
+ icon_state = "irs_suit"
+ item_state = "irs_suit"
+ body_parts_covered = CHEST|GROIN|LEGS|ARMS
+ mutantrace_variation = STYLE_DIGITIGRADE|STYLE_NO_ANTHRO_ICON
+
+/obj/item/clothing/suit/osi
+ name = "\improper O.S.I. body armor"
+ desc = "You're beyond good and evil, super man. You work for the government. And you're a tool, boy, a tool! Built for a single purpose by the United States of shut your third fucking damn eye for a fucking reason! You can't teach a hammer to love nails, son. That dog don't hunt!"
+ icon_state = "osi_suit"
+ item_state = "osi_suit"
+ body_parts_covered = CHEST|GROIN|LEGS|ARMS
+ mutantrace_variation = STYLE_DIGITIGRADE|STYLE_NO_ANTHRO_ICON
+
+/obj/item/clothing/suit/tmc
+ name = "\improper Lost M.C. cut"
+ desc = "Making sure everyone knows you're in the best biker gang this side of Alderney."
+ icon_state = "tmc_suit"
+ item_state = "tmc_suit"
+ body_parts_covered = CHEST|GROIN|LEGS|ARMS
+ mutantrace_variation = STYLE_DIGITIGRADE|STYLE_NO_ANTHRO_ICON
+
+/obj/item/clothing/suit/pg
+ name = "powder ganger jacket"
+ desc = "Remind Security of their mistakes in giving prisoners blasting charges."
+ icon_state = "pg_suit"
+ item_state = "pg_suit"
+ body_parts_covered = CHEST|GROIN|LEGS|ARMS
+ mutantrace_variation = STYLE_DIGITIGRADE|STYLE_NO_ANTHRO_ICON
+
+/obj/item/clothing/suit/deckers
+ name = "decker hoodie"
+ desc = "Based? Based on what?"
+ icon_state = "decker_suit"
+ item_state = "decker_suit"
+ body_parts_covered = CHEST|GROIN|LEGS|ARMS
+ mutantrace_variation = STYLE_DIGITIGRADE|STYLE_NO_ANTHRO_ICON
+
+/obj/item/clothing/suit/morningstar
+ name = "morningstar coat"
+ desc = "This coat costs more than you've ever made in your entire life."
+ icon_state = "morningstar_suit"
+ item_state = "morningstar_suit"
+ body_parts_covered = CHEST|GROIN|LEGS|ARMS
+ mutantrace_variation = STYLE_DIGITIGRADE|STYLE_NO_ANTHRO_ICON
+
+/obj/item/clothing/suit/saints
+ name = "Third Street Saints fur coat"
+ desc = "Rated 10 out of 10 in Cosmo for best coat brand."
+ icon_state = "saints_suit"
+ item_state = "saints_suit"
+ body_parts_covered = CHEST|GROIN|LEGS|ARMS
+ mutantrace_variation = STYLE_DIGITIGRADE|STYLE_NO_ANTHRO_ICON
+
+/obj/item/clothing/suit/phantom
+ name = "phantom thief coat"
+ desc = "Your foes will never see you coming in this stealthy yet stylish getup."
+ icon_state = "phantom_suit"
+ item_state = "phantom_suit"
+ body_parts_covered = CHEST|GROIN|LEGS|ARMS
+ mutantrace_variation = STYLE_DIGITIGRADE|STYLE_NO_ANTHRO_ICON
+
+/obj/item/clothing/suit/allies
+ name = "allies body armor"
+ desc = "How 'bout some action!? Sponsored by DonkSoft Co. for historical reenactment of the Third World War!"
+ icon_state = "allies_armor"
+ item_state = "allies_armor"
+ body_parts_covered = CHEST|GROIN
+ mutantrace_variation = STYLE_DIGITIGRADE|STYLE_NO_ANTHRO_ICON
+
+/obj/item/clothing/suit/soviet
+ name = "soviet armored coat"
+ desc = "Conscript reporting! Sponsored by DonkSoft Co. for historical reenactment of the Third World War!"
+ icon_state = "soviet_suit"
+ item_state = "soviet_suit"
+ body_parts_covered = CHEST|GROIN|LEGS|ARMS
+ mutantrace_variation = STYLE_DIGITIGRADE|STYLE_NO_ANTHRO_ICON
+
+/obj/item/clothing/suit/yuri
+ name = "yuri initiate coat"
+ desc = "Yuri is master! Sponsored by DonkSoft Co. for historical reenactment of the Third World War!"
+ icon_state = "yuri_coat"
+ item_state = "yuri_coat"
+ body_parts_covered = CHEST|GROIN|ARMS
+ mutantrace_variation = STYLE_DIGITIGRADE|STYLE_NO_ANTHRO_ICON
+
+/obj/item/clothing/suit/sybil_slickers
+ name = "sybil slickers protective gear"
+ desc = "Given to members of the Sybil Slickers football team!"
+ icon_state = "football_armor_blue"
+ item_state = "football_armor_blue"
+ body_parts_covered = CHEST|GROIN|LEGS|ARMS
+ mutantrace_variation = STYLE_DIGITIGRADE|STYLE_NO_ANTHRO_ICON
+
+/obj/item/clothing/suit/basil_boys
+ name = "basil boys protective gear"
+ desc = "Given to members of the Basil Boys football team!"
+ icon_state = "football_armor_red"
+ item_state = "football_armor_red"
+ body_parts_covered = CHEST|GROIN|LEGS|ARMS
+ mutantrace_variation = STYLE_DIGITIGRADE|STYLE_NO_ANTHRO_ICON
+
+/obj/item/clothing/suit/yakuza
+ name = "tojo clan jacket"
+ desc = "The jacket of a mad dog."
+ icon_state = "MajimaJacket"
+ item_state = "MajimaJacket"
+ body_parts_covered = CHEST|GROIN|LEGS|ARMS
+ mutantrace_variation = STYLE_DIGITIGRADE|STYLE_NO_ANTHRO_ICON
+
+/obj/item/clothing/suit/dutch
+ name = "dutch's jacket"
+ desc = "For those long nights on the beach in Tahiti."
+ icon_state = "DutchJacket"
+ item_state = "DutchJacket"
+ body_parts_covered = CHEST|GROIN|LEGS|ARMS
+ mutantrace_variation = STYLE_DIGITIGRADE|STYLE_NO_ANTHRO_ICON
+
diff --git a/code/modules/clothing/under/costume.dm b/code/modules/clothing/under/costume.dm
index 4e5be1306c..9ebc79bd41 100644
--- a/code/modules/clothing/under/costume.dm
+++ b/code/modules/clothing/under/costume.dm
@@ -356,9 +356,131 @@
icon_state = "sakura_kimono"
item_state = "sakura_kimono"
+/obj/item/clothing/under/costume/irs
+ name = "internal revenue service outfit"
+ icon_state = "irs_jumpsuit"
+ item_state = "irs_jumpsuit"
+ can_adjust = FALSE
+ mutantrace_variation = STYLE_DIGITIGRADE|STYLE_NO_ANTHRO_ICON
+
+/obj/item/clothing/under/costume/osi
+ name = "O.S.I. jumpsuit"
+ icon_state = "osi_jumpsuit"
+ item_state = "osi_jumpsuit"
+ can_adjust = FALSE
+ mutantrace_variation = STYLE_DIGITIGRADE|STYLE_NO_ANTHRO_ICON
+
+/obj/item/clothing/under/costume/tmc
+ name = "Lost MC clothing"
+ icon_state = "tmc_jumpsuit"
+ item_state = "tmc_jumpsuit"
+ can_adjust = FALSE
+ mutantrace_variation = STYLE_DIGITIGRADE|STYLE_NO_ANTHRO_ICON
+
+/obj/item/clothing/under/costume/pg
+ name = "powder ganger prison jumpsuit"
+ icon_state = "pg_jumpsuit"
+ item_state = "pg_jumpsuit"
+ can_adjust = FALSE
+ mutantrace_variation = STYLE_DIGITIGRADE|STYLE_NO_ANTHRO_ICON
+
+/obj/item/clothing/under/costume/driscoll
+ name = "O'Driscoll outfit"
+ icon_state = "driscoll_jumpsuit"
+ item_state = "driscoll_jumpsuit"
+ can_adjust = FALSE
+ mutantrace_variation = STYLE_DIGITIGRADE|STYLE_NO_ANTHRO_ICON
+
+/obj/item/clothing/under/costume/deckers
+ name = "deckers outfit"
+ icon_state = "decker_jumpsuit"
+ item_state = "decker_jumpsuit"
+ can_adjust = FALSE
+ mutantrace_variation = STYLE_DIGITIGRADE|STYLE_NO_ANTHRO_ICON
+
+/obj/item/clothing/under/costume/morningstar
+ name = "Morningstar suit"
+ icon_state = "morningstar_jumpsuit"
+ item_state = "morningstar_jumpsuit"
+ can_adjust = FALSE
+ mutantrace_variation = STYLE_DIGITIGRADE|STYLE_NO_ANTHRO_ICON
+
+/obj/item/clothing/under/costume/saints
+ name = "Saints outfit"
+ icon_state = "saints_jumpsuit"
+ item_state = "saints_jumpsuit"
+ can_adjust = FALSE
+ mutantrace_variation = STYLE_DIGITIGRADE|STYLE_NO_ANTHRO_ICON
+
+/obj/item/clothing/under/costume/phantom
+ name = "Phantom Thief outfit"
+ icon_state = "phantom_jumpsuit"
+ item_state = "phantom_jumpsuit"
+ can_adjust = FALSE
+ mutantrace_variation = STYLE_DIGITIGRADE|STYLE_NO_ANTHRO_ICON
+
+/obj/item/clothing/under/costume/allies
+ name = "allies tanktop"
+ icon_state = "allies_uniform"
+ item_state = "allies_uniform"
+ can_adjust = FALSE
+ mutantrace_variation = STYLE_DIGITIGRADE|STYLE_NO_ANTHRO_ICON
+
+/obj/item/clothing/under/costume/soviet_families
+ name = "soviet conscript uniform"
+ icon_state = "soviet_uniform"
+ item_state = "soviet_uniform"
+ can_adjust = FALSE
+ mutantrace_variation = STYLE_DIGITIGRADE|STYLE_NO_ANTHRO_ICON
+
+/obj/item/clothing/under/costume/yuri
+ name = "yuri initiate jumpsuit"
+ icon_state = "yuri_uniform"
+ item_state = "yuri_uniform"
+ can_adjust = FALSE
+ mutantrace_variation = STYLE_DIGITIGRADE|STYLE_NO_ANTHRO_ICON
+
+/obj/item/clothing/under/costume/sybil_slickers
+ name = "sybil slickers uniform"
+ icon_state = "football_blue"
+ item_state = "football_blue"
+ can_adjust = FALSE
+ mutantrace_variation = STYLE_DIGITIGRADE|STYLE_NO_ANTHRO_ICON
+
+/obj/item/clothing/under/costume/basil_boys
+ name = "basil boys uniform"
+ icon_state = "football_red"
+ item_state = "football_red"
+ can_adjust = FALSE
+ mutantrace_variation = STYLE_DIGITIGRADE|STYLE_NO_ANTHRO_ICON
+
/obj/item/clothing/under/costume/swagoutfit
name = "Swag outfit"
desc = "Why don't you go secure some bitches?"
icon_state = "SwagOutfit"
item_state = "SwagOutfit"
can_adjust = FALSE
+
+/obj/item/clothing/under/costume/jackbros
+ name = "jack bros outfit"
+ desc = "For when it's time to hee some hos."
+ icon_state = "JackFrostUniform"
+ item_state = "JackFrostUniform"
+ can_adjust = FALSE
+ mutantrace_variation = STYLE_DIGITIGRADE|STYLE_NO_ANTHRO_ICON
+
+/obj/item/clothing/under/costume/yakuza
+ name = "tojo clan pants"
+ desc = "For those long nights under the traffic cone."
+ icon_state = "MajimaPants"
+ item_state = "MajimaPants"
+ can_adjust = FALSE
+ mutantrace_variation = STYLE_DIGITIGRADE|STYLE_NO_ANTHRO_ICON
+
+/obj/item/clothing/under/costume/dutch
+ name = "dutch's suit"
+ desc = "You can feel a god damn plan coming on."
+ icon_state = "DutchUniform"
+ item_state = "DutchUniform"
+ can_adjust = FALSE
+ mutantrace_variation = STYLE_DIGITIGRADE|STYLE_NO_ANTHRO_ICON
diff --git a/code/modules/clothing/under/jobs/security.dm b/code/modules/clothing/under/jobs/security.dm
index 0b1fb99bf8..c0d08204f0 100644
--- a/code/modules/clothing/under/jobs/security.dm
+++ b/code/modules/clothing/under/jobs/security.dm
@@ -242,3 +242,10 @@
icon_state = "spacepol"
item_state = "spacepol"
can_adjust = FALSE
+
+/obj/item/clothing/under/rank/security/officer/beatcop
+ name = "space police uniform"
+ desc = "A police uniform often found in the lines at donut shops."
+ icon_state = "spacepolice_families"
+ item_state = "spacepolice_families"
+ can_adjust = FALSE
diff --git a/code/modules/clothing/under/suits.dm b/code/modules/clothing/under/suits.dm
index 7f0ecf3d70..8898696668 100644
--- a/code/modules/clothing/under/suits.dm
+++ b/code/modules/clothing/under/suits.dm
@@ -111,6 +111,25 @@
body_parts_covered = CHEST|GROIN
can_adjust = FALSE
+
+/obj/item/clothing/under/suit/beige
+ name = "beige suit"
+ desc = "An excellent light colored suit, experts in the field stress that it should not to be confused with the inferior tan suit."
+ icon_state = "beige_suit"
+ item_state = "beige_suit"
+ mutantrace_variation = STYLE_DIGITIGRADE|STYLE_NO_ANTHRO_ICON
+
+/obj/item/clothing/under/suit/henchmen
+ name = "henchmen jumpsuit"
+ desc = "A very gaudy jumpsuit for a proper Henchman. Guild regulations, you understand."
+ icon = 'icons/obj/clothing/uniforms.dmi'
+ mob_overlay_icon = 'icons/mob/clothing/uniform.dmi'
+ icon_state = "henchmen"
+ item_state = "henchmen"
+ body_parts_covered = CHEST|GROIN|LEGS|FEET|ARMS|HANDS|HEAD
+ flags_inv = HIDEGLOVES|HIDESHOES|HIDEEARS|HIDEEYES|HIDEHAIR|HIDETAUR
+ mutantrace_variation = STYLE_DIGITIGRADE|STYLE_NO_ANTHRO_ICON
+
/obj/item/clothing/under/suit/polychromic //enables all three overlays to reduce copypasta and defines basic stuff
name = "polychromic suit"
desc = "For when you want to show off your horrible colour coordination skills."
diff --git a/code/modules/mining/ores_coins.dm b/code/modules/mining/ores_coins.dm
index 264da96a27..83344949ac 100644
--- a/code/modules/mining/ores_coins.dm
+++ b/code/modules/mining/ores_coins.dm
@@ -72,7 +72,7 @@
/obj/item/stack/ore/uranium
name = "uranium ore"
icon_state = "Uranium ore"
- // inhand_icon_state = "Uranium ore"
+ // item_state = "Uranium ore"
singular_name = "uranium ore chunk"
points = 30
// material_flags = MATERIAL_NO_EFFECTS
diff --git a/code/modules/mob/dead/dead.dm b/code/modules/mob/dead/dead.dm
index ce767ae00d..90b6da26fa 100644
--- a/code/modules/mob/dead/dead.dm
+++ b/code/modules/mob/dead/dead.dm
@@ -126,5 +126,5 @@ INITIALIZE_IMMEDIATE(/mob/dead)
return ..()
/mob/dead/onTransitZ(old_z,new_z)
- ..()
+ . = ..()
update_z(new_z)
diff --git a/code/modules/mob/dead/new_player/new_player.dm b/code/modules/mob/dead/new_player/new_player.dm
index 2068ed47e8..c2d950bc8f 100644
--- a/code/modules/mob/dead/new_player/new_player.dm
+++ b/code/modules/mob/dead/new_player/new_player.dm
@@ -578,8 +578,6 @@
Spl.Fade(TRUE)
character.playsound_local(get_turf(character), 'sound/voice/ApproachingTG.ogg', 25)
- character.update_parallax_teleport()
-
job.standard_assign_skills(character.mind)
SSticker.minds += character.mind
@@ -762,6 +760,7 @@
client.prefs.scars_list["[cur_scar_index]"] = valid_scars
client.prefs.save_character()
+
client.prefs.copy_to(H, initial_spawn = TRUE)
H.dna.update_dna_identity()
if(mind)
diff --git a/code/modules/mob/dead/observer/observer.dm b/code/modules/mob/dead/observer/observer.dm
index 0f30fe20fc..624db8caf5 100644
--- a/code/modules/mob/dead/observer/observer.dm
+++ b/code/modules/mob/dead/observer/observer.dm
@@ -377,8 +377,6 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
return
ghostize(0, penalize = TRUE)
-
-
/mob/dead/observer/Move(NewLoc, direct)
if (SEND_SIGNAL(src, COMSIG_MOVABLE_PRE_MOVE, NewLoc) & COMPONENT_MOVABLE_BLOCK_PRE_MOVE)
return
@@ -388,7 +386,6 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
if(NewLoc)
forceMove(NewLoc)
- update_parallax_contents()
else
forceMove(get_turf(src)) //Get out of closets and such as a ghost
if((direct & NORTH) && y < world.maxy)
@@ -487,9 +484,7 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
if(!L || !L.len)
to_chat(usr, "No area available.")
return
-
usr.forceMove(pick(L))
- update_parallax_contents()
/mob/dead/observer/proc/view_gas()
set category = "Ghost"
@@ -568,7 +563,6 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
if(T && isturf(T)) //Make sure the turf exists, then move the source to that destination.
A.forceMove(T)
- A.update_parallax_contents()
else
to_chat(A, "This mob is not located in the game world.")
diff --git a/code/modules/mob/dead/observer/respawn.dm b/code/modules/mob/dead/observer/respawn.dm
index 94ebf42a2a..32f775ed46 100644
--- a/code/modules/mob/dead/observer/respawn.dm
+++ b/code/modules/mob/dead/observer/respawn.dm
@@ -165,7 +165,7 @@
/**
* Actual proc that removes us and puts us back on lobby
- *
+ *
* Returns the new mob.
*/
/mob/dead/observer/proc/transfer_to_lobby()
@@ -174,8 +174,7 @@
return
client.screen.Cut()
client.view_size.resetToDefault()
- client.generate_clickcatcher()
- client.apply_clickcatcher()
+ client.update_clickcatcher()
client.view_size.setDefault(getScreenSize(client.prefs.widescreenpref))
client.view_size.resetToDefault()
diff --git a/code/modules/mob/inventory.dm b/code/modules/mob/inventory.dm
index 3eaefebc56..67a95006ab 100644
--- a/code/modules/mob/inventory.dm
+++ b/code/modules/mob/inventory.dm
@@ -414,10 +414,6 @@
drop_all_held_items()
/obj/item/proc/equip_to_best_slot(mob/M)
- if(src != M.get_active_held_item())
- to_chat(M, "You are not holding anything to equip!")
- return FALSE
-
if(M.equip_to_appropriate_slot(src, TRUE))
M.update_inv_hands()
return TRUE
diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm
index 6acd604ca5..61839ce123 100644
--- a/code/modules/mob/living/carbon/carbon.dm
+++ b/code/modules/mob/living/carbon/carbon.dm
@@ -646,7 +646,7 @@
become_blind(EYES_COVERED)
else if(tinttotal >= TINT_DARKENED)
cure_blind(EYES_COVERED)
- overlay_fullscreen("tint", /atom/movable/screen/fullscreen/impaired, 2)
+ overlay_fullscreen("tint", /atom/movable/screen/fullscreen/scaled/impaired, 2)
else
cure_blind(EYES_COVERED)
clear_fullscreen("tint", 0)
@@ -722,10 +722,10 @@
visionseverity = 9
if(-INFINITY to -24)
visionseverity = 10
- overlay_fullscreen("critvision", /atom/movable/screen/fullscreen/crit/vision, visionseverity)
+ overlay_fullscreen("critvision", /atom/movable/screen/fullscreen/scaled/crit/vision, visionseverity)
else
clear_fullscreen("critvision")
- overlay_fullscreen("crit", /atom/movable/screen/fullscreen/crit, severity)
+ overlay_fullscreen("crit", /atom/movable/screen/fullscreen/scaled/crit, severity)
else
clear_fullscreen("crit")
clear_fullscreen("critvision")
@@ -749,7 +749,7 @@
severity = 6
if(45 to INFINITY)
severity = 7
- overlay_fullscreen("oxy", /atom/movable/screen/fullscreen/oxy, severity)
+ overlay_fullscreen("oxy", /atom/movable/screen/fullscreen/scaled/oxy, severity)
else
clear_fullscreen("oxy")
@@ -770,7 +770,7 @@
severity = 5
if(85 to INFINITY)
severity = 6
- overlay_fullscreen("brute", /atom/movable/screen/fullscreen/brute, severity)
+ overlay_fullscreen("brute", /atom/movable/screen/fullscreen/scaled/brute, severity)
else
clear_fullscreen("brute")
diff --git a/code/modules/mob/living/carbon/carbon_defense.dm b/code/modules/mob/living/carbon/carbon_defense.dm
index 46ba9a5eb0..a12d6614fa 100644
--- a/code/modules/mob/living/carbon/carbon_defense.dm
+++ b/code/modules/mob/living/carbon/carbon_defense.dm
@@ -361,7 +361,7 @@
return embeds
-/mob/living/carbon/flash_act(intensity = 1, override_blindness_check = 0, affect_silicon = 0, visual = 0, type = /atom/movable/screen/fullscreen/flash, override_protection = 0)
+/mob/living/carbon/flash_act(intensity = 1, override_blindness_check = 0, affect_silicon = 0, visual = 0, type = /atom/movable/screen/fullscreen/tiled/flash, override_protection = 0)
. = ..()
var/damage = override_protection ? intensity : intensity - get_eye_protection()
diff --git a/code/modules/mob/living/carbon/human/death.dm b/code/modules/mob/living/carbon/human/death.dm
index 4e6aac4bcb..b7d9b0f61a 100644
--- a/code/modules/mob/living/carbon/human/death.dm
+++ b/code/modules/mob/living/carbon/human/death.dm
@@ -1,3 +1,4 @@
+GLOBAL_LIST_EMPTY(dead_players_during_shift)
/mob/living/carbon/human/gib_animation()
new /obj/effect/temp_visual/gib_animation(loc, "gibbed-h")
@@ -39,7 +40,9 @@
dizziness = 0
jitteriness = 0
-
+ if(client && !suiciding && !(client in GLOB.dead_players_during_shift))
+ GLOB.dead_players_during_shift += client
+ GLOB.deaths_during_shift++
if(ismecha(loc))
var/obj/mecha/M = loc
if(M.occupant == src)
diff --git a/code/modules/mob/living/carbon/human/dummy.dm b/code/modules/mob/living/carbon/human/dummy.dm
index 3074abd070..974dafb147 100644
--- a/code/modules/mob/living/carbon/human/dummy.dm
+++ b/code/modules/mob/living/carbon/human/dummy.dm
@@ -31,6 +31,27 @@ INITIALIZE_IMMEDIATE(/mob/living/carbon/human/dummy)
randomize_human(src)
dna.initialize_dna(skip_index = TRUE) //Skip stuff that requires full round init.
+/// Provides a dummy that is consistently bald, white, naked, etc.
+/mob/living/carbon/human/dummy/consistent
+
+/mob/living/carbon/human/dummy/consistent/setup_human_dna()
+ create_dna(src)
+ dna.initialize_dna(skip_index = TRUE)
+ dna.features["body_markings"] = "None"
+ dna.features["ears"] = "Cat"
+ dna.features["ethcolor"] = COLOR_WHITE
+ dna.features["frills"] = "None"
+ dna.features["horns"] = "None"
+ dna.features["mcolor"] = COLOR_LIME
+ dna.features["moth_antennae"] = "Plain"
+ dna.features["moth_markings"] = "None"
+ dna.features["moth_wings"] = "Plain"
+ dna.features["snout"] = "Round"
+ dna.features["spines"] = "None"
+ dna.features["tail_human"] = "Cat"
+ dna.features["tail_lizard"] = "Smooth"
+
+
//Inefficient pooling/caching way.
GLOBAL_LIST_EMPTY(human_dummy_list)
GLOBAL_LIST_EMPTY(dummy_mob_list)
diff --git a/code/modules/mob/living/carbon/human/inventory.dm b/code/modules/mob/living/carbon/human/inventory.dm
index c75654e10b..199a6b44be 100644
--- a/code/modules/mob/living/carbon/human/inventory.dm
+++ b/code/modules/mob/living/carbon/human/inventory.dm
@@ -209,7 +209,7 @@
update_tint()
if(G.vision_correction)
if(HAS_TRAIT(src, TRAIT_NEARSIGHT))
- overlay_fullscreen("nearsighted", /atom/movable/screen/fullscreen/impaired, 1)
+ overlay_fullscreen("nearsighted", /atom/movable/screen/fullscreen/scaled/impaired, 1)
if(G.vision_flags || G.darkness_view || G.invis_override || G.invis_view || !isnull(G.lighting_alpha))
update_sight()
if(!QDELETED(src))
diff --git a/code/modules/mob/living/carbon/human/species.dm b/code/modules/mob/living/carbon/human/species.dm
index 1c5bdb6114..22e97290ad 100644
--- a/code/modules/mob/living/carbon/human/species.dm
+++ b/code/modules/mob/living/carbon/human/species.dm
@@ -1920,11 +1920,8 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
if(H.stat == CONSCIOUS && H != user && prob(I.force + ((100 - H.health) * 0.5))) // rev deconversion through blunt trauma.
var/datum/antagonist/rev/rev = H.mind.has_antag_datum(/datum/antagonist/rev)
- var/datum/antagonist/gang/gang = H.mind.has_antag_datum(/datum/antagonist/gang && !/datum/antagonist/gang/boss)
if(rev)
rev.remove_revolutionary(FALSE, user)
- if(gang)
- H.mind.remove_antag_datum(/datum/antagonist/gang)
if(bloody) //Apply blood
if(H.wear_mask)
diff --git a/code/modules/mob/living/carbon/inventory.dm b/code/modules/mob/living/carbon/inventory.dm
index fca7c1f72c..13f811196e 100644
--- a/code/modules/mob/living/carbon/inventory.dm
+++ b/code/modules/mob/living/carbon/inventory.dm
@@ -161,55 +161,58 @@
return index && hand_bodyparts[index]
/**
- * Proc called when giving an item to another player
+ * Proc called when offering an item to another player
*
* This handles creating an alert and adding an overlay to it
*/
/mob/living/carbon/proc/give(target)
- var/obj/item/receiving = get_active_held_item()
- if(!receiving)
+ var/obj/item/offered_item = get_active_held_item()
+ if(!offered_item)
to_chat(src, "You're not holding anything to give!")
return
- visible_message("[src] is offering [receiving]", \
- "You offer [receiving]", null, 2)
- var/mob/living/carbon/targets = list()
- if(!target)
- for(var/mob/living/carbon/C in orange(1, src))
- if(!CanReach(C))
- return
- targets += C
- else
- targets += target
- if(!targets)
+
+ if(IS_DEAD_OR_INCAP(src))
+ to_chat(src, span_warning("You're unable to offer anything in your current state!"))
return
- for(var/mob/living/carbon/C in targets)
- var/atom/movable/screen/alert/give/G = C.throw_alert("[src]", /atom/movable/screen/alert/give)
- if(!G)
- return
- G.setup(C, src, receiving)
+
+ if(has_status_effect(STATUS_EFFECT_OFFERING))
+ to_chat(src, span_warning("You're already offering up something!"))
+ return
+
+ if(offered_item.on_offered(src)) // see if the item interrupts with its own behavior
+ return
+
+ visible_message(span_notice("[src] is offering [offered_item]."), \
+ span_notice("You offer [offered_item]."), null, 2)
+
+ apply_status_effect(STATUS_EFFECT_OFFERING, offered_item)
/**
* Proc called when the player clicks the give alert
*
- * Handles checking if the player taking the item has open slots and is in range of the giver
+ * Handles checking if the player taking the item has open slots and is in range of the offerer
* Also deals with the actual transferring of the item to the players hands
* Arguments:
- * * giver - The person giving the original item
- * * I - The item being given by the giver
+ * * offerer - The person giving the original item
+ * * I - The item being given by the offerer
*/
-/mob/living/carbon/proc/take(mob/living/carbon/giver, obj/item/I)
- clear_alert("[giver]")
- if(get_dist(src, giver) > 1)
- to_chat(src, "[giver] is out of range! ")
+/mob/living/carbon/proc/take(mob/living/carbon/offerer, obj/item/I)
+ clear_alert("[offerer]")
+ if(get_dist(src, offerer) > 1)
+ to_chat(src, span_warning("[offerer] is out of range!"))
return
- if(!I || giver.get_active_held_item() != I)
- to_chat(src, "[giver] is no longer holding the item they were offering! ")
+ if(!I || offerer.get_active_held_item() != I)
+ to_chat(src, span_warning("[offerer] is no longer holding the item they were offering!"))
return
if(!get_empty_held_indexes())
to_chat(src, "You have no empty hands!")
return
- if(!giver.temporarilyRemoveItemFromInventory(I))
- visible_message("[src] tries to hand over [I] but it's stuck to them....", \
+ if(I.on_offer_taken(offerer, src)) // see if the item has special behavior for being accepted
+ return
+ if(!offerer.temporarilyRemoveItemFromInventory(I))
+ visible_message("[offerer] tries to hand over [I] but it's stuck to them....", \
" You make a fool of yourself trying to give away an item stuck to your hands")
return
+ visible_message(span_notice("[src] takes [I] from [offerer]"), \
+ span_notice("You take [I] from [offerer]"))
put_in_hands(I)
diff --git a/code/modules/mob/living/carbon/status_procs.dm b/code/modules/mob/living/carbon/status_procs.dm
index 5b5aa9d5c0..f7cb0e0760 100644
--- a/code/modules/mob/living/carbon/status_procs.dm
+++ b/code/modules/mob/living/carbon/status_procs.dm
@@ -5,7 +5,7 @@
/mob/living/carbon/adjust_drugginess(amount)
druggy = max(druggy+amount, 0)
if(druggy)
- overlay_fullscreen("high", /atom/movable/screen/fullscreen/high)
+ overlay_fullscreen("high", /atom/movable/screen/fullscreen/tiled/high)
throw_alert("high", /atom/movable/screen/alert/high)
SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "high", /datum/mood_event/high)
sound_environment_override = SOUND_ENVIRONMENT_DRUGGED
@@ -18,7 +18,7 @@
/mob/living/carbon/set_drugginess(amount)
druggy = max(amount, 0)
if(druggy)
- overlay_fullscreen("high", /atom/movable/screen/fullscreen/high)
+ overlay_fullscreen("high", /atom/movable/screen/fullscreen/tiled/high)
throw_alert("high", /atom/movable/screen/alert/high)
else
clear_fullscreen("high")
diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm
index 28fd4e0451..2070d07e14 100644
--- a/code/modules/mob/living/living.dm
+++ b/code/modules/mob/living/living.dm
@@ -579,7 +579,7 @@
UNLINT(livingdoll.filters += filter(type="alpha", icon = mob_mask))
livingdoll.filters += filter(type="drop_shadow", size = -1)
if(severity > 0)
- overlay_fullscreen("brute", /atom/movable/screen/fullscreen/brute, severity)
+ overlay_fullscreen("brute", /atom/movable/screen/fullscreen/scaled/brute, severity)
else
clear_fullscreen("brute")
diff --git a/code/modules/mob/living/living_defense.dm b/code/modules/mob/living/living_defense.dm
index 7423a164a4..5ce80c5575 100644
--- a/code/modules/mob/living/living_defense.dm
+++ b/code/modules/mob/living/living_defense.dm
@@ -539,7 +539,7 @@
//called when the mob receives a bright flash
-/mob/living/proc/flash_act(intensity = 1, override_blindness_check = 0, affect_silicon = 0, visual = 0, type = /atom/movable/screen/fullscreen/flash, override_protection = 0)
+/mob/living/proc/flash_act(intensity = 1, override_blindness_check = 0, affect_silicon = 0, visual = 0, type = /atom/movable/screen/fullscreen/tiled/flash, override_protection = 0)
if((override_protection || get_eye_protection() < intensity) && (override_blindness_check || !(HAS_TRAIT(src, TRAIT_BLIND))))
overlay_fullscreen("flash", type)
addtimer(CALLBACK(src, .proc/clear_fullscreen, "flash", 25), 25)
diff --git a/code/modules/mob/living/silicon/ai/freelook/eye.dm b/code/modules/mob/living/silicon/ai/freelook/eye.dm
index dcaa28b39c..3fad48e584 100644
--- a/code/modules/mob/living/silicon/ai/freelook/eye.dm
+++ b/code/modules/mob/living/silicon/ai/freelook/eye.dm
@@ -84,7 +84,6 @@
if(ai.client && !ai.multicam_on)
ai.client.eye = src
update_ai_detect_hud()
- update_parallax_contents()
//Holopad
if(istype(ai.current, /obj/machinery/holopad))
var/obj/machinery/holopad/H = ai.current
diff --git a/code/modules/mob/living/silicon/pai/death.dm b/code/modules/mob/living/silicon/pai/death.dm
index c2bc49f9a8..07cb671706 100644
--- a/code/modules/mob/living/silicon/pai/death.dm
+++ b/code/modules/mob/living/silicon/pai/death.dm
@@ -4,7 +4,7 @@
stat = DEAD
update_mobility()
update_sight()
- clear_fullscreens()
+ wipe_fullscreens()
//New pAI's get a brand new mind to prevent meta stuff from their previous life. This new mind causes problems down the line if it's not deleted here.
remove_from_alive_mob_list()
diff --git a/code/modules/mob/living/silicon/silicon_defense.dm b/code/modules/mob/living/silicon/silicon_defense.dm
index 6c9d508fd8..55d0336671 100644
--- a/code/modules/mob/living/silicon/silicon_defense.dm
+++ b/code/modules/mob/living/silicon/silicon_defense.dm
@@ -144,6 +144,6 @@
P.on_hit(src, 0, def_zone)
return BULLET_ACT_HIT
-/mob/living/silicon/flash_act(intensity = 1, override_blindness_check = 0, affect_silicon = 0, visual = 0, type = /atom/movable/screen/fullscreen/flash/static)
+/mob/living/silicon/flash_act(intensity = 1, override_blindness_check = 0, affect_silicon = 0, visual = 0, type = /atom/movable/screen/fullscreen/tiled/flash/static)
if(affect_silicon)
return ..()
diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/elite.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/elite.dm
index de6b858f79..be596b0292 100644
--- a/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/elite.dm
+++ b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/elite.dm
@@ -113,7 +113,7 @@ While using this makes the system rely on OnFire, it still gives options for tim
severity = 7
hud_used.healths.icon_state = "elite_health[severity]"
if(severity > 0)
- overlay_fullscreen("brute", /atom/movable/screen/fullscreen/brute, severity)
+ overlay_fullscreen("brute", /atom/movable/screen/fullscreen/scaled/brute, severity)
else
clear_fullscreen("brute")
diff --git a/code/modules/mob/living/status_procs.dm b/code/modules/mob/living/status_procs.dm
index 02e88e3741..498ca19a39 100644
--- a/code/modules/mob/living/status_procs.dm
+++ b/code/modules/mob/living/status_procs.dm
@@ -599,7 +599,7 @@
/mob/living/proc/become_nearsighted(source)
if(!HAS_TRAIT(src, TRAIT_NEARSIGHT))
- overlay_fullscreen("nearsighted", /atom/movable/screen/fullscreen/impaired, 1)
+ overlay_fullscreen("nearsighted", /atom/movable/screen/fullscreen/scaled/impaired, 1)
ADD_TRAIT(src, TRAIT_NEARSIGHT, source)
/mob/living/proc/cure_husk(source)
diff --git a/code/modules/mob/login.dm b/code/modules/mob/login.dm
index 7d2daac326..aac55c614b 100644
--- a/code/modules/mob/login.dm
+++ b/code/modules/mob/login.dm
@@ -58,10 +58,6 @@
//readd this mob's HUDs (antag, med, etc)
reload_huds()
- reload_fullscreen() // Reload any fullscreen overlays this mob has.
-
- add_click_catcher()
-
sync_mind()
//Reload alternate appearances
@@ -93,6 +89,9 @@
if(has_field_of_vision && CONFIG_GET(flag/use_field_of_vision))
LoadComponent(/datum/component/field_of_vision, field_of_vision_type)
+ // load rendering
+ reload_rendering()
+
AddElement(/datum/element/weather_listener, /datum/weather/ash_storm, ZTRAIT_ASHSTORM, GLOB.ash_storm_sounds)
// optimized area sound effects. Enable during events (compile flag when 😳)
diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm
index d240f97a23..03c5e76e87 100644
--- a/code/modules/mob/mob.dm
+++ b/code/modules/mob/mob.dm
@@ -1,25 +1,3 @@
-/mob/Destroy()//This makes sure that mobs with clients/keys are not just deleted from the game.
- remove_from_mob_list()
- remove_from_dead_mob_list()
- remove_from_alive_mob_list()
- GLOB.all_clockwork_mobs -= src
- focus = null
- LAssailant = null
- movespeed_modification = null
- for (var/alert in alerts)
- clear_alert(alert, TRUE)
- if(observers && observers.len)
- for(var/M in observers)
- var/mob/dead/observe = M
- observe.reset_perspective(null)
- qdel(hud_used)
- for(var/cc in client_colours)
- qdel(cc)
- client_colours = null
- ghostize()
- ..()
- return QDEL_HINT_HARDDEL
-
/mob/Initialize()
add_to_mob_list()
if(stat == DEAD)
@@ -38,8 +16,32 @@
update_config_movespeed()
update_movespeed(TRUE)
initialize_actionspeed()
+ init_rendering()
hook_vr("mob_new",list(src))
+/mob/Destroy()//This makes sure that mobs with clients/keys are not just deleted from the game.
+ remove_from_mob_list()
+ remove_from_dead_mob_list()
+ remove_from_alive_mob_list()
+ GLOB.all_clockwork_mobs -= src
+ focus = null
+ LAssailant = null
+ movespeed_modification = null
+ for (var/alert in alerts)
+ clear_alert(alert, TRUE)
+ if(observers && observers.len)
+ for(var/M in observers)
+ var/mob/dead/observe = M
+ observe.reset_perspective(null)
+ dispose_rendering()
+ qdel(hud_used)
+ for(var/cc in client_colours)
+ qdel(cc)
+ client_colours = null
+ ghostize()
+ ..()
+ return QDEL_HINT_HARDDEL
+
/mob/GenerateTag()
tag = "mob_[next_mob_id++]"
diff --git a/code/modules/mob/mob_defines.dm b/code/modules/mob/mob_defines.dm
index 8e6226069c..527b0c0917 100644
--- a/code/modules/mob/mob_defines.dm
+++ b/code/modules/mob/mob_defines.dm
@@ -16,6 +16,10 @@
attack_hand_unwieldlyness = CLICK_CD_MELEE
attack_hand_speed = 0
+ // Rendering
+ /// Fullscreen objects
+ var/list/fullscreens = list()
+
/// What receives our keyboard input. src by default.
var/datum/focus
@@ -143,7 +147,6 @@
var/registered_z
var/list/alerts = list() // contains /atom/movable/screen/alert only // On /mob so clientless mobs will throw alerts properly
- var/list/screens = list()
var/list/client_colours = list()
var/hud_type = /datum/hud
diff --git a/code/modules/mob/mob_movement.dm b/code/modules/mob/mob_movement.dm
index bfd4cfcd29..1da355a419 100644
--- a/code/modules/mob/mob_movement.dm
+++ b/code/modules/mob/mob_movement.dm
@@ -110,6 +110,11 @@
SEND_SIGNAL(mob, COMSIG_MOB_CLIENT_MOVE, src, direction, n, oldloc, add_delay)
+/mob/Moved(atom/OldLoc, Dir)
+ . = ..()
+ if(client)
+ client.parallax_holder.Update()
+
/// Process_Grab(): checks for grab, attempts to break if so. Return TRUE to prevent movement.
/client/proc/Process_Grab()
if(mob.pulledby)
@@ -398,3 +403,8 @@
/mob/proc/canZMove(direction, turf/target)
return FALSE
+
+/mob/onTransitZ(old_z, new_z)
+ . = ..()
+ if(old_z != new_z)
+ client?.parallax_holder?.Reset()
diff --git a/code/modules/mob/status_procs.dm b/code/modules/mob/status_procs.dm
index 089484ea9f..da1919443a 100644
--- a/code/modules/mob/status_procs.dm
+++ b/code/modules/mob/status_procs.dm
@@ -53,7 +53,7 @@
if(eye_blind) // UNCONSCIOUS or has blind trait, or has temporary blindness
if(stat == CONSCIOUS || stat == SOFT_CRIT)
throw_alert("blind", /atom/movable/screen/alert/blind)
- overlay_fullscreen("blind", /atom/movable/screen/fullscreen/blind)
+ overlay_fullscreen("blind", /atom/movable/screen/fullscreen/scaled/blind)
// You are blind why should you be able to make out details like color, only shapes near you
// add_client_colour(/datum/client_colour/monochrome/blind)
else // CONSCIOUS no blind trait, no blindness
diff --git a/code/modules/paperwork/carbonpaper.dm b/code/modules/paperwork/carbonpaper.dm
index 9a306333b3..afb5e9de2c 100644
--- a/code/modules/paperwork/carbonpaper.dm
+++ b/code/modules/paperwork/carbonpaper.dm
@@ -2,7 +2,7 @@
name = "sheet of carbon"
icon_state = "paper_stack"
item_state = "paper"
- // inhand_icon_state = "paper"
+ // item_state = "paper"
show_written_words = FALSE
var/copied = FALSE
var/iscopy = FALSE
diff --git a/code/modules/paperwork/handlabeler.dm b/code/modules/paperwork/handlabeler.dm
index ad251a6c82..1c4afbf518 100644
--- a/code/modules/paperwork/handlabeler.dm
+++ b/code/modules/paperwork/handlabeler.dm
@@ -4,7 +4,7 @@
icon = 'icons/obj/bureaucracy.dmi'
icon_state = "labeler0"
item_state = "flight"
- // inhand_icon_state = "flight"
+ // item_state = "flight"
var/label = null
var/labels_left = 30
var/mode = 0
@@ -118,7 +118,7 @@
desc = "A roll of paper. Use it on a hand labeler to refill it."
icon_state = "labeler_refill"
item_state = "electropack"
- // inhand_icon_state = "electropack"
+ // item_state = "electropack"
lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi'
righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi'
w_class = WEIGHT_CLASS_TINY
diff --git a/code/modules/paperwork/paper_cutter.dm b/code/modules/paperwork/paper_cutter.dm
index 2bb743abbd..0dbc80521b 100644
--- a/code/modules/paperwork/paper_cutter.dm
+++ b/code/modules/paperwork/paper_cutter.dm
@@ -132,6 +132,6 @@
icon = 'icons/obj/bureaucracy.dmi'
icon_state = "cutterblade"
item_state = "knife"
- // inhand_icon_state = "knife"
+ // item_state = "knife"
lefthand_file = 'icons/mob/inhands/equipment/kitchen_lefthand.dmi'
righthand_file = 'icons/mob/inhands/equipment/kitchen_righthand.dmi'
diff --git a/code/modules/paperwork/paperbin.dm b/code/modules/paperwork/paperbin.dm
index 6775532614..8fab76a406 100644
--- a/code/modules/paperwork/paperbin.dm
+++ b/code/modules/paperwork/paperbin.dm
@@ -4,7 +4,7 @@
icon = 'icons/obj/bureaucracy.dmi'
icon_state = "paper_bin1"
item_state = "sheet-metal"
- // inhand_icon_state = "sheet-metal"
+ // item_state = "sheet-metal"
lefthand_file = 'icons/mob/inhands/misc/sheets_lefthand.dmi'
righthand_file = 'icons/mob/inhands/misc/sheets_righthand.dmi'
throwforce = 0
diff --git a/code/modules/paperwork/pen.dm b/code/modules/paperwork/pen.dm
index 41e22de9e6..20c9c6211a 100644
--- a/code/modules/paperwork/pen.dm
+++ b/code/modules/paperwork/pen.dm
@@ -16,7 +16,7 @@
icon = 'icons/obj/bureaucracy.dmi'
icon_state = "pen"
item_state = "pen"
- // inhand_icon_state = "pen"
+ // item_state = "pen"
// worn_icon_state = "pen"
slot_flags = ITEM_SLOT_BELT | ITEM_SLOT_EARS
throwforce = 0
@@ -289,7 +289,7 @@
icon = 'icons/obj/bureaucracy.dmi'
icon_state = "digging_pen"
item_state = "pen"
- // inhand_icon_state = "pen"
+ // item_state = "pen"
// worn_icon_state = "pen"
force = 3
w_class = WEIGHT_CLASS_TINY
diff --git a/code/modules/paperwork/stamps.dm b/code/modules/paperwork/stamps.dm
index 104f70bfdf..1c3f76a676 100644
--- a/code/modules/paperwork/stamps.dm
+++ b/code/modules/paperwork/stamps.dm
@@ -4,7 +4,7 @@
icon = 'icons/obj/bureaucracy.dmi'
icon_state = "stamp-ok"
item_state = "stamp"
- // inhand_icon_state = "stamp"
+ // item_state = "stamp"
throwforce = 0
w_class = WEIGHT_CLASS_TINY
throw_speed = 3
diff --git a/code/modules/recycling/disposal/bin.dm b/code/modules/recycling/disposal/bin.dm
index 7fc57ee3a7..85b5cdac51 100644
--- a/code/modules/recycling/disposal/bin.dm
+++ b/code/modules/recycling/disposal/bin.dm
@@ -465,7 +465,7 @@
/obj/machinery/disposal/bin/get_remote_view_fullscreens(mob/user)
if(user.stat == DEAD || !(user.sight & (SEEOBJS|SEEMOBS)))
- user.overlay_fullscreen("remote_view", /atom/movable/screen/fullscreen/impaired, 2)
+ user.overlay_fullscreen("remote_view", /atom/movable/screen/fullscreen/scaled/impaired, 2)
//Delivery Chute
diff --git a/code/modules/research/anomaly/anomaly_core.dm b/code/modules/research/anomaly/anomaly_core.dm
index 7aeb7b3a9b..555647a82a 100644
--- a/code/modules/research/anomaly/anomaly_core.dm
+++ b/code/modules/research/anomaly/anomaly_core.dm
@@ -3,7 +3,7 @@
name = "anomaly core"
desc = "The neutralized core of an anomaly. It'd probably be valuable for research."
icon_state = "anomaly_core"
- //inhand_icon_state = "electronic"
+ //item_state = "electronic"
lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi'
righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi'
resistance_flags = FIRE_PROOF
diff --git a/code/modules/ruins/spaceruin_code/hilbertshotel.dm b/code/modules/ruins/spaceruin_code/hilbertshotel.dm
index 7109c87999..4a03844a33 100644
--- a/code/modules/ruins/spaceruin_code/hilbertshotel.dm
+++ b/code/modules/ruins/spaceruin_code/hilbertshotel.dm
@@ -302,7 +302,7 @@ GLOBAL_VAR_INIT(hhmysteryRoomNumber, 1337)
user.reset_perspective(parentSphere)
user.set_machine(src)
var/datum/action/peepholeCancel/PHC = new
- user.overlay_fullscreen("remote_view", /atom/movable/screen/fullscreen/impaired, 1)
+ user.overlay_fullscreen("remote_view", /atom/movable/screen/fullscreen/scaled/impaired, 1)
PHC.Grant(user)
return TRUE
diff --git a/code/modules/shuttle/docking.dm b/code/modules/shuttle/docking.dm
index 5c96bca07c..103e331039 100644
--- a/code/modules/shuttle/docking.dm
+++ b/code/modules/shuttle/docking.dm
@@ -162,7 +162,7 @@
for(var/i in 1 to areas_to_move.len)
CHECK_TICK
var/area/internal_area = areas_to_move[i]
- internal_area.afterShuttleMove(new_parallax_dir) //areas
+ internal_area.afterShuttleMove(new_parallax_dir, parallax_speed) //areas
for(var/i in 1 to old_turfs.len)
CHECK_TICK
diff --git a/code/modules/shuttle/emergency.dm b/code/modules/shuttle/emergency.dm
index cc37fbbcc4..2a15558e64 100644
--- a/code/modules/shuttle/emergency.dm
+++ b/code/modules/shuttle/emergency.dm
@@ -461,11 +461,11 @@
for(var/area/shuttle/escape/E in GLOB.sortedAreas)
areas += E
hyperspace_sound(HYPERSPACE_END, areas)
- if(time_left <= PARALLAX_LOOP_TIME)
+ if(time_left <= parallax_speed)
var/area_parallax = FALSE
for(var/place in shuttle_areas)
var/area/shuttle/shuttle_area = place
- if(shuttle_area.parallax_movedir)
+ if(shuttle_area.parallax_moving)
area_parallax = TRUE
break
if(area_parallax)
diff --git a/code/modules/shuttle/navigation_computer.dm b/code/modules/shuttle/navigation_computer.dm
index a215b58b55..029d50585a 100644
--- a/code/modules/shuttle/navigation_computer.dm
+++ b/code/modules/shuttle/navigation_computer.dm
@@ -356,7 +356,7 @@
playsound(console, 'sound/machines/terminal_prompt_confirm.ogg', 25, 0)
remote_eye.setLoc(T)
to_chat(target, "Jumped to [selected]")
- C.overlay_fullscreen("flash", /atom/movable/screen/fullscreen/flash/static)
+ C.overlay_fullscreen("flash", /atom/movable/screen/fullscreen/tiled/flash/static)
C.clear_fullscreen("flash", 3)
else
playsound(console, 'sound/machines/terminal_prompt_deny.ogg', 25, 0)
diff --git a/code/modules/shuttle/on_move.dm b/code/modules/shuttle/on_move.dm
index 8c1b3fa955..ceb9b7adea 100644
--- a/code/modules/shuttle/on_move.dm
+++ b/code/modules/shuttle/on_move.dm
@@ -117,8 +117,6 @@ All ShuttleMove procs go here
if(rotation)
shuttleRotate(rotation)
- update_parallax_contents()
-
return TRUE
/atom/movable/proc/lateShuttleMove(turf/oldT, list/movement_force, move_dir)
@@ -153,16 +151,21 @@ All ShuttleMove procs go here
//The old turf has now been given back to the area that turf originaly belonged to
var/area/old_dest_area = newT.loc
- parallax_movedir = old_dest_area.parallax_movedir
-
+ parallax_moving = old_dest_area.parallax_moving
+ parallax_move_angle = old_dest_area.parallax_move_angle
+ parallax_move_speed = old_dest_area.parallax_move_speed
old_dest_area.contents -= newT
contents += newT
newT.change_area(old_dest_area, src)
return TRUE
// Called on areas after everything has been moved
-/area/proc/afterShuttleMove(new_parallax_dir)
- parallax_movedir = new_parallax_dir
+/area/proc/afterShuttleMove(new_parallax_dir, speed)
+ if(!new_parallax_dir)
+ parallax_moving = FALSE
+ return
+ parallax_move_angle = dir2angle(new_parallax_dir)
+ parallax_move_speed = speed
return TRUE
/area/proc/lateShuttleMove()
@@ -309,6 +312,7 @@ All ShuttleMove procs go here
if(buckled)
shake_force *= 0.25
shake_camera(src, shake_force, 1)
+ client?.parallax_holder?.Reset(auto_z_change = FALSE, force = TRUE)
/mob/living/lateShuttleMove(turf/oldT, list/movement_force, move_dir)
if(buckled)
diff --git a/code/modules/shuttle/shuttle.dm b/code/modules/shuttle/shuttle.dm
index 0eb7f6ee20..d3e52410d6 100644
--- a/code/modules/shuttle/shuttle.dm
+++ b/code/modules/shuttle/shuttle.dm
@@ -367,6 +367,9 @@
var/can_move_docking_ports = FALSE
var/list/hidden_turfs = list()
+ /// parallax speed in seconds per loop
+ var/parallax_speed = 25
+
/obj/docking_port/mobile/register(replace = FALSE)
. = ..()
if(!id)
@@ -709,27 +712,24 @@
create_ripples(destination, tl)
var/obj/docking_port/stationary/S0 = get_docked()
- if(istype(S0, /obj/docking_port/stationary/transit) && timeLeft(1) <= PARALLAX_LOOP_TIME)
+ if(istype(S0, /obj/docking_port/stationary/transit) && timeLeft(1) <= parallax_speed)
+ var/parallax_ongoing = FALSE
for(var/place in shuttle_areas)
var/area/shuttle/shuttle_area = place
- if(shuttle_area.parallax_movedir)
- parallax_slowdown()
+ if(shuttle_area.parallax_moving)
+ parallax_ongoing = TRUE
+ if(parallax_ongoing)
+ parallax_slowdown()
/obj/docking_port/mobile/proc/parallax_slowdown()
- for(var/place in shuttle_areas)
- var/area/shuttle/shuttle_area = place
- shuttle_area.parallax_movedir = FALSE
- if(assigned_transit?.assigned_area)
- assigned_transit.assigned_area.parallax_movedir = FALSE
- var/list/L0 = return_ordered_turfs(x, y, z, dir)
- for (var/thing in L0)
- var/turf/T = thing
- if(!T || !istype(T.loc, area_type))
- continue
- for (var/thing2 in T)
- var/atom/movable/AM = thing2
- if (length(AM.client_mobs_in_contents))
- AM.update_parallax_contents()
+ for(var/mob/M in GLOB.player_list)
+ var/area/A = get_area(M)
+ if(A in shuttle_areas)
+ M.client?.parallax_holder?.StopScrolling(A.parallax_move_angle, parallax_speed)
+ for(var/area/shuttle_area in shuttle_areas + assigned_transit?.assigned_area)
+ shuttle_area.parallax_moving = FALSE
+ shuttle_area.parallax_move_speed = 0
+ shuttle_area.parallax_move_angle = 0
/obj/docking_port/mobile/proc/check_transit_zone()
if(assigned_transit)
diff --git a/code/modules/surgery/organs/eyes.dm b/code/modules/surgery/organs/eyes.dm
index 9861a1b639..06865a0f39 100644
--- a/code/modules/surgery/organs/eyes.dm
+++ b/code/modules/surgery/organs/eyes.dm
@@ -41,7 +41,7 @@
return
switch(eye_damaged)
if(BLURRY_VISION_ONE, BLURRY_VISION_TWO)
- owner.overlay_fullscreen("eye_damage", /atom/movable/screen/fullscreen/impaired, eye_damaged)
+ owner.overlay_fullscreen("eye_damage", /atom/movable/screen/fullscreen/scaled/impaired, eye_damaged)
if(BLIND_VISION_THREE)
owner.become_blind(EYE_DAMAGE)
if(ishuman(owner))
@@ -106,7 +106,7 @@
else if(eye_damaged == BLIND_VISION_THREE)
owner.become_blind(EYE_DAMAGE)
if(eye_damaged && eye_damaged != BLIND_VISION_THREE)
- owner.overlay_fullscreen("eye_damage", /atom/movable/screen/fullscreen/impaired, eye_damaged)
+ owner.overlay_fullscreen("eye_damage", /atom/movable/screen/fullscreen/scaled/impaired, eye_damaged)
else
owner.clear_fullscreen("eye_damage")
diff --git a/code/modules/vending/clothesmate.dm b/code/modules/vending/clothesmate.dm
index b2ac560849..67c376bb98 100644
--- a/code/modules/vending/clothesmate.dm
+++ b/code/modules/vending/clothesmate.dm
@@ -163,7 +163,65 @@
/obj/item/clothing/ears/headphones = 10,
/obj/item/clothing/suit/apron/purple_bartender = 4,
/obj/item/clothing/under/rank/civilian/bartender/purple = 4,
-
+ /obj/item/storage/backpack/henchmen = 20,
+ /obj/item/clothing/under/suit/henchmen = 20,
+ /obj/item/clothing/under/costume/yakuza = 20,
+ /obj/item/clothing/shoes/yakuza = 20,
+ /obj/item/clothing/suit/yakuza = 20,
+ /obj/item/clothing/under/costume/jackbros = 20,
+ /obj/item/clothing/shoes/jackbros = 20,
+ /obj/item/clothing/head/jackbros = 20,
+ /obj/item/clothing/under/costume/dutch = 20,
+ /obj/item/clothing/head/bowler = 20,
+ /obj/item/clothing/suit/dutch = 20,
+ /obj/item/clothing/suit/irs = 20,
+ /obj/item/clothing/under/costume/irs = 20,
+ /obj/item/clothing/head/irs = 20,
+ /obj/item/clothing/suit/osi = 20,
+ /obj/item/clothing/under/costume/osi = 20,
+ /obj/item/clothing/glasses/osi = 20,
+ /obj/item/clothing/suit/tmc = 20,
+ /obj/item/clothing/under/costume/tmc = 20,
+ /obj/item/clothing/head/tmc = 20,
+ /obj/item/clothing/suit/pg = 20,
+ /obj/item/clothing/under/costume/pg = 20,
+ /obj/item/clothing/head/pg = 20,
+ /obj/item/clothing/suit/driscoll = 20,
+ /obj/item/clothing/under/costume/driscoll = 20,
+ /obj/item/clothing/mask/gas/driscoll = 20,
+ /obj/item/clothing/suit/deckers = 20,
+ /obj/item/clothing/under/costume/deckers = 20,
+ /obj/item/clothing/head/deckers = 20,
+ /obj/item/clothing/shoes/deckers = 20,
+ /obj/item/clothing/suit/morningstar = 20,
+ /obj/item/clothing/under/costume/morningstar = 20,
+ /obj/item/clothing/head/morningstar = 20,
+ /obj/item/clothing/shoes/morningstar = 20,
+ /obj/item/clothing/suit/saints = 20,
+ /obj/item/clothing/under/costume/saints = 20,
+ /obj/item/clothing/head/saints = 20,
+ /obj/item/clothing/shoes/saints = 20,
+ /obj/item/clothing/suit/phantom = 20,
+ /obj/item/clothing/under/costume/phantom = 20,
+ /obj/item/clothing/glasses/phantom = 20,
+ /obj/item/clothing/shoes/phantom = 20,
+ /obj/item/clothing/suit/allies = 20,
+ /obj/item/clothing/under/costume/allies = 20,
+ /obj/item/clothing/head/allies = 20,
+ /obj/item/clothing/suit/soviet = 20,
+ /obj/item/clothing/under/costume/soviet_families = 20,
+ /obj/item/clothing/head/ushanka/soviet = 20,
+ /obj/item/clothing/suit/yuri = 20,
+ /obj/item/clothing/under/costume/yuri = 20,
+ /obj/item/clothing/head/yuri = 20,
+ /obj/item/clothing/suit/sybil_slickers = 20,
+ /obj/item/clothing/under/costume/sybil_slickers = 20,
+ /obj/item/clothing/head/sybil_slickers = 20,
+ /obj/item/clothing/shoes/sybil_slickers = 20,
+ /obj/item/clothing/suit/basil_boys = 20,
+ /obj/item/clothing/under/costume/basil_boys = 20,
+ /obj/item/clothing/head/basil_boys = 20,
+ /obj/item/clothing/shoes/basil_boys = 20,
/* Commenting out until next Christmas or made automatic
/obj/item/clothing/accessory/sweater/uglyxmas = 3,
/obj/item/clothing/under/costume/christmas = 3,
diff --git a/config/dynamic.json b/config/dynamic.json
index bed7b4b387..b229658e3a 100644
--- a/config/dynamic.json
+++ b/config/dynamic.json
@@ -65,6 +65,10 @@
"Clock Cult": {
"weight": 3
+ },
+
+ "Families": {
+ "weight": 3
}
},
@@ -117,6 +121,10 @@
"weight": 4
},
+ "Family Head Aspirants": {
+ "weight": 3
+ },
+
"Spiders": {
"weight": 0
}
diff --git a/html/changelogs/archive/2022-02.yml b/html/changelogs/archive/2022-02.yml
index af963dd2c0..d38f2b34a7 100644
--- a/html/changelogs/archive/2022-02.yml
+++ b/html/changelogs/archive/2022-02.yml
@@ -50,3 +50,27 @@
much better interface in TGUI
- qol: You can now lock and unlock bots, including their maintenance hatches, via
the UI.
+ silicons:
+ - refactor: clickcatchers, parallax
+2022-02-14:
+ keronshb:
+ - rscadd: Adds the new MRP version of Families
+ - rscadd: Adds the new Antag UI panel for Families, disables it for other antags
+ (for now)
+ - rscadd: Adds High Fives (needed for the Handshake)
+ - rscdel: Removed old Gang War
+ - bugfix: Fixed Observer Alerts and general alerts for give/take/restraints.
+ - soundadd: Adds related sounds for Families
+ - imageadd: Adds related icons for Families
+ - code_imp: Changed the petehat/gang to just petehat on the jungle away mission
+ - code_imp: Adds HAND_ITEM
+ - code_imp: A bunch of UI code for Antags/Families
+ - code_imp: Adds IS_DEAD_OR_INCAP
+ - code_imp: Adds GLOBAL_LIST_EMPTY(pre_setup_antags)
+ - code_imp: Adds Queued Deletions
+ - code_imp: Sanity check for attached_effect
+ - code_imp: Adds Suicide cries for antags
+ - code_imp: Adds preview_outfits for antags
+ - refactor: Refactors give/take > offer/receive
+ - config: Added Families to the dynamic config
+ - admin: Adds admin tp Family stuff
diff --git a/icons/effects/summon.dmi b/icons/effects/summon.dmi
new file mode 100644
index 0000000000..fefd79d845
Binary files /dev/null and b/icons/effects/summon.dmi differ
diff --git a/icons/mecha/mecha_96x96.dmi b/icons/mecha/mecha_96x96.dmi
new file mode 100644
index 0000000000..dbcd513359
Binary files /dev/null and b/icons/mecha/mecha_96x96.dmi differ
diff --git a/icons/mob/clothing/back.dmi b/icons/mob/clothing/back.dmi
index 8c34b6cc4e..5d27cf95d8 100644
Binary files a/icons/mob/clothing/back.dmi and b/icons/mob/clothing/back.dmi differ
diff --git a/icons/mob/clothing/eyes.dmi b/icons/mob/clothing/eyes.dmi
index 876159c258..49702bf920 100644
Binary files a/icons/mob/clothing/eyes.dmi and b/icons/mob/clothing/eyes.dmi differ
diff --git a/icons/mob/clothing/feet.dmi b/icons/mob/clothing/feet.dmi
index 42faf6cf98..9ca0762ca4 100644
Binary files a/icons/mob/clothing/feet.dmi and b/icons/mob/clothing/feet.dmi differ
diff --git a/icons/mob/clothing/hands.dmi b/icons/mob/clothing/hands.dmi
index 38f1d8e0af..ca8a7312fa 100644
Binary files a/icons/mob/clothing/hands.dmi and b/icons/mob/clothing/hands.dmi differ
diff --git a/icons/mob/clothing/head.dmi b/icons/mob/clothing/head.dmi
index 3b6af60828..fb0246e6e3 100644
Binary files a/icons/mob/clothing/head.dmi and b/icons/mob/clothing/head.dmi differ
diff --git a/icons/mob/clothing/mask.dmi b/icons/mob/clothing/mask.dmi
index 47ac700cbf..b0cbc3b623 100644
Binary files a/icons/mob/clothing/mask.dmi and b/icons/mob/clothing/mask.dmi differ
diff --git a/icons/mob/clothing/suit.dmi b/icons/mob/clothing/suit.dmi
index c3502badc9..8be696eb45 100644
Binary files a/icons/mob/clothing/suit.dmi and b/icons/mob/clothing/suit.dmi differ
diff --git a/icons/mob/clothing/under/costume.dmi b/icons/mob/clothing/under/costume.dmi
new file mode 100644
index 0000000000..1f55b13314
Binary files /dev/null and b/icons/mob/clothing/under/costume.dmi differ
diff --git a/icons/mob/clothing/uniform.dmi b/icons/mob/clothing/uniform.dmi
index 2ce33c7141..7f1717b8ca 100644
Binary files a/icons/mob/clothing/uniform.dmi and b/icons/mob/clothing/uniform.dmi differ
diff --git a/icons/mob/hud.dmi b/icons/mob/hud.dmi
index 1d2007fd63..32741d958b 100644
Binary files a/icons/mob/hud.dmi and b/icons/mob/hud.dmi differ
diff --git a/icons/obj/clothing/glasses.dmi b/icons/obj/clothing/glasses.dmi
index 4fce479a9d..1752892b60 100644
Binary files a/icons/obj/clothing/glasses.dmi and b/icons/obj/clothing/glasses.dmi differ
diff --git a/icons/obj/clothing/gloves.dmi b/icons/obj/clothing/gloves.dmi
index f6b15043f2..d4cd4b4f2f 100644
Binary files a/icons/obj/clothing/gloves.dmi and b/icons/obj/clothing/gloves.dmi differ
diff --git a/icons/obj/clothing/hats.dmi b/icons/obj/clothing/hats.dmi
index 3ebd15a511..25c93428a4 100644
Binary files a/icons/obj/clothing/hats.dmi and b/icons/obj/clothing/hats.dmi differ
diff --git a/icons/obj/clothing/masks.dmi b/icons/obj/clothing/masks.dmi
index 01ea212e6e..ebc7140dae 100644
Binary files a/icons/obj/clothing/masks.dmi and b/icons/obj/clothing/masks.dmi differ
diff --git a/icons/obj/clothing/shoes.dmi b/icons/obj/clothing/shoes.dmi
index f87eaefa98..d1e422eb6b 100644
Binary files a/icons/obj/clothing/shoes.dmi and b/icons/obj/clothing/shoes.dmi differ
diff --git a/icons/obj/clothing/suits.dmi b/icons/obj/clothing/suits.dmi
index 8e60dc7066..5493361129 100644
Binary files a/icons/obj/clothing/suits.dmi and b/icons/obj/clothing/suits.dmi differ
diff --git a/icons/obj/clothing/under/costume.dmi b/icons/obj/clothing/under/costume.dmi
new file mode 100644
index 0000000000..33d0aad418
Binary files /dev/null and b/icons/obj/clothing/under/costume.dmi differ
diff --git a/icons/obj/clothing/uniforms.dmi b/icons/obj/clothing/uniforms.dmi
index e7467d6352..3a5dfb97a9 100644
Binary files a/icons/obj/clothing/uniforms.dmi and b/icons/obj/clothing/uniforms.dmi differ
diff --git a/icons/obj/gang/actions.dmi b/icons/obj/gang/actions.dmi
new file mode 100644
index 0000000000..c8996ccd6f
Binary files /dev/null and b/icons/obj/gang/actions.dmi differ
diff --git a/icons/obj/gang/cell_phone.dmi b/icons/obj/gang/cell_phone.dmi
new file mode 100644
index 0000000000..40a5c33cbe
Binary files /dev/null and b/icons/obj/gang/cell_phone.dmi differ
diff --git a/icons/obj/gang/signup_points.dmi b/icons/obj/gang/signup_points.dmi
new file mode 100644
index 0000000000..2bda4b8ce1
Binary files /dev/null and b/icons/obj/gang/signup_points.dmi differ
diff --git a/icons/obj/gang/tags.dmi b/icons/obj/gang/tags.dmi
new file mode 100644
index 0000000000..61059c47e4
Binary files /dev/null and b/icons/obj/gang/tags.dmi differ
diff --git a/icons/obj/gang/wanted_160x32.dmi b/icons/obj/gang/wanted_160x32.dmi
new file mode 100644
index 0000000000..c25f5758d4
Binary files /dev/null and b/icons/obj/gang/wanted_160x32.dmi differ
diff --git a/icons/obj/gang/wanted_stars.dmi b/icons/obj/gang/wanted_stars.dmi
new file mode 100644
index 0000000000..8fd9205b56
Binary files /dev/null and b/icons/obj/gang/wanted_stars.dmi differ
diff --git a/icons/obj/storage.dmi b/icons/obj/storage.dmi
index 1925834ebb..351aeb3546 100644
Binary files a/icons/obj/storage.dmi and b/icons/obj/storage.dmi differ
diff --git a/icons/screen/clickcatcher.dmi b/icons/screen/clickcatcher.dmi
new file mode 100644
index 0000000000..50a3616991
Binary files /dev/null and b/icons/screen/clickcatcher.dmi differ
diff --git a/icons/screen/fullscreen_15x15.dmi b/icons/screen/fullscreen_15x15.dmi
new file mode 100644
index 0000000000..502e9ad3f9
Binary files /dev/null and b/icons/screen/fullscreen_15x15.dmi differ
diff --git a/icons/screen/fullscreen_tiled.dmi b/icons/screen/fullscreen_tiled.dmi
new file mode 100644
index 0000000000..11b70842ec
Binary files /dev/null and b/icons/screen/fullscreen_tiled.dmi differ
diff --git a/icons/screen/parallax.dmi b/icons/screen/parallax.dmi
new file mode 100644
index 0000000000..36dd36ee7a
Binary files /dev/null and b/icons/screen/parallax.dmi differ
diff --git a/interface/stylesheet.dm b/interface/stylesheet.dm
index dea6da4254..292f2fc4d9 100644
--- a/interface/stylesheet.dm
+++ b/interface/stylesheet.dm
@@ -58,6 +58,9 @@ em {font-style: normal; font-weight: bold;}
.aiprivradio {color: #ff00ff;}
.redteamradio {color: #ff0000;}
.blueteamradio {color: #0000ff;}
+.greenteamradio {color: #00ff00;}
+.yellowteamradio {color: #d1ba22;}
+.gangradio {color: #ac2ea1;}
.yell { font-weight: bold;}
diff --git a/sound/ambience/antag/thatshowfamiliesworks.ogg b/sound/ambience/antag/thatshowfamiliesworks.ogg
new file mode 100644
index 0000000000..4ecad617e7
Binary files /dev/null and b/sound/ambience/antag/thatshowfamiliesworks.ogg differ
diff --git a/sound/effects/families_police.ogg b/sound/effects/families_police.ogg
new file mode 100644
index 0000000000..38940f3f0a
Binary files /dev/null and b/sound/effects/families_police.ogg differ
diff --git a/tgstation.dme b/tgstation.dme
index 50a44347a4..4328158bfc 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -28,7 +28,6 @@
#include "code\__DEFINES\admin.dm"
#include "code\__DEFINES\antagonists.dm"
#include "code\__DEFINES\atmospherics.dm"
-#include "code\__DEFINES\atom_hud.dm"
#include "code\__DEFINES\blackmarket.dm"
#include "code\__DEFINES\botany.dm"
#include "code\__DEFINES\callbacks.dm"
@@ -37,7 +36,6 @@
#include "code\__DEFINES\citadel_defines.dm"
#include "code\__DEFINES\cleaning.dm"
#include "code\__DEFINES\clockcult.dm"
-#include "code\__DEFINES\colors.dm"
#include "code\__DEFINES\combat.dm"
#include "code\__DEFINES\configuration.dm"
#include "code\__DEFINES\construction.dm"
@@ -144,6 +142,9 @@
#include "code\__DEFINES\_flags\shields.dm"
#include "code\__DEFINES\admin\keybindings.dm"
#include "code\__DEFINES\chemistry\reactions.dm"
+#include "code\__DEFINES\color\color_priority.dm"
+#include "code\__DEFINES\color\colors.dm"
+#include "code\__DEFINES\color\lum.dm"
#include "code\__DEFINES\combat\attack_types.dm"
#include "code\__DEFINES\combat\block.dm"
#include "code\__DEFINES\combat\block_parry.dm"
@@ -155,6 +156,8 @@
#include "code\__DEFINES\material\worth.dm"
#include "code\__DEFINES\mobs\innate_abilities.dm"
#include "code\__DEFINES\mobs\slowdowns.dm"
+#include "code\__DEFINES\rendering\atom_hud.dm"
+#include "code\__DEFINES\rendering\parallax.dm"
#include "code\__DEFINES\research\anomalies.dm"
#include "code\__DEFINES\research\stock_parts.dm"
#include "code\__DEFINES\skills\defines.dm"
@@ -187,7 +190,6 @@
#include "code\__HELPERS\level_traits.dm"
#include "code\__HELPERS\lighting.dm"
#include "code\__HELPERS\markov.dm"
-#include "code\__HELPERS\matrices.dm"
#include "code\__HELPERS\mobs.dm"
#include "code\__HELPERS\mouse_control.dm"
#include "code\__HELPERS\names.dm"
@@ -212,6 +214,8 @@
#include "code\__HELPERS\verbs.dm"
#include "code\__HELPERS\view.dm"
#include "code\__HELPERS\yelling.dm"
+#include "code\__HELPERS\matrices\color_matrix.dm"
+#include "code\__HELPERS\matrices\transform_matrix.dm"
#include "code\__HELPERS\sorts\__main.dm"
#include "code\__HELPERS\sorts\InsertSort.dm"
#include "code\__HELPERS\sorts\MergeSort.dm"
@@ -267,7 +271,7 @@
#include "code\_onclick\hud\credits.dm"
#include "code\_onclick\hud\devil.dm"
#include "code\_onclick\hud\drones.dm"
-#include "code\_onclick\hud\fullscreen.dm"
+#include "code\_onclick\hud\families.dm"
#include "code\_onclick\hud\generic_dextrous.dm"
#include "code\_onclick\hud\ghost.dm"
#include "code\_onclick\hud\guardian.dm"
@@ -277,7 +281,6 @@
#include "code\_onclick\hud\map_popups.dm"
#include "code\_onclick\hud\monkey.dm"
#include "code\_onclick\hud\movable_screen_objects.dm"
-#include "code\_onclick\hud\parallax.dm"
#include "code\_onclick\hud\picture_in_picture.dm"
#include "code\_onclick\hud\plane_master.dm"
#include "code\_onclick\hud\radial.dm"
@@ -292,6 +295,16 @@
#include "code\_onclick\hud\screen_objects\stamina.dm"
#include "code\_onclick\hud\screen_objects\storage.dm"
#include "code\_onclick\hud\screen_objects\vore.dm"
+#include "code\_rendering\mob.dm"
+#include "code\_rendering\atom_huds\alternate_appearance.dm"
+#include "code\_rendering\atom_huds\atom_hud.dm"
+#include "code\_rendering\atom_huds\data_huds.dm"
+#include "code\_rendering\clickcatcher\clickcatcher.dm"
+#include "code\_rendering\fullscreen\fullscreen.dm"
+#include "code\_rendering\parallax\parallax.dm"
+#include "code\_rendering\parallax\parallax_holder.dm"
+#include "code\_rendering\parallax\parallax_object.dm"
+#include "code\_rendering\parallax\types\space.dm"
#include "code\controllers\admin.dm"
#include "code\controllers\configuration_citadel.dm"
#include "code\controllers\controller.dm"
@@ -442,7 +455,6 @@
#include "code\datums\forced_movement.dm"
#include "code\datums\holocall.dm"
#include "code\datums\http.dm"
-#include "code\datums\hud.dm"
#include "code\datums\mind.dm"
#include "code\datums\mutable_appearance.dm"
#include "code\datums\numbered_display.dm"
@@ -783,12 +795,10 @@
#include "code\datums\wounds\loss.dm"
#include "code\datums\wounds\pierce.dm"
#include "code\datums\wounds\slash.dm"
-#include "code\game\alternate_appearance.dm"
#include "code\game\atoms.dm"
#include "code\game\atoms_movable.dm"
#include "code\game\atoms_movement.dm"
#include "code\game\communications.dm"
-#include "code\game\data_huds.dm"
#include "code\game\say.dm"
#include "code\game\shuttle_engines.dm"
#include "code\game\sound.dm"
@@ -837,17 +847,7 @@
#include "code\game\gamemodes\dynamic\ruleset_picking.dm"
#include "code\game\gamemodes\eldritch_cult\eldritch_cult.dm"
#include "code\game\gamemodes\extended\extended.dm"
-#include "code\game\gamemodes\gangs\dominator.dm"
-#include "code\game\gamemodes\gangs\dominator_countdown.dm"
-#include "code\game\gamemodes\gangs\gang.dm"
-#include "code\game\gamemodes\gangs\gang_datums.dm"
-#include "code\game\gamemodes\gangs\gang_decals.dm"
-#include "code\game\gamemodes\gangs\gang_hud.dm"
-#include "code\game\gamemodes\gangs\gang_items.dm"
-#include "code\game\gamemodes\gangs\gang_pen.dm"
-#include "code\game\gamemodes\gangs\gangs.dm"
-#include "code\game\gamemodes\gangs\gangtool.dm"
-#include "code\game\gamemodes\gangs\implant_gang.dm"
+#include "code\game\gamemodes\gang\gang.dm"
#include "code\game\gamemodes\meteor\meteor.dm"
#include "code\game\gamemodes\meteor\meteors.dm"
#include "code\game\gamemodes\monkey\monkey.dm"
@@ -1009,6 +1009,7 @@
#include "code\game\mecha\mecha_wreckage.dm"
#include "code\game\mecha\combat\combat.dm"
#include "code\game\mecha\combat\durand.dm"
+#include "code\game\mecha\combat\five_stars.dm"
#include "code\game\mecha\combat\gygax.dm"
#include "code\game\mecha\combat\honker.dm"
#include "code\game\mecha\combat\marauder.dm"
@@ -1165,6 +1166,7 @@
#include "code\game\objects\items\singularityhammer.dm"
#include "code\game\objects\items\spear.dm"
#include "code\game\objects\items\stunbaton.dm"
+#include "code\game\objects\items\summon.dm"
#include "code\game\objects\items\taster.dm"
#include "code\game\objects\items\teleportation.dm"
#include "code\game\objects\items\teleprod.dm"
@@ -1749,6 +1751,12 @@
#include "code\modules\antagonists\fugitive\fugitive_outfits.dm"
#include "code\modules\antagonists\fugitive\fugitive_ship.dm"
#include "code\modules\antagonists\fugitive\hunter.dm"
+#include "code\modules\antagonists\gang\cellphone.dm"
+#include "code\modules\antagonists\gang\gang.dm"
+#include "code\modules\antagonists\gang\handler.dm"
+#include "code\modules\antagonists\gang\induction_package.dm"
+#include "code\modules\antagonists\gang\outfits.dm"
+#include "code\modules\antagonists\gang\themes.dm"
#include "code\modules\antagonists\greentext\greentext.dm"
#include "code\modules\antagonists\greybois\greybois.dm"
#include "code\modules\antagonists\highlander\highlander.dm"
diff --git a/tgui/packages/tgui-panel/chat/constants.js b/tgui/packages/tgui-panel/chat/constants.js
index a15497f2da..a9adb0ed52 100644
--- a/tgui/packages/tgui-panel/chat/constants.js
+++ b/tgui/packages/tgui-panel/chat/constants.js
@@ -58,7 +58,7 @@ export const MESSAGE_TYPES = [
type: MESSAGE_TYPE_RADIO,
name: 'Radio',
description: 'All departments of radio messages',
- selector: '.alert, .minorannounce, .syndradio, .centcomradio, .aiprivradio, .comradio, .secradio, .engradio, .medradio, .sciradio, .suppradio, .servradio, .radio, .deptradio, .binarysay, .newscaster, .resonate',
+ selector: '.alert, .minorannounce, .syndradio, .centcomradio, .aiprivradio, .comradio, .secradio, .gangradio, .engradio, .medradio, .sciradio, .suppradio, .servradio, .radio, .deptradio, .binarysay, .newscaster, .resonate',
},
{
type: MESSAGE_TYPE_INFO,
diff --git a/tgui/packages/tgui-panel/styles/goon/chat-dark.scss b/tgui/packages/tgui-panel/styles/goon/chat-dark.scss
index f42268f7b0..4406d24015 100644
--- a/tgui/packages/tgui-panel/styles/goon/chat-dark.scss
+++ b/tgui/packages/tgui-panel/styles/goon/chat-dark.scss
@@ -396,6 +396,10 @@ em {
color: #8f4a4b;
}
+.gangradio {
+ color: #ac2ea1;
+}
+
.centcomradio {
color: #2681a5;
}
diff --git a/tgui/packages/tgui-panel/styles/goon/chat-light.scss b/tgui/packages/tgui-panel/styles/goon/chat-light.scss
index 1b4a055bb5..e94d2a4e4b 100644
--- a/tgui/packages/tgui-panel/styles/goon/chat-light.scss
+++ b/tgui/packages/tgui-panel/styles/goon/chat-light.scss
@@ -414,6 +414,10 @@ em {
color: #6d3f40;
}
+.gangradio {
+ color: #ac2ea1;
+}
+
.centcomradio {
color: #686868;
}
diff --git a/tgui/packages/tgui/interfaces/AntagInfoGangmember.tsx b/tgui/packages/tgui/interfaces/AntagInfoGangmember.tsx
new file mode 100644
index 0000000000..5b0e09c286
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/AntagInfoGangmember.tsx
@@ -0,0 +1,136 @@
+import { useBackend } from '../backend';
+import { BlockQuote, Icon, Section, Stack } from '../components';
+import { Window } from '../layouts';
+
+type Info = {
+ antag_name: string;
+ gang_name: string;
+ gang_objective: string;
+ gang_clothes: string[];
+};
+
+export const AntagInfoGangmember = (props, context) => {
+ const { data } = useBackend(context);
+ const {
+ gang_name,
+ antag_name,
+ } = data;
+ return (
+
+
+
+
+
+ {gang_name} for life! You are a {antag_name}!
+
+
+ As a gang member, support your family above all!
+ Tag turf with a spraycan, wear your family's clothes,
+ induct new members with induction packages,
+ and accomplish your family objective.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+const GangClothesPrintout = (props, context) => {
+ const { data } = useBackend(context);
+ const {
+ gang_name,
+ gang_clothes,
+ } = data;
+ return (
+
+
+
+
+
+
+
+ Wear the following to represent the {gang_name}:
+
+
+
+
+ {gang_clothes && gang_clothes.length ? (
+ gang_clothes.map(clothes_item => (
+
+ - {clothes_item}
+
+ ))
+ ) : (
+ "- Anything!"
+ )}
+
+
+ );
+};
+
+const GangPhonePrintout = () => {
+ return (
+
+
+
+
+
+
+
+ You were given a cell phone with your induction package!
+
+
+
+
+
+ Use it in hand to activate it, then speak into it to talk with
+ your other family members.
+
+
+
+ );
+};
+
+
+const GangObjectivePrintout = (props, context) => {
+ const { data } = useBackend(context);
+ const {
+ gang_objective,
+ } = data;
+ return (
+
+
+ Your family's goal:
+
+
+ {gang_objective
+ || "No objective set! This is a problem!"}
+
+
+ );
+};
diff --git a/tools/ci/install_rust_g.sh b/tools/ci/install_rust_g.sh
old mode 100644
new mode 100755