diff --git a/_maps/map_files/BoxStation/BoxStation.dmm b/_maps/map_files/BoxStation/BoxStation.dmm
index bba92be7a7..dcd2c47cb2 100644
--- a/_maps/map_files/BoxStation/BoxStation.dmm
+++ b/_maps/map_files/BoxStation/BoxStation.dmm
@@ -27901,8 +27901,8 @@
pixel_x = 30
},
/obj/machinery/light,
-/mob/living/simple_animal/bot/cleanbot{
- name = "C.L.E.A.N."
+/obj/machinery/computer/crew{
+ dir = 1
},
/turf/open/floor/plasteel/white,
/area/medical/medbay/central)
@@ -31596,7 +31596,7 @@
/area/medical/sleeper)
"bwC" = (
/obj/machinery/computer/med_data{
- dir = 4
+ dir = 3
},
/turf/open/floor/plasteel/white,
/area/medical/sleeper)
@@ -56882,6 +56882,12 @@
/obj/item/pen,
/turf/open/floor/plasteel/white,
/area/science/circuit)
+"ium" = (
+/mob/living/simple_animal/bot/cleanbot{
+ name = "C.L.E.A.N."
+ },
+/turf/open/floor/plasteel/white,
+/area/medical/medbay/central)
"izv" = (
/obj/machinery/vending/clothing,
/obj/machinery/light/small{
@@ -94182,7 +94188,7 @@ blm
bmL
boi
bpw
-bhh
+ium
bsx
btX
bvj
diff --git a/code/__DEFINES/antagonists.dm b/code/__DEFINES/antagonists.dm
index 4c454aa61a..e35defd485 100644
--- a/code/__DEFINES/antagonists.dm
+++ b/code/__DEFINES/antagonists.dm
@@ -35,3 +35,8 @@
//Overthrow time to update heads obj
#define OBJECTIVE_UPDATING_TIME 300
+
+//Gangshit
+#define NOT_DOMINATING -1
+#define MAX_LEADERS_GANG 3
+#define INITIAL_DOM_ATTEMPTS 3
diff --git a/code/__DEFINES/citadel_defines.dm b/code/__DEFINES/citadel_defines.dm
index 9f5f52180c..2abe0db04e 100644
--- a/code/__DEFINES/citadel_defines.dm
+++ b/code/__DEFINES/citadel_defines.dm
@@ -33,8 +33,8 @@
#define BALLS_VOLUME_MULT 1
#define BALLS_SIZE_MIN 1
-#define BALLS_SIZE_DEF 3
-#define BALLS_SIZE_MAX 7
+#define BALLS_SIZE_DEF 2
+#define BALLS_SIZE_MAX 3
#define BALLS_SACK_SIZE_MIN 1
#define BALLS_SACK_SIZE_DEF 8
diff --git a/code/__DEFINES/components.dm b/code/__DEFINES/components.dm
index 3338fc1cda..dbe8cfbb62 100644
--- a/code/__DEFINES/components.dm
+++ b/code/__DEFINES/components.dm
@@ -142,8 +142,9 @@
#define COMSIG_CARBON_SOUNDBANG "carbon_soundbang" //from base of mob/living/carbon/soundbang_act(): (list(intensity))
// /obj signals
-#define COMSIG_OBJ_DECONSTRUCT "obj_deconstruct" //from base of obj/deconstruct(): (disassembled)
-#define COMSIG_OBJ_SETANCHORED "obj_setanchored" //called in /obj/structure/setAnchored(): (value)
+#define COMSIG_OBJ_DECONSTRUCT "obj_deconstruct" //from base of obj/deconstruct(): (disassembled)
+#define COMSIG_OBJ_BREAK "obj_break" //from base of /obj/obj_break(): (damage_flag)
+#define COMSIG_OBJ_SETANCHORED "obj_setanchored" //called in /obj/structure/setAnchored(): (value)
// /obj/item signals
#define COMSIG_ITEM_ATTACK "item_attack" //from base of obj/item/attack(): (/mob/living/target, /mob/living/user)
diff --git a/code/__DEFINES/role_preferences.dm b/code/__DEFINES/role_preferences.dm
index 6773d55e53..b316c003a9 100644
--- a/code/__DEFINES/role_preferences.dm
+++ b/code/__DEFINES/role_preferences.dm
@@ -33,7 +33,8 @@
#define ROLE_DRONE "drone"
#define ROLE_DEATHSQUAD "deathsquad"
#define ROLE_LAVALAND "lavaland"
-#define ROLE_INTERNAL_AFFAIRS "internal affairs agent"
+#define ROLE_INTERNAL_AFFAIRS "internal affairs agent"
+#define ROLE_GANG "gangster"
//Missing assignment means it's not a gamemode specific role, IT'S NOT A BUG OR ERROR.
//The gamemode specific ones are just so the gamemodes can query whether a player is old enough
@@ -58,7 +59,8 @@ GLOBAL_LIST_INIT(special_roles, list(
ROLE_SERVANT_OF_RATVAR = /datum/game_mode/clockwork_cult,
ROLE_OVERTHROW = /datum/game_mode/overthrow,
ROLE_INTERNAL_AFFAIRS = /datum/game_mode/traitor/internal_affairs,
- ROLE_SENTIENCE
+ ROLE_SENTIENCE,
+ ROLE_GANG = /datum/game_mode/gang
))
//Job defines for what happens when you fail to qualify for any job during job selection
diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm
index 805e72cfcb..5c2deae875 100644
--- a/code/__DEFINES/traits.dm
+++ b/code/__DEFINES/traits.dm
@@ -71,6 +71,8 @@
#define TRAIT_MUSICIAN "musician"
#define TRAIT_CROCRIN_IMMUNE "crocin_immune"
#define TRAIT_NYMPHO "nymphomania"
+#define TRAIT_MASO "masochism"
+#define TRAIT_ASSBLASTUSA "assblastusa"
// common trait sources
#define TRAIT_GENERIC "generic"
diff --git a/code/__HELPERS/_cit_helpers.dm b/code/__HELPERS/_cit_helpers.dm
index eb2a564d1b..668b151b6e 100644
--- a/code/__HELPERS/_cit_helpers.dm
+++ b/code/__HELPERS/_cit_helpers.dm
@@ -58,8 +58,11 @@ GLOBAL_LIST_EMPTY(ipc_antennas_list)
//Genitals and Arousal Lists
GLOBAL_LIST_EMPTY(cock_shapes_list)//global_lists.dm for the list initializations //Now also _DATASTRUCTURES globals.dm
GLOBAL_LIST_EMPTY(cock_shapes_icons) //Associated list for names->icon_states for cockshapes.
+GLOBAL_LIST_EMPTY(balls_shapes_list)
+GLOBAL_LIST_EMPTY(balls_shapes_icons)
GLOBAL_LIST_EMPTY(breasts_size_list)
GLOBAL_LIST_EMPTY(breasts_shapes_list)
+GLOBAL_LIST_EMPTY(breasts_shapes_icons)
GLOBAL_LIST_EMPTY(vagina_shapes_list)
GLOBAL_LIST_INIT(cum_into_containers_list, list(/obj/item/reagent_containers/food/snacks/pie)) //Yer fuggin snowflake name list jfc
GLOBAL_LIST_INIT(dick_nouns, list("dick","cock","member","shaft"))
@@ -123,36 +126,36 @@ GLOBAL_VAR_INIT(miscreants_allowed, FALSE)
/mob/living/carbon/proc/has_penis()
if(getorganslot("penis"))//slot shared with ovipositor
if(istype(getorganslot("penis"), /obj/item/organ/genital/penis))
- return 1
- return 0
+ return TRUE
+ return FALSE
/mob/living/carbon/proc/has_balls()
if(getorganslot("balls"))
if(istype(getorganslot("balls"), /obj/item/organ/genital/testicles))
- return 1
- return 0
+ return TRUE
+ return FALSE
/mob/living/carbon/proc/has_vagina()
if(getorganslot("vagina"))
- return 1
- return 0
+ return TRUE
+ return FALSE
/mob/living/carbon/proc/has_breasts()
if(getorganslot("breasts"))
- return 1
- return 0
+ return TRUE
+ return FALSE
/mob/living/carbon/proc/has_ovipositor()
if(getorganslot("penis"))//shared slot
if(istype(getorganslot("penis"), /obj/item/organ/genital/ovipositor))
- return 1
- return 0
+ return TRUE
+ return FALSE
/mob/living/carbon/human/proc/has_eggsack()
if(getorganslot("balls"))
if(istype(getorganslot("balls"), /obj/item/organ/genital/eggsack))
- return 1
- return 0
+ return TRUE
+ return FALSE
/mob/living/carbon/human/proc/is_bodypart_exposed(bodypart)
@@ -161,16 +164,16 @@ GLOBAL_VAR_INIT(miscreants_allowed, FALSE)
L = get_equipped_items()
for(var/obj/item/I in L)
if(I.body_parts_covered & GROIN)
- return 0
- return 1
+ return FALSE
+ return TRUE
/mob/living/carbon/proc/is_chest_exposed(var/list/L)
if(!L)
L = get_equipped_items()
for(var/obj/item/I in L)
if(I.body_parts_covered & CHEST)
- return 0
- return 1
+ return FALSE
+ return TRUE
////////////////////////
//DANGER | DEBUG PROCS//
@@ -191,40 +194,3 @@ GLOBAL_VAR_INIT(miscreants_allowed, FALSE)
H.give_vagina()
H.give_womb()
H.give_breasts()
-
-/client/proc/test_mammal_overlays()
- set name = "Mass Give Mammalitus"
- set category = "Dangerous"
- set desc = "Turns every human into a mammal with tails, ears, etc. WARNING: NOT FOR LIVE SERVER USAGE!!"
-
- log_admin("[src] turned everyone into mammals.")
- message_admins("[src] turned everyone into mammals.")
- for(var/mob/living/carbon/human/H in GLOB.mob_list)
- if(!H.dna)
- continue
- var/datum/dna/hdna = H.dna
- H.set_species(/datum/species/mammal)
- var/subspec = pick("Fox","Wolf","Fennec")
- switch(subspec)
- if("Wolf")
- hdna.features["mam_tail"] = "Wolf"
- hdna.features["mam_ears"] = "Wolf"
- hdna.features["mam_snouts"] = "Wolf"
- hdna.features["mam_body_markings"] = "Wolf"
- hdna.features["mcolor"] = "555"
- hdna.features["mcolor2"] = "999"
- hdna.features["mcolor3"] = "999"
- if("Fox")
- hdna.features["mam_tail"] = "Fox"
- hdna.features["mam_ears"] = "Fox"
- hdna.features["mam_snouts"] = "Fox, Long"
- hdna.features["mam_body_markings"] = "Fox"
- hdna.features["mcolor"] = "f60"
- hdna.features["mcolor2"] = "fff"
- hdna.features["mcolor3"] = "fff"
- if("Fennec")
- hdna.features["mam_tail"] = "Fennec"
- hdna.features["mam_ears"] = "Fennec"
- hdna.features["mam_snouts"] = "Fox, Short"
- hdna.features["mam_body_markings"] = "Fox"
- H.regenerate_icons()
diff --git a/code/__HELPERS/global_lists.dm b/code/__HELPERS/global_lists.dm
index f6ef08fe0d..ac113b4e08 100644
--- a/code/__HELPERS/global_lists.dm
+++ b/code/__HELPERS/global_lists.dm
@@ -46,7 +46,6 @@
init_sprite_accessory_subtypes(/datum/sprite_accessory/xeno_dorsal, GLOB.xeno_dorsal_list)
//genitals
init_sprite_accessory_subtypes(/datum/sprite_accessory/penis, GLOB.cock_shapes_list)
-
for(var/K in GLOB.cock_shapes_list)
var/datum/sprite_accessory/penis/value = GLOB.cock_shapes_list[K]
GLOB.cock_shapes_icons[K] = value.icon_state
@@ -54,6 +53,14 @@
init_sprite_accessory_subtypes(/datum/sprite_accessory/vagina, GLOB.vagina_shapes_list)
init_sprite_accessory_subtypes(/datum/sprite_accessory/breasts, GLOB.breasts_shapes_list)
GLOB.breasts_size_list = list("a","b","c","d","e") //We need the list to choose from initialized, but it's no longer a sprite_accessory thing.
+ for(var/K in GLOB.breasts_shapes_list)
+ var/datum/sprite_accessory/breasts/value = GLOB.breasts_shapes_list[K]
+ GLOB.breasts_shapes_icons[K] = value.icon_state
+
+ init_sprite_accessory_subtypes(/datum/sprite_accessory/testicles, GLOB.balls_shapes_list)
+ for(var/K in GLOB.balls_shapes_list)
+ var/datum/sprite_accessory/testicles/value = GLOB.balls_shapes_list[K]
+ GLOB.balls_shapes_icons[K] = value.icon_state
//END OF CIT CHANGES
//Species
diff --git a/code/__HELPERS/mobs.dm b/code/__HELPERS/mobs.dm
index 34a34273dc..51c814a5f9 100644
--- a/code/__HELPERS/mobs.dm
+++ b/code/__HELPERS/mobs.dm
@@ -77,6 +77,8 @@
//CIT CHANGES - genitals and such
if(!GLOB.cock_shapes_list.len)
init_sprite_accessory_subtypes(/datum/sprite_accessory/penis, GLOB.cock_shapes_list)
+ if(!GLOB.balls_shapes_list.len)
+ init_sprite_accessory_subtypes(/datum/sprite_accessory/testicles, GLOB.balls_shapes_list)
if(!GLOB.vagina_shapes_list.len)
init_sprite_accessory_subtypes(/datum/sprite_accessory/vagina, GLOB.vagina_shapes_list)
if(!GLOB.breasts_shapes_list.len)
@@ -168,6 +170,7 @@
"balls_amount" = 2,
"balls_sack_size" = BALLS_SACK_SIZE_DEF,
"balls_size" = BALLS_SIZE_DEF,
+ "balls_shape" = "Pair",
"balls_cum_rate" = CUM_RATE,
"balls_cum_mult" = CUM_RATE_MULT,
"balls_efficiency" = CUM_EFFICIENCY,
@@ -185,7 +188,7 @@
"has_breasts" = FALSE,
"breasts_color" = pick("FFFFFF","7F7F7F", "7FFF7F", "7F7FFF", "FF7F7F", "7FFFFF", "FF7FFF", "FFFF7F"),
"breasts_size" = pick(GLOB.breasts_size_list),
- "breasts_shape" = pick(GLOB.breasts_shapes_list),
+ "breasts_shape" = "Pair",
"breasts_fluid" = "milk",
"has_vag" = FALSE,
"vag_shape" = pick(GLOB.vagina_shapes_list),
diff --git a/code/datums/components/storage/concrete/_concrete.dm b/code/datums/components/storage/concrete/_concrete.dm
index 4d7e8bddc5..0b78605869 100644
--- a/code/datums/components/storage/concrete/_concrete.dm
+++ b/code/datums/components/storage/concrete/_concrete.dm
@@ -6,6 +6,8 @@
/datum/component/storage/concrete
var/drop_all_on_deconstruct = TRUE
var/drop_all_on_destroy = FALSE
+ var/drop_all_on_break = FALSE
+ var/unlock_on_break = FALSE
var/transfer_contents_on_component_transfer = FALSE
var/list/datum/component/storage/slaves = list()
@@ -16,6 +18,7 @@
. = ..()
RegisterSignal(parent, COMSIG_ATOM_CONTENTS_DEL, .proc/on_contents_del)
RegisterSignal(parent, COMSIG_OBJ_DECONSTRUCT, .proc/on_deconstruct)
+ RegisterSignal(parent, COMSIG_OBJ_BREAK, .proc/on_break)
/datum/component/storage/concrete/Destroy()
var/atom/real_location = real_location()
@@ -100,6 +103,12 @@
if(drop_all_on_deconstruct)
do_quick_empty()
+/datum/component/storage/concrete/proc/on_break(datum/source, damage_flag)
+ if(drop_all_on_break)
+ do_quick_empty()
+ if(unlock_on_break)
+ set_locked(source, FALSE)
+
/datum/component/storage/concrete/can_see_contents()
. = ..()
for(var/i in slaves)
diff --git a/code/datums/components/storage/concrete/emergency.dm b/code/datums/components/storage/concrete/emergency.dm
new file mode 100644
index 0000000000..e5f9d83a17
--- /dev/null
+++ b/code/datums/components/storage/concrete/emergency.dm
@@ -0,0 +1,22 @@
+/datum/component/storage/concrete/emergency
+ drop_all_on_break = TRUE
+ unlock_on_break = TRUE
+ locked = TRUE
+
+/datum/component/storage/concrete/emergency/Initialize()
+ . = ..()
+ RegisterSignal(parent, COMSIG_ATOM_EMAG_ACT, .proc/unlock_me)
+
+/datum/component/storage/concrete/emergency/signal_insertion_attempt(datum/source, obj/item/I, mob/M, silent = FALSE, force = FALSE)
+ if(!silent && istype(I, /obj/item/card/emag))
+ silent = TRUE // suppresses the message
+ return ..()
+
+/datum/component/storage/concrete/check_locked(datum/source, mob/user, message = FALSE)
+ . = locked && GLOB.security_level < SEC_LEVEL_RED
+ if(message && . && user)
+ to_chat(user, "The storage unit will only unlock during a Red or Delta security alert.")
+
+/datum/component/storage/concrete/emergency/proc/unlock_me(datum/source)
+ if(locked)
+ set_locked(source, FALSE)
diff --git a/code/datums/components/storage/storage.dm b/code/datums/components/storage/storage.dm
index dc25729e37..869fe59ae9 100644
--- a/code/datums/components/storage/storage.dm
+++ b/code/datums/components/storage/storage.dm
@@ -156,8 +156,7 @@
next += slave.parent
/datum/component/storage/proc/attack_self(datum/source, mob/M)
- if(locked)
- to_chat(M, "[parent] seems to be locked!")
+ if(check_locked(source, M, TRUE))
return FALSE
if((M.get_active_held_item() == parent) && allow_quick_empty)
quick_empty(M)
@@ -166,8 +165,7 @@
if(!isitem(O) || !click_gather || SEND_SIGNAL(O, COMSIG_CONTAINS_STORAGE))
return FALSE
. = COMPONENT_NO_ATTACK
- if(locked)
- to_chat(M, "[parent] seems to be locked!")
+ if(check_locked(source, M, TRUE))
return FALSE
var/atom/A = parent
var/obj/item/I = O
@@ -238,8 +236,7 @@
var/atom/A = parent
if((!ishuman(M) && (A.loc != M)) || (M.stat != CONSCIOUS) || M.restrained() || !M.canmove)
return
- if(locked)
- to_chat(M, "[parent] seems to be locked!")
+ if(check_locked(null, M, TRUE))
return FALSE
A.add_fingerprint(M)
to_chat(M, "You start dumping out [parent].")
@@ -281,7 +278,7 @@
/datum/component/storage/proc/set_locked(datum/source, new_state)
locked = new_state
- if(locked)
+ if(check_locked())
close_all()
/datum/component/storage/proc/_process_numerical_display()
@@ -456,8 +453,7 @@
var/atom/A = parent
var/atom/dump_destination = dest_object.get_dumping_location()
if(A.Adjacent(M) && dump_destination && M.Adjacent(dump_destination))
- if(locked)
- to_chat(M, "[parent] seems to be locked!")
+ if(check_locked(null, M, TRUE))
return FALSE
if(dump_destination.storage_contents_dump_act(src, M))
playsound(A, "rustle", 50, 1, -5)
@@ -563,10 +559,9 @@
var/atom/host = parent
if(real_location == I.loc)
return FALSE //Means the item is already in the storage item
- if(locked)
+ if(check_locked(null, M, !stop_messages))
if(M && !stop_messages)
host.add_fingerprint(M)
- to_chat(M, "[host] seems to be locked!")
return FALSE
if(real_location.contents.len >= max_items)
if(!stop_messages)
@@ -659,8 +654,10 @@
/datum/component/storage/proc/on_check()
return TRUE
-/datum/component/storage/proc/check_locked()
- return locked
+/datum/component/storage/proc/check_locked(datum/source, mob/user, message = FALSE)
+ . = locked
+ if(message && . && user)
+ to_chat(user, "[parent] seems to be locked!")
/datum/component/storage/proc/signal_take_type(datum/source, type, atom/destination, amount = INFINITY, check_adjacent = FALSE, force = FALSE, mob/user, list/inserted)
if(!force)
@@ -720,9 +717,7 @@
if(A.loc == user)
. = COMPONENT_NO_ATTACK_HAND
- if(locked)
- to_chat(user, "[parent] seems to be locked!")
- else
+ if(!check_locked(source, user, TRUE))
show_to(user)
A.do_jiggle()
@@ -747,8 +742,7 @@
/datum/component/storage/proc/on_alt_click(datum/source, mob/user)
if(!isliving(user) || !user.CanReach(parent))
return
- if(locked)
- to_chat(user, "[parent] seems to be locked!")
+ if(check_locked(source, user, TRUE))
return
var/atom/A = parent
diff --git a/code/datums/ert.dm b/code/datums/ert.dm
index d3c256308d..d61c95c8f2 100644
--- a/code/datums/ert.dm
+++ b/code/datums/ert.dm
@@ -21,6 +21,8 @@
/datum/ert/amber
code = "Amber"
+ leader_role = /datum/antagonist/ert/commander/red
+ roles = list(/datum/antagonist/ert/security/red, /datum/antagonist/ert/medic/red, /datum/antagonist/ert/engineer/red)
/datum/ert/red
leader_role = /datum/antagonist/ert/commander/red
diff --git a/code/datums/traits/neutral.dm b/code/datums/traits/neutral.dm
index f299e9904a..f70e3a3c68 100644
--- a/code/datums/traits/neutral.dm
+++ b/code/datums/traits/neutral.dm
@@ -93,3 +93,12 @@
gain_text = "You feel more prudish."
lose_text = "You don't feel as prudish as before."
medical_record_text = "Patient exhibits a special gene that makes them immune to Crocin and Hexacrocin."
+
+/datum/quirk/assblastusa
+ name = "Buns of Steel"
+ desc = "You've never skipped ass day. With this trait, you are completely immune to all forms of ass slapping and anyone who tries to slap your rock hard ass usually gets a broken hand."
+ mob_trait = TRAIT_ASSBLASTUSA
+ value = 0
+ medical_record_text = "Patient never skipped ass day."
+ gain_text = "Your ass rivals those of golems."
+ lose_text = "Your butt feels more squishy and slappable."
\ No newline at end of file
diff --git a/code/game/machinery/computer/atmos_alert.dm b/code/game/machinery/computer/atmos_alert.dm
index 94f5e63afb..cb3cbf2aa6 100644
--- a/code/game/machinery/computer/atmos_alert.dm
+++ b/code/game/machinery/computer/atmos_alert.dm
@@ -80,9 +80,21 @@
/obj/machinery/computer/atmos_alert/update_icon()
..()
+ cut_overlays()
+ SSvis_overlays.remove_vis_overlay(src, managed_vis_overlays)
+ var/overlay_state = icon_screen
if(stat & (NOPOWER|BROKEN))
+ add_overlay("[icon_keyboard]_off")
return
+ add_overlay(icon_keyboard)
if(priority_alarms.len)
+ overlay_state = "alert:2"
add_overlay("alert:2")
else if(minor_alarms.len)
+ overlay_state = "alert:1"
add_overlay("alert:1")
+ else
+ overlay_state = "alert:0"
+ add_overlay("alert:0")
+ SSvis_overlays.add_vis_overlay(src, icon, overlay_state, layer, plane, dir)
+ SSvis_overlays.add_vis_overlay(src, icon, overlay_state, ABOVE_LIGHTING_LAYER, ABOVE_LIGHTING_PLANE, dir, alpha=128)
diff --git a/code/game/machinery/computer/station_alert.dm b/code/game/machinery/computer/station_alert.dm
index 6769244335..36ba4b0c94 100644
--- a/code/game/machinery/computer/station_alert.dm
+++ b/code/game/machinery/computer/station_alert.dm
@@ -76,12 +76,23 @@
/obj/machinery/computer/station_alert/update_icon()
..()
+ cut_overlays()
+ SSvis_overlays.remove_vis_overlay(src, managed_vis_overlays)
+ var/overlay_state = icon_screen
if(stat & (NOPOWER|BROKEN))
+ add_overlay("[icon_keyboard]_off")
return
+ add_overlay(icon_keyboard)
var/active_alarms = FALSE
for(var/cat in alarms)
var/list/L = alarms[cat]
if(L.len)
active_alarms = TRUE
if(active_alarms)
+ overlay_state = "alert:2"
add_overlay("alert:2")
+ else
+ overlay_state = "alert:0"
+ add_overlay("alert:0")
+ SSvis_overlays.add_vis_overlay(src, icon, overlay_state, layer, plane, dir)
+ SSvis_overlays.add_vis_overlay(src, icon, overlay_state, ABOVE_LIGHTING_LAYER, ABOVE_LIGHTING_PLANE, dir, alpha=128)
diff --git a/code/game/objects/effects/decals/crayon.dm b/code/game/objects/effects/decals/crayon.dm
index 34263ef9a4..0a0348ae39 100644
--- a/code/game/objects/effects/decals/crayon.dm
+++ b/code/game/objects/effects/decals/crayon.dm
@@ -3,6 +3,7 @@
desc = "Graffiti. Damn kids."
icon = 'icons/effects/crayondecal.dmi'
icon_state = "rune1"
+ plane = GAME_PLANE //makes the graffiti visible over a wall.
gender = NEUTER
mergeable_decal = FALSE
var/do_icon_rotate = TRUE
diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm
index 9ff1b5cf01..9314faab27 100644
--- a/code/game/objects/items.dm
+++ b/code/game/objects/items.dm
@@ -449,7 +449,11 @@ GLOBAL_VAR_INIT(rpg_loot_items, FALSE)
return 0
/obj/item/proc/eyestab(mob/living/carbon/M, mob/living/carbon/user)
-
+ if(user.has_trait(TRAIT_PACIFISM))
+ to_chat(user, "You don't want to harm [M]!")
+ return
+ if(user.has_trait(TRAIT_CLUMSY) && prob(50))
+ M = user
var/is_human_victim = 0
var/obj/item/bodypart/affecting = M.get_bodypart(BODY_ZONE_HEAD)
if(ishuman(M))
diff --git a/code/game/objects/items/cards_ids.dm b/code/game/objects/items/cards_ids.dm
index 96f5dae14c..05ffcbf2fd 100644
--- a/code/game/objects/items/cards_ids.dm
+++ b/code/game/objects/items/cards_ids.dm
@@ -95,25 +95,14 @@
var/atom/A = target
if(!proximity && prox_check || !(isobj(A) || issilicon(A) || isbot(A) || isdrone(A)))
return
-
- //Citadel changes: modular code misfiring, so we're bypassing into main code.
+ if(istype(A, /obj/item/storage) && !(istype(A, /obj/item/storage/lockbox) || istype(A, /obj/item/storage/pod)))
+ return
if(!uses)
user.visible_message("[src] emits a weak spark. It's burnt out!")
playsound(src, 'sound/effects/light_flicker.ogg', 100, 1)
return
else if(uses <= 3)
playsound(src, 'sound/effects/light_flicker.ogg', 30, 1) //Tiiiiiiny warning sound to let ya know your emag's almost dead
-
- if(istype(A,/obj/item/storage/lockbox))
- if(!A.emag_act(user))
- return
- uses = max(uses - 1, 0)
- if(!uses)
- user.visible_message("[src] fizzles and sparks. It seems like it's out of charges.")
- playsound(src, 'sound/effects/light_flicker.ogg', 100, 1)
- return
- if(istype(A,/obj/item/storage))
- return
if(!A.emag_act(user))
return
uses = max(uses - 1, 0)
diff --git a/code/game/objects/items/crayons.dm b/code/game/objects/items/crayons.dm
index 26f8a73cb3..30089f298f 100644
--- a/code/game/objects/items/crayons.dm
+++ b/code/game/objects/items/crayons.dm
@@ -68,6 +68,8 @@
var/pre_noise = FALSE
var/post_noise = FALSE
+ var/datum/team/gang/gang //For marking territory.
+ var/gang_tag_delay = 30 //this is the delay for gang mode tag applications on anything that gang = true on.
/obj/item/toy/crayon/suicide_act(mob/user)
user.visible_message("[user] is jamming [src] up [user.p_their()] nose and into [user.p_their()] brain. It looks like [user.p_theyre()] trying to commit suicide!")
@@ -288,6 +290,13 @@
else if(drawing in numerals)
temp = "number"
+ // 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"
+ if(gang_mode && (!can_claim_for_gang(user, target)))
+ return
var/graf_rot
if(drawing in oriented)
@@ -310,20 +319,22 @@
clicky = CLAMP(text2num(click_params["icon-y"]) - 16, -(world.icon_size/2), world.icon_size/2)
if(!instant)
- to_chat(user, "You start drawing a [temp] on the [target.name]...")
+ to_chat(user, "You start drawing a [temp] on the [target.name]...")
if(pre_noise)
audible_message("You hear spraying.")
playsound(user.loc, 'sound/effects/spray.ogg', 5, 1, 5)
- var/takes_time = !instant
+ 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)
- if(!do_after(user, 50, target = target))
+ 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.
return
if(length(text_buffer))
@@ -332,26 +343,34 @@
var/list/turf/affected_turfs = list()
+
if(actually_paints)
- 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)
- C.pixel_x = clickx
- C.pixel_y = clicky
- affected_turfs += target
- if(PAINT_LARGE_HORIZONTAL)
- var/turf/left = locate(target.x-1,target.y,target.z)
- var/turf/right = locate(target.x+1,target.y,target.z)
- if(is_type_in_list(left, validSurfaces) && is_type_in_list(right, validSurfaces))
- var/obj/effect/decal/cleanable/crayon/C = new(left, paint_color, drawing, temp, graf_rot, PAINT_LARGE_HORIZONTAL_ICON)
+ 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)
+ 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)
- affected_turfs += left
- affected_turfs += right
+ C.pixel_x = clickx
+ C.pixel_y = clicky
affected_turfs += target
- else
- to_chat(user, "There isn't enough space to paint!")
- return
+ if(PAINT_LARGE_HORIZONTAL)
+ var/turf/left = locate(target.x-1,target.y,target.z)
+ var/turf/right = locate(target.x+1,target.y,target.z)
+ if(is_type_in_list(left, validSurfaces) && is_type_in_list(right, validSurfaces))
+ var/obj/effect/decal/cleanable/crayon/C = new(left, paint_color, drawing, temp, graf_rot, PAINT_LARGE_HORIZONTAL_ICON)
+ C.add_hiddenprint(user)
+ affected_turfs += left
+ affected_turfs += right
+ affected_turfs += target
+ else
+ to_chat(user, "There isn't enough space to paint!")
+ return
if(!instant)
to_chat(user, "You finish drawing \the [temp].")
@@ -373,6 +392,52 @@
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_area(target)
+ if(!A || (!is_station_level(A.z)) || !A.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_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!")
@@ -524,6 +589,7 @@
is_capped = TRUE
self_contained = FALSE // Don't disappear when they're empty
can_change_colour = TRUE
+ gang = TRUE //Gang check is true for all things upon the honored hierarchy of spraycans, except those that are FALSE.
validSurfaces = list(/turf/open/floor, /turf/closed/wall)
reagent_contents = list("welding_fuel" = 1, "ethanol" = 1)
@@ -669,6 +735,7 @@
icon_capped = "deathcan2_cap"
icon_uncapped = "deathcan2"
use_overlays = FALSE
+ gang = FALSE
volume_multiplier = 25
charges = 100
@@ -683,6 +750,7 @@
icon_capped = "clowncan2_cap"
icon_uncapped = "clowncan2"
use_overlays = FALSE
+ gang = FALSE
reagent_contents = list("lube" = 1, "banana" = 1)
volume_multiplier = 5
@@ -695,6 +763,7 @@
icon_capped = "mimecan_cap"
icon_uncapped = "mimecan"
use_overlays = FALSE
+ gang = FALSE
can_change_colour = FALSE
paint_color = "#FFFFFF" //RGB
@@ -703,6 +772,26 @@
post_noise = FALSE
reagent_contents = list("nothing" = 1, "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))
+ to_chat(user, "This spraycan has been specially modified with a stage 2 nozzle kit, making it faster.")
+
#undef RANDOM_GRAFFITI
#undef RANDOM_LETTER
#undef RANDOM_NUMBER
diff --git a/code/game/objects/items/implants/implant_mindshield.dm b/code/game/objects/items/implants/implant_mindshield.dm
index ed930ee480..1982740cc9 100644
--- a/code/game/objects/items/implants/implant_mindshield.dm
+++ b/code/game/objects/items/implants/implant_mindshield.dm
@@ -27,7 +27,7 @@
if(target.mind.has_antag_datum(/datum/antagonist/brainwashed))
target.mind.remove_antag_datum(/datum/antagonist/brainwashed)
- if(target.mind.has_antag_datum(/datum/antagonist/rev/head) || target.mind.unconvertable)
+ if(target.mind.has_antag_datum(/datum/antagonist/rev/head) || target.mind.unconvertable || target.mind.has_antag_datum(/datum/antagonist/gang/boss))
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
@@ -38,9 +38,12 @@
I.update_icon()
return FALSE
+ var/datum/antagonist/gang/gang = target.mind.has_antag_datum(/datum/antagonist/gang)
var/datum/antagonist/rev/rev = target.mind.has_antag_datum(/datum/antagonist/rev)
if(rev)
rev.remove_revolutionary(FALSE, user)
+ if(gang)
+ target.mind.remove_antag_datum(/datum/antagonist/gang)
if(!silent)
if(target.mind in SSticker.mode.cult)
to_chat(target, "You feel something interfering with your mental conditioning, but you resist it!")
diff --git a/code/game/objects/items/kitchen.dm b/code/game/objects/items/kitchen.dm
index 11d6f264b8..ef2aa825e7 100644
--- a/code/game/objects/items/kitchen.dm
+++ b/code/game/objects/items/kitchen.dm
@@ -49,8 +49,6 @@
forkload = null
else if(user.zone_selected == BODY_ZONE_PRECISE_EYES)
- if(user.has_trait(TRAIT_CLUMSY) && prob(50))
- M = user
return eyestab(M,user)
else
return ..()
@@ -79,8 +77,6 @@
/obj/item/kitchen/knife/attack(mob/living/carbon/M, mob/living/carbon/user)
if(user.zone_selected == BODY_ZONE_PRECISE_EYES)
- if(user.has_trait(TRAIT_CLUMSY) && prob(50))
- M = user
return eyestab(M,user)
else
return ..()
diff --git a/code/game/objects/items/melee/misc.dm b/code/game/objects/items/melee/misc.dm
index 3a35f4d8ef..8ed0f273e6 100644
--- a/code/game/objects/items/melee/misc.dm
+++ b/code/game/objects/items/melee/misc.dm
@@ -283,7 +283,7 @@
else
var/turf/T = get_turf(src)
if(!isspaceturf(T))
- consume_turf(T)
+ shard.consume_turf(T)
/obj/item/melee/supermatter_sword/afterattack(target, mob/user, proximity_flag)
. = ..()
@@ -330,18 +330,7 @@
else if(!isturf(target))
shard.Bumped(target)
else
- consume_turf(target)
-
-/obj/item/melee/supermatter_sword/proc/consume_turf(turf/T)
- var/oldtype = T.type
- var/turf/newT = T.ScrapeAway()
- if(newT.type == oldtype)
- return
- playsound(T, 'sound/effects/supermatter.ogg', 50, 1)
- T.visible_message("[T] smacks into [src] and rapidly flashes to ash.",\
- "You hear a loud crack as you are washed with a wave of heat.")
- shard.Consume()
- T.CalculateAdjacentTurfs()
+ shard.consume_turf(target)
/obj/item/melee/supermatter_sword/add_blood_DNA(list/blood_dna)
return FALSE
diff --git a/code/game/objects/items/plushes.dm b/code/game/objects/items/plushes.dm
index 703de902b6..28a744fbb1 100644
--- a/code/game/objects/items/plushes.dm
+++ b/code/game/objects/items/plushes.dm
@@ -635,6 +635,13 @@
attack_verb = list("lit", "flickered", "flashed")
squeak_override = list('sound/weapons/magout.ogg' = 1)
+/obj/item/toy/plush/box
+ name = "cardboard plushie"
+ desc = "A toy box plushie, it holds cotten. Only a baddie would place a bomb through the postal system..."
+ icon_state = "box"
+ item_state = "box"
+ attack_verb = list("open", "closed", "packed", "hidden", "rigged", "bombed", "sent", "gave")
+
/obj/item/toy/plush/borgplushie
name = "robot plushie"
desc = "An adorable stuffed toy of a robot."
@@ -659,6 +666,12 @@
icon_state = "neeb"
item_state = "neeb"
+/obj/item/toy/plush/borgplushie/bhijn
+ desc = "An adorable stuffed toy of a IPC."
+ icon_state = "bhijn"
+ item_state = "bhijn"
+ attack_verb = list("closed", "reworked", "merged")
+
/obj/item/toy/plush/bird
name = "bird plushie"
desc = "An adorable stuffed plushie that resembles an avian."
@@ -743,6 +756,7 @@
/obj/item/toy/plush/mammal/edgar
icon_state = "edgar"
item_state = "edgar"
+ attack_verb = list("collared", "tricked", "headpatted")
/obj/item/toy/plush/mammal/frank
icon_state = "frank"
@@ -777,6 +791,16 @@
icon_state = "zed"
item_state = "zed"
+/obj/item/toy/plush/mammal/justin
+ icon_state = "justin"
+ item_state = "justin"
+ attack_verb = list("buttslapped", "fixed")
+
+/obj/item/toy/plush/mammal/reece
+ icon_state = "reece"
+ item_state = "reece"
+ attack_verb = list("healed", "cured", "demoted")
+
/obj/item/toy/plush/mammal/dog
desc = "An adorable stuffed toy that resembles a canine."
icon_state = "katlin"
@@ -840,6 +864,12 @@
icon_state = "drew"
item_state = "drew"
+/obj/item/toy/plush/catgirl/trilby
+ desc = "A masked stuffed toy that resembles a feline scientist."
+ icon_state = "trilby"
+ item_state = "trilby"
+ attack_verb = list("pred", "coded", "remembered")
+
/obj/item/toy/plush/catgirl/fermis
name = "medcat plushie"
desc = "An affectionate stuffed toy that resembles a certain medcat, comes complete with battery operated wagging tail!! You get the impression she's cheering you on to to find happiness and be kind to people."
diff --git a/code/game/objects/items/shields.dm b/code/game/objects/items/shields.dm
index 1171bd7bf7..30c7115346 100644
--- a/code/game/objects/items/shields.dm
+++ b/code/game/objects/items/shields.dm
@@ -153,3 +153,16 @@
slot_flags = null
to_chat(user, "[src] can now be concealed.")
add_fingerprint(user)
+
+/obj/item/shield/makeshift
+ name = "metal shield"
+ desc = "A large shield made of wired and welded sheets of metal. The handle is made of cloth and leather making it unwieldy."
+ armor = list("melee" = 25, "bullet" = 25, "laser" = 5, "energy" = 0, "bomb" = 30, "bio" = 0, "rad" = 0, "fire" = 70, "acid" = 80)
+ lefthand_file = 'icons/mob/inhands/equipment/shields_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/equipment/shields_righthand.dmi'
+ item_state = "makeshift_shield"
+ materials = list(MAT_METAL = 18000)
+ slot_flags = null
+ block_chance = 25
+ force = 5
+ throwforce = 7
diff --git a/code/game/objects/items/stunbaton.dm b/code/game/objects/items/stunbaton.dm
index 5265f555da..3904355893 100644
--- a/code/game/objects/items/stunbaton.dm
+++ b/code/game/objects/items/stunbaton.dm
@@ -58,16 +58,15 @@
switch_status(FALSE)
/obj/item/melee/baton/proc/switch_status(new_status = FALSE, silent = FALSE)
- if(status == new_status)
- return
- status = new_status
+ if(status != new_status)
+ status = new_status
+ if(!silent)
+ playsound(loc, "sparks", 75, 1, -1)
+ if(status)
+ START_PROCESSING(SSobj, src)
+ else
+ STOP_PROCESSING(SSobj, src)
update_icon()
- if(!silent)
- playsound(loc, "sparks", 75, 1, -1)
- if(status)
- START_PROCESSING(SSobj, src)
- else
- STOP_PROCESSING(SSobj, src)
/obj/item/melee/baton/process()
deductcharge(hitcost * 0.004, FALSE)
@@ -93,7 +92,7 @@
if(cell)
to_chat(user, "[src] already has a cell.")
else
- if(C.maxcharge < hitcost)
+ if(C.maxcharge < hitcost * STUNBATON_CHARGE_LENIENCY)
to_chat(user, "[src] requires a higher capacity cell.")
return
if(!user.transferItemToLoc(W, src))
diff --git a/code/game/objects/items/tools/screwdriver.dm b/code/game/objects/items/tools/screwdriver.dm
index 2e8cb569a6..217b53c928 100644
--- a/code/game/objects/items/tools/screwdriver.dm
+++ b/code/game/objects/items/tools/screwdriver.dm
@@ -75,11 +75,6 @@
return ..()
if(user.zone_selected != BODY_ZONE_PRECISE_EYES && user.zone_selected != BODY_ZONE_HEAD)
return ..()
- if(user.has_trait(TRAIT_PACIFISM))
- to_chat(user, "You don't want to harm [M]!")
- return
- if(user.has_trait(TRAIT_CLUMSY) && prob(50))
- M = user
return eyestab(M,user)
/obj/item/screwdriver/brass
@@ -103,7 +98,7 @@
/obj/item/screwdriver/abductor/get_belt_overlay()
return mutable_appearance('icons/obj/clothing/belt_overlays.dmi', "screwdriver_nuke")
-
+
/obj/item/screwdriver/power
name = "hand drill"
desc = "A simple powered hand drill. It's fitted with a screw bit."
diff --git a/code/game/objects/obj_defense.dm b/code/game/objects/obj_defense.dm
index 6221416db8..2db2cd08d8 100644
--- a/code/game/objects/obj_defense.dm
+++ b/code/game/objects/obj_defense.dm
@@ -241,6 +241,7 @@ GLOBAL_DATUM_INIT(acid_overlay, /mutable_appearance, mutable_appearance('icons/e
//what happens when the obj's health is below integrity_failure level.
/obj/proc/obj_break(damage_flag)
+ SEND_SIGNAL(src, COMSIG_OBJ_BREAK, damage_flag)
return
//what happens when the obj's integrity reaches zero.
diff --git a/code/game/turfs/simulated/wall/misc_walls.dm b/code/game/turfs/simulated/wall/misc_walls.dm
index f40f74787a..8efac31f65 100644
--- a/code/game/turfs/simulated/wall/misc_walls.dm
+++ b/code/game/turfs/simulated/wall/misc_walls.dm
@@ -77,6 +77,19 @@
return ..()
+/turf/closed/wall/clockwork/try_destroy(obj/item/I, mob/user, turf/T)
+ if(!heated)
+ return ..()
+ if(!istype(I, /obj/item/pickaxe/drill/jackhammer))
+ return FALSE
+ to_chat(user, "You begin to smash though [src]...")
+ if(!do_after(user, 70, TRUE, src))
+ return FALSE
+ I.play_tool_sound(src)
+ visible_message("[user] smashes through [src] with [I]!", "You hear the grinding of metal.")
+ dismantle_wall()
+ return TRUE
+
/turf/closed/wall/clockwork/ReplaceWithLattice()
..()
for(var/obj/structure/lattice/L in src)
diff --git a/code/modules/admin/topic.dm b/code/modules/admin/topic.dm
index 226f2a5456..6fa118ab7f 100644
--- a/code/modules/admin/topic.dm
+++ b/code/modules/admin/topic.dm
@@ -903,6 +903,13 @@
else
dat += "
Alien | "
+ //Gang
+ if(jobban_isbanned(M, ROLE_GANG) || isbanned_dept)
+ dat += "Gang | "
+ else
+ dat += "Gang | "
+
+
//Other Roles (black)
dat += ""
dat += "| Other Roles |
|---|
"
@@ -974,7 +981,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)
+ joblist += list(ROLE_OPERATIVE, ROLE_REV, ROLE_CULTIST, ROLE_SERVANT_OF_RATVAR, ROLE_ABDUCTOR, ROLE_ALIEN, ROLE_GANG)
if("convertantags")
joblist += list(ROLE_REV, ROLE_CULTIST, ROLE_SERVANT_OF_RATVAR, ROLE_ALIEN)
if("otherroles")
diff --git a/code/modules/atmospherics/machinery/components/binary_devices/pump.dm b/code/modules/atmospherics/machinery/components/binary_devices/pump.dm
index e7df188f70..abdc3ada57 100644
--- a/code/modules/atmospherics/machinery/components/binary_devices/pump.dm
+++ b/code/modules/atmospherics/machinery/components/binary_devices/pump.dm
@@ -27,7 +27,26 @@ Thus, the two variables affect pump operation are set in New():
construction_type = /obj/item/pipe/directional
pipe_state = "pump"
-
+
+/obj/machinery/atmospherics/components/binary/pump/CtrlClick(mob/user)
+ var/area/A = get_area(src)
+ var/turf/T = get_turf(src)
+ if(user.canUseTopic(src, BE_CLOSE, FALSE,))
+ on = !on
+ update_icon()
+ investigate_log("Pump, [src.name], turned on by [key_name(usr)] at [x], [y], [z], [A]", INVESTIGATE_ATMOS)
+ message_admins("Pump, [src.name], turned [on ? "on" : "off"] by [ADMIN_LOOKUPFLW(usr)] at [ADMIN_COORDJMP(T)], [A]")
+ return ..()
+
+/obj/machinery/atmospherics/components/binary/pump/AltClick(mob/user)
+ var/area/A = get_area(src)
+ var/turf/T = get_turf(src)
+ if(user.canUseTopic(src, BE_CLOSE, FALSE,))
+ target_pressure = MAX_OUTPUT_PRESSURE
+ to_chat(user,"You maximize the pressure on the [src].")
+ investigate_log("Pump, [src.name], was maximized by [key_name(usr)] at [x], [y], [z], [A]", INVESTIGATE_ATMOS)
+ message_admins("Pump, [src.name], was maximized by [ADMIN_LOOKUPFLW(usr)] at [ADMIN_COORDJMP(T)], [A]")
+
/obj/machinery/atmospherics/components/binary/pump/layer1
piping_layer = PIPING_LAYER_MIN
pixel_x = -PIPING_LAYER_P_X
diff --git a/code/modules/atmospherics/machinery/components/binary_devices/volume_pump.dm b/code/modules/atmospherics/machinery/components/binary_devices/volume_pump.dm
index 46b646cabf..2efff16301 100644
--- a/code/modules/atmospherics/machinery/components/binary_devices/volume_pump.dm
+++ b/code/modules/atmospherics/machinery/components/binary_devices/volume_pump.dm
@@ -28,6 +28,16 @@ Thus, the two variables affect pump operation are set in New():
construction_type = /obj/item/pipe/directional
pipe_state = "volumepump"
+/obj/machinery/atmospherics/components/binary/volume_pump/CtrlClick(mob/user)
+ var/area/A = get_area(src)
+ var/turf/T = get_turf(src)
+ if(user.canUseTopic(src, BE_CLOSE, FALSE,))
+ on = !on
+ update_icon()
+ investigate_log("Pump, [src.name], turned on by [key_name(usr)] at [x], [y], [z], [A]", INVESTIGATE_ATMOS)
+ message_admins("Pump, [src.name], turned [on ? "on" : "off"] by [ADMIN_LOOKUPFLW(usr)] at [ADMIN_COORDJMP(T)], [A]")
+ return ..()
+
/obj/machinery/atmospherics/components/binary/volume_pump/layer1
piping_layer = PIPING_LAYER_MIN
pixel_x = -PIPING_LAYER_P_X
@@ -192,4 +202,4 @@ Thus, the two variables affect pump operation are set in New():
else
investigate_log("Pump, [src.name], was unwrenched by [key_name(usr)] at [x], [y], [z], [A]", INVESTIGATE_ATMOS)
message_admins("Pump, [src.name], was unwrenched by [ADMIN_LOOKUPFLW(user)] at [A]")
- return TRUE
\ No newline at end of file
+ return TRUE
diff --git a/code/modules/atmospherics/machinery/components/trinary_devices/filter.dm b/code/modules/atmospherics/machinery/components/trinary_devices/filter.dm
index 4101deb00c..1e52e238cf 100644
--- a/code/modules/atmospherics/machinery/components/trinary_devices/filter.dm
+++ b/code/modules/atmospherics/machinery/components/trinary_devices/filter.dm
@@ -12,6 +12,25 @@
construction_type = /obj/item/pipe/trinary/flippable
pipe_state = "filter"
+/obj/machinery/atmospherics/components/trinary/filter/CtrlClick(mob/user)
+ var/area/A = get_area(src)
+ var/turf/T = get_turf(src)
+ if(user.canUseTopic(src, BE_CLOSE, FALSE,))
+ on = !on
+ update_icon()
+ investigate_log("Pump, [src.name], turned on by [key_name(usr)] at [x], [y], [z], [A]", INVESTIGATE_ATMOS)
+ message_admins("Pump, [src.name], turned [on ? "on" : "off"] by [ADMIN_LOOKUPFLW(usr)] at [ADMIN_COORDJMP(T)], [A]")
+ return ..()
+
+/obj/machinery/atmospherics/components/trinary/filter/AltClick(mob/user)
+ var/area/A = get_area(src)
+ var/turf/T = get_turf(src)
+ if(user.canUseTopic(src, BE_CLOSE, FALSE,))
+ target_pressure = MAX_OUTPUT_PRESSURE
+ to_chat(user,"You maximize the pressure on the [src].")
+ investigate_log("Pump, [src.name], was maximized by [key_name(usr)] at [x], [y], [z], [A]", INVESTIGATE_ATMOS)
+ message_admins("Pump, [src.name], was maximized by [ADMIN_LOOKUPFLW(usr)] at [ADMIN_COORDJMP(T)], [A]")
+
/obj/machinery/atmospherics/components/trinary/filter/layer1
piping_layer = PIPING_LAYER_MIN
pixel_x = -PIPING_LAYER_P_X
diff --git a/code/modules/atmospherics/machinery/components/trinary_devices/mixer.dm b/code/modules/atmospherics/machinery/components/trinary_devices/mixer.dm
index 4dd2972526..ba9fdf31af 100644
--- a/code/modules/atmospherics/machinery/components/trinary_devices/mixer.dm
+++ b/code/modules/atmospherics/machinery/components/trinary_devices/mixer.dm
@@ -14,7 +14,25 @@
pipe_state = "mixer"
//node 3 is the outlet, nodes 1 & 2 are intakes
-
+/obj/machinery/atmospherics/components/trinary/mixer/CtrlClick(mob/user)
+ var/area/A = get_area(src)
+ var/turf/T = get_turf(src)
+ if(user.canUseTopic(src, BE_CLOSE, FALSE,))
+ on = !on
+ update_icon()
+ investigate_log("Pump, [src.name], turned on by [key_name(usr)] at [x], [y], [z], [A]", INVESTIGATE_ATMOS)
+ message_admins("Pump, [src.name], turned [on ? "on" : "off"] by [ADMIN_LOOKUPFLW(usr)] at [ADMIN_COORDJMP(T)], [A]")
+ return ..()
+
+/obj/machinery/atmospherics/components/trinary/mixer/AltClick(mob/user)
+ var/area/A = get_area(src)
+ var/turf/T = get_turf(src)
+ if(user.canUseTopic(src, BE_CLOSE, FALSE,))
+ target_pressure = MAX_OUTPUT_PRESSURE
+ to_chat(user,"You maximize the pressure on the [src].")
+ investigate_log("Pump, [src.name], was maximized by [key_name(usr)] at [x], [y], [z], [A]", INVESTIGATE_ATMOS)
+ message_admins("Pump, [src.name], was maximized by [ADMIN_LOOKUPFLW(usr)] at [ADMIN_COORDJMP(T)], [A]")
+
/obj/machinery/atmospherics/components/trinary/mixer/layer1
piping_layer = PIPING_LAYER_MIN
pixel_x = -PIPING_LAYER_P_X
diff --git a/code/modules/cargo/packs.dm b/code/modules/cargo/packs.dm
index ca8f10869d..479a8db0ad 100644
--- a/code/modules/cargo/packs.dm
+++ b/code/modules/cargo/packs.dm
@@ -873,8 +873,7 @@
/datum/supply_pack/engineering/shield_sat
name = "Shield Generator Satellite"
desc = "Protect the very existence of this station with these Anti-Meteor defenses. Contains three Shield Generator Satellites."
- cost = 3000
- special = TRUE
+ cost = 4000
contains = list(
/obj/machinery/satellite/meteor_shield,
/obj/machinery/satellite/meteor_shield,
@@ -886,8 +885,7 @@
/datum/supply_pack/engineering/shield_sat_control
name = "Shield System Control Board"
desc = "A control system for the Shield Generator Satellite system."
- cost = 5000
- special = TRUE
+ cost = 4000
contains = list(/obj/item/circuitboard/computer/sat_control)
crate_name= "shield control board crate"
diff --git a/code/modules/client/preferences.dm b/code/modules/client/preferences.dm
index d80f56c8c8..91a329c67b 100644
--- a/code/modules/client/preferences.dm
+++ b/code/modules/client/preferences.dm
@@ -117,6 +117,7 @@ GLOBAL_LIST_EMPTY(preferences_datums)
"balls_color" = "fff",
"balls_amount" = 2,
"balls_sack_size" = BALLS_SACK_SIZE_DEF,
+ "balls_shape" = "Single",
"balls_size" = BALLS_SIZE_DEF,
"balls_cum_rate" = CUM_RATE,
"balls_cum_mult" = CUM_RATE_MULT,
@@ -701,6 +702,7 @@ GLOBAL_LIST_EMPTY(preferences_datums)
else
dat += "Testicles Color:"
dat += " Change
"
+ dat += "Testicles showing:[features["balls_shape"]]"
dat += APPEARANCE_CATEGORY_COLUMN
dat += "Has Vagina:"
dat += "[features["has_vag"] == TRUE ? "Yes" : "No"]"
@@ -1836,7 +1838,7 @@ GLOBAL_LIST_EMPTY(preferences_datums)
else if((MUTCOLORS_PARTSONLY in pref_species.species_traits) || ReadHSV(temp_hsv)[3] >= ReadHSV("#202020")[3])
features["cock_color"] = sanitize_hexcolor(new_cockcolor)
else
- user << "Invalid color. Your color is not bright enough."
+ to_chat(user,"Invalid color. Your color is not bright enough.")
if("cock_length")
var/new_length = input(user, "Penis length in inches:\n([COCK_SIZE_MIN]-[COCK_SIZE_MAX])", "Character Preference") as num|null
@@ -1858,7 +1860,13 @@ GLOBAL_LIST_EMPTY(preferences_datums)
else if((MUTCOLORS_PARTSONLY in pref_species.species_traits) || ReadHSV(temp_hsv)[3] >= ReadHSV("#202020")[3])
features["balls_color"] = sanitize_hexcolor(new_ballscolor)
else
- user << "Invalid color. Your color is not bright enough."
+ to_chat(user,"Invalid color. Your color is not bright enough.")
+
+ if("balls_shape")
+ var/new_shape
+ new_shape = input(user, "Testicle Type:", "Character Preference") as null|anything in GLOB.balls_shapes_list
+ if(new_shape)
+ features["balls_shape"] = new_shape
if("egg_size")
var/new_size
@@ -1874,7 +1882,7 @@ GLOBAL_LIST_EMPTY(preferences_datums)
if(ReadHSV(temp_hsv)[3] >= ReadHSV("#202020")[3])
features["eggsack_egg_color"] = sanitize_hexcolor(new_egg_color)
else
- user << "Invalid color. Your color is not bright enough."
+ to_chat(user,"Invalid color. Your color is not bright enough.")
if("breasts_size")
var/new_size
@@ -1897,7 +1905,7 @@ GLOBAL_LIST_EMPTY(preferences_datums)
else if((MUTCOLORS_PARTSONLY in pref_species.species_traits) || ReadHSV(temp_hsv)[3] >= ReadHSV("#202020")[3])
features["breasts_color"] = sanitize_hexcolor(new_breasts_color)
else
- user << "Invalid color. Your color is not bright enough."
+ to_chat(user,"Invalid color. Your color is not bright enough.")
if("vag_shape")
var/new_shape
@@ -1914,7 +1922,7 @@ GLOBAL_LIST_EMPTY(preferences_datums)
else if((MUTCOLORS_PARTSONLY in pref_species.species_traits) || ReadHSV(temp_hsv)[3] >= ReadHSV("#202020")[3])
features["vag_color"] = sanitize_hexcolor(new_vagcolor)
else
- user << "Invalid color. Your color is not bright enough."
+ to_chat(user,"Invalid color. Your color is not bright enough.")
if("ooccolor")
var/new_ooccolor = input(user, "Choose your OOC colour:", "Game Preference",ooccolor) as color|null
diff --git a/code/modules/client/preferences_savefile.dm b/code/modules/client/preferences_savefile.dm
index 39b44f1ab9..c2675ba867 100644
--- a/code/modules/client/preferences_savefile.dm
+++ b/code/modules/client/preferences_savefile.dm
@@ -330,6 +330,7 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
S["feature_has_balls"] >> features["has_balls"]
S["feature_balls_color"] >> features["balls_color"]
S["feature_balls_size"] >> features["balls_size"]
+ S["feature_balls_shape"] >> features["balls_shape"]
S["feature_balls_sack_size"] >> features["balls_sack_size"]
S["feature_balls_fluid"] >> features["balls_fluid"]
//breasts features
diff --git a/code/modules/clothing/gloves/miscellaneous.dm b/code/modules/clothing/gloves/miscellaneous.dm
index b691074c27..bc36353ac5 100644
--- a/code/modules/clothing/gloves/miscellaneous.dm
+++ b/code/modules/clothing/gloves/miscellaneous.dm
@@ -27,7 +27,7 @@
/obj/item/clothing/gloves/combat
name = "combat gloves"
desc = "These tactical gloves are fireproof and shock resistant."
- icon_state = "black"
+ icon_state = "combat"
item_state = "blackgloves"
siemens_coefficient = 0
permeability_coefficient = 0.05
diff --git a/code/modules/clothing/outfits/ert.dm b/code/modules/clothing/outfits/ert.dm
index c6d657b271..b4310a7f42 100644
--- a/code/modules/clothing/outfits/ert.dm
+++ b/code/modules/clothing/outfits/ert.dm
@@ -46,7 +46,8 @@
/datum/outfit/ert/commander/alert
name = "ERT Commander - High Alert"
-
+
+ suit = /obj/item/clothing/suit/space/hardsuit/ert/alert
glasses = /obj/item/clothing/glasses/thermal/eyepatch
backpack_contents = list(/obj/item/storage/box/engineer=1,\
/obj/item/melee/baton/loaded=1,\
@@ -80,7 +81,8 @@
/datum/outfit/ert/security/alert
name = "ERT Security - High Alert"
-
+
+ suit = /obj/item/clothing/suit/space/hardsuit/ert/alert/sec
backpack_contents = list(/obj/item/storage/box/engineer=1,\
/obj/item/storage/box/handcuffs=1,\
/obj/item/clothing/mask/gas/sechailer/swat=1,\
@@ -117,6 +119,7 @@
/datum/outfit/ert/medic/alert
name = "ERT Medic - High Alert"
+ suit = /obj/item/clothing/suit/space/hardsuit/ert/alert/med
backpack_contents = list(/obj/item/storage/box/engineer=1,\
/obj/item/melee/baton/loaded=1,\
/obj/item/clothing/mask/gas/sechailer/swat=1,\
@@ -153,6 +156,7 @@
/datum/outfit/ert/engineer/alert
name = "ERT Engineer - High Alert"
+ suit = /obj/item/clothing/suit/space/hardsuit/ert/alert/engi
backpack_contents = list(/obj/item/storage/box/engineer=1,\
/obj/item/melee/baton/loaded=1,\
/obj/item/clothing/mask/gas/sechailer/swat=1,\
diff --git a/code/modules/clothing/shoes/taeclowndo.dm b/code/modules/clothing/shoes/taeclowndo.dm
new file mode 100644
index 0000000000..20d9fa6b70
--- /dev/null
+++ b/code/modules/clothing/shoes/taeclowndo.dm
@@ -0,0 +1,36 @@
+/obj/item/clothing/shoes/clown_shoes/taeclowndo
+ var/list/spelltypes = list (
+ /obj/effect/proc_holder/spell/targeted/conjure_item/summon_pie,
+ /obj/effect/proc_holder/spell/aimed/banana_peel,
+ /obj/effect/proc_holder/spell/targeted/touch/megahonk,
+ /obj/effect/proc_holder/spell/targeted/touch/bspie,
+ )
+ var/list/spells = list()
+
+
+/obj/item/clothing/shoes/clown_shoes/taeclowndo/equipped(mob/user, slot)
+ . = ..()
+ if(!ishuman(user))
+ return
+ var/mob/living/carbon/human/H = user
+ if(!(H.has_trait(TRAIT_CLUMSY)) && !(H.mind && H.mind.assigned_role == "Clown"))
+ return
+ if(slot == SLOT_SHOES)
+ spells = new
+ for(var/spell in spelltypes)
+ var/obj/effect/proc_holder/spell/S = new spell
+ spells += S
+ S.charge_counter = 0
+ S.start_recharge()
+ H.mind.AddSpell(S)
+
+/obj/item/clothing/shoes/clown_shoes/taeclowndo/dropped(mob/user)
+ . = ..()
+ if(!ishuman(user))
+ return
+ var/mob/living/carbon/human/H = user
+ if(H.get_item_by_slot(SLOT_SHOES) == src)
+ for(var/spell in spells)
+ var/obj/effect/proc_holder/spell/S = spell
+ H.mind.spell_list.Remove(S)
+ qdel(S)
\ No newline at end of file
diff --git a/code/modules/clothing/spacesuits/miscellaneous.dm b/code/modules/clothing/spacesuits/miscellaneous.dm
index 652c0048c2..6d7c1036be 100644
--- a/code/modules/clothing/spacesuits/miscellaneous.dm
+++ b/code/modules/clothing/spacesuits/miscellaneous.dm
@@ -8,7 +8,8 @@ Contains:
- NASA Voidsuit
- Father Christmas' magical clothes
- Pirate's spacesuit
- - ERT hardsuit: command, sec, engi, med
+ - ERT hardsuit: Command, Sec, Engi, Med
+ - ERT High Alarm - Command, Sec, Engi, Med
- EVA spacesuit
- Freedom's spacesuit (freedom from vacuum's oppression)
- Carp hardsuit
@@ -185,6 +186,7 @@ Contains:
slowdown = 0
strip_delay = 130
resistance_flags = ACID_PROOF
+ tauric = TRUE //Citadel Add for tauric hardsuits
//ERT Security
/obj/item/clothing/head/helmet/space/hardsuit/ert/sec
@@ -198,7 +200,6 @@ Contains:
icon_state = "ert_security"
item_state = "ert_security"
helmettype = /obj/item/clothing/head/helmet/space/hardsuit/ert/sec
- tauric = TRUE //Citadel Add for tauric hardsuits
//ERT Engineering
/obj/item/clothing/head/helmet/space/hardsuit/ert/engi
@@ -212,7 +213,6 @@ Contains:
icon_state = "ert_engineer"
item_state = "ert_engineer"
helmettype = /obj/item/clothing/head/helmet/space/hardsuit/ert/engi
- tauric = TRUE //Citadel Add for tauric hardsuits
//ERT Medical
/obj/item/clothing/head/helmet/space/hardsuit/ert/med
@@ -227,8 +227,73 @@ Contains:
item_state = "ert_medical"
helmettype = /obj/item/clothing/head/helmet/space/hardsuit/ert/med
species_exception = list(/datum/species/angel)
+
+ //Red alert ERT
+
+/obj/item/clothing/head/helmet/space/hardsuit/ert/alert
+ name = "emergency response unit helmet"
+ desc = "Red alert command helmet for the ERT. This one is more armored than its standard version."
+ icon_state = "hardsuit0-ert_commander-alert"
+ item_state = "hardsuit0-ert_commander-alert"
+ item_color = "ert_commander-alert"
+ armor = list("melee" = 70, "bullet" = 55, "laser" = 50, "energy" = 50, "bomb" = 65, "bio" = 100, "rad" = 100, "fire" = 100, "acid" = 100)
+ strip_delay = 130
+ item_flags = NODROP
+ brightness_on = 8
+ resistance_flags = FIRE_PROOF | ACID_PROOF
+
+/obj/item/clothing/suit/space/hardsuit/ert/alert
+ name = "emergency response team suit"
+ desc = "Red alert command suit for the ERT. This one is more armored than its standard version."
+ icon_state = "ert_command-alert"
+ item_state = "ert_command-alert"
+ helmettype = /obj/item/clothing/head/helmet/space/hardsuit/ert/alert
+ allowed = list(/obj/item/gun, /obj/item/ammo_box, /obj/item/ammo_casing, /obj/item/melee/baton, /obj/item/restraints/handcuffs, /obj/item/tank/internals)
+ armor = list("melee" = 70, "bullet" = 55, "laser" = 50, "energy" = 50, "bomb" = 65, "bio" = 100, "rad" = 100, "fire" = 100, "acid" = 100)
+ strip_delay = 130
+ resistance_flags = FIRE_PROOF | ACID_PROOF
tauric = TRUE //Citadel Add for tauric hardsuits
+ //ERT Security
+/obj/item/clothing/head/helmet/space/hardsuit/ert/alert/sec
+ desc = "Red alert security helmet for the ERT. This one is more armored than its standard version."
+ icon_state = "hardsuit0-ert_security-alert"
+ item_state = "hardsuit0-ert_security-alert"
+ item_color = "ert_security-alert"
+
+/obj/item/clothing/suit/space/hardsuit/ert/alert/sec
+ desc = "Red alert security suit for the ERT. This one is more armored than its standard version."
+ icon_state = "ert_security-alert"
+ item_state = "ert_security-alert"
+ helmettype = /obj/item/clothing/head/helmet/space/hardsuit/ert/alert/sec
+
+ //ERT Engineering
+/obj/item/clothing/head/helmet/space/hardsuit/ert/alert/engi
+ desc = "Red alert engineer helmet for the ERT. This one is more armored than its standard version."
+ icon_state = "hardsuit0-ert_engineer-alert"
+ item_state = "hardsuit0-ert_engineer-alert"
+ item_color = "ert_engineer-alert"
+
+/obj/item/clothing/suit/space/hardsuit/ert/alert/engi
+ desc = "Red alert engineer suit for the ERT. This one is more armored than its standard version."
+ icon_state = "ert_engineer-alert"
+ item_state = "ert_engineer-alert"
+ helmettype = /obj/item/clothing/head/helmet/space/hardsuit/ert/alert/engi
+
+ //ERT Medical
+/obj/item/clothing/head/helmet/space/hardsuit/ert/alert/med
+ desc = "Red alert medical helmet for the ERT. This one is more armored than its standard version."
+ icon_state = "hardsuit0-ert_medical-alert"
+ item_state = "hardsuit0-ert_medical-alert"
+ item_color = "ert_medical-alert"
+
+/obj/item/clothing/suit/space/hardsuit/ert/alert/med
+ desc = "Red alert medical suit for the ERT. This one is more armored than its standard version."
+ icon_state = "ert_medical-alert"
+ item_state = "ert_medical-alert"
+ helmettype = /obj/item/clothing/head/helmet/space/hardsuit/ert/alert/med
+ species_exception = list(/datum/species/angel)
+
/obj/item/clothing/suit/space/eva
name = "EVA suit"
icon_state = "space"
@@ -280,7 +345,6 @@ Contains:
item_flags = NODROP
mutantrace_variation = NO_MUTANTRACE_VARIATION
-
/obj/item/clothing/suit/space/hardsuit/carp
name = "carp space suit"
desc = "A slimming piece of dubious space carp technology, you suspect it won't stand up to hand-to-hand blows."
@@ -291,7 +355,6 @@ Contains:
allowed = list(/obj/item/tank/internals, /obj/item/gun/ballistic/automatic/speargun) //I'm giving you a hint here
helmettype = /obj/item/clothing/head/helmet/space/hardsuit/carp
-
/obj/item/clothing/head/helmet/space/hardsuit/ert/paranormal
name = "paranormal response unit helmet"
desc = "A helmet worn by those who deal with paranormal threats for a living."
diff --git a/code/modules/clothing/suits/_suits.dm b/code/modules/clothing/suits/_suits.dm
index 8a02245550..7346dc9ea9 100644
--- a/code/modules/clothing/suits/_suits.dm
+++ b/code/modules/clothing/suits/_suits.dm
@@ -36,7 +36,7 @@
if(tauric == TRUE)
center = TRUE
dimension_x = 64
- else if(H.dna.features["taur"] in list("Fox","Wolf","Otie","Drake","Lab","Shepherd","Husky","Eevee","Panther","Horse","Cow","Tiger"))
+ else if(H.dna.features["taur"] in list("Fox","Wolf","Otie","Drake","Lab","Shepherd","Husky","Eevee","Panther","Horse","Cow","Tiger","Deer"))
taurmode = PAW_TAURIC
if(tauric == TRUE)
center = TRUE
diff --git a/code/modules/clothing/suits/toggles.dm b/code/modules/clothing/suits/toggles.dm
index 481fbade71..5d534e00a7 100644
--- a/code/modules/clothing/suits/toggles.dm
+++ b/code/modules/clothing/suits/toggles.dm
@@ -24,11 +24,11 @@
ToggleHood()
/obj/item/clothing/suit/hooded/item_action_slot_check(slot, mob/user)
- if(slot == SLOT_WEAR_SUIT)
+ if(slot == SLOT_WEAR_SUIT || slot == SLOT_NECK)
return 1
/obj/item/clothing/suit/hooded/equipped(mob/user, slot)
- if(slot != SLOT_WEAR_SUIT)
+ if(slot != SLOT_WEAR_SUIT && slot != SLOT_NECK)
RemoveHood()
..()
diff --git a/code/modules/crafting/recipes.dm b/code/modules/crafting/recipes.dm
index c3858da6ad..ca10d5092f 100644
--- a/code/modules/crafting/recipes.dm
+++ b/code/modules/crafting/recipes.dm
@@ -54,6 +54,18 @@
category = CAT_WEAPONRY
subcategory = CAT_WEAPON
+/datum/crafting_recipe/makeshiftshield
+ name = "Makeshift Metal Shield"
+ result = /obj/item/shield/makeshift
+ reqs = list(/obj/item/stack/cable_coil = 30,
+ /obj/item/stack/sheet/metal = 10,
+ /obj/item/stack/sheet/cloth = 2,
+ /obj/item/stack/sheet/leather = 3)
+ tools = list(TOOL_WELDER, TOOL_SCREWDRIVER, TOOL_WIRECUTTER)
+ time = 100
+ category = CAT_WEAPONRY
+ subcategory = CAT_WEAPON
+
/datum/crafting_recipe/molotov
name = "Molotov"
result = /obj/item/reagent_containers/food/drinks/bottle/molotov
diff --git a/code/modules/food_and_drinks/drinks/drinks/bottle.dm b/code/modules/food_and_drinks/drinks/drinks/bottle.dm
index 46e637e640..b753a6c246 100644
--- a/code/modules/food_and_drinks/drinks/drinks/bottle.dm
+++ b/code/modules/food_and_drinks/drinks/drinks/bottle.dm
@@ -73,7 +73,7 @@
var/mob/living/carbon/human/H = target
var/headarmor = 0 // Target's head armor
- armor_block = H.run_armor_check(affecting, "melee","","",armour_penetration) // For normal attack damage
+ armor_block = H.run_armor_check(affecting, "melee", null, null,armour_penetration) // For normal attack damage
//If they have a hat/helmet and the user is targeting their head.
if(istype(H.head, /obj/item/clothing/head) && affecting == BODY_ZONE_HEAD)
diff --git a/code/modules/food_and_drinks/food/snacks.dm b/code/modules/food_and_drinks/food/snacks.dm
index f2b195c32d..2b37065709 100644
--- a/code/modules/food_and_drinks/food/snacks.dm
+++ b/code/modules/food_and_drinks/food/snacks.dm
@@ -108,9 +108,9 @@ All foods are distributed among various categories. Use common sense.
else if(fullness > 150 && fullness < 500)
user.visible_message("[user] takes a [eatverb] from \the [src].", "You take a [eatverb] from \the [src].")
else if(fullness > 500 && fullness < 600)
- user.visible_message("[user] unwillingly takes a [eatverb] of a bit of \the [src].", "You unwillingly take a [eatverb] of a bit of \the [src].")
+ user.visible_message("[user] unwillingly takes a [eatverb] of a bit of \the [src].", "You unwillingly take a [eatverb] of a bit of \the [src].")
else if(fullness > (600 * (1 + M.overeatduration / 2000))) // The more you eat - the more you can eat
- user.visible_message("[user] cannot force any more of \the [src] to go down [user.p_their()] throat!", "You cannot force any more of \the [src] to go down your throat!")
+ user.visible_message("[user] cannot force any more of \the [src] to go down [user.p_their()] throat!", "You cannot force any more of \the [src] to go down your throat!")
return 0
if(M.has_trait(TRAIT_VORACIOUS))
M.changeNext_move(CLICK_CD_MELEE * 0.5) //nom nom nom
diff --git a/code/modules/food_and_drinks/food/snacks_pie.dm b/code/modules/food_and_drinks/food/snacks_pie.dm
index 04177783e6..8264b4153e 100644
--- a/code/modules/food_and_drinks/food/snacks_pie.dm
+++ b/code/modules/food_and_drinks/food/snacks_pie.dm
@@ -62,6 +62,28 @@
/obj/item/reagent_containers/food/snacks/pie/cream/nostun
stunning = FALSE
+/obj/item/reagent_containers/food/snacks/pie/cream/body
+
+/obj/item/reagent_containers/food/snacks/pie/cream/body/Destroy()
+ var/turf/T = get_turf(src)
+ for(var/atom/movable/A in contents)
+ A.forceMove(T)
+ A.throw_at(T, 1, 1)
+ . = ..()
+
+/obj/item/reagent_containers/food/snacks/pie/cream/body/On_Consume(mob/living/carbon/M)
+ if(!reagents.total_volume) //so that it happens on the last bite
+ if(iscarbon(M) && contents.len)
+ var/turf/T = get_turf(src)
+ for(var/atom/movable/A in contents)
+ A.forceMove(T)
+ A.throw_at(T, 1, 1)
+ M.visible_message("[src] bursts out of [M]!")
+ M.emote("scream")
+ M.Knockdown(40)
+ M.adjustBruteLoss(60)
+ return ..()
+
/obj/item/reagent_containers/food/snacks/pie/berryclafoutis
name = "berry clafoutis"
desc = "No black birds, this is a good sign."
diff --git a/code/modules/integrated_electronics/subtypes/weaponized.dm b/code/modules/integrated_electronics/subtypes/weaponized.dm
index f9259359a3..7bccbfafcd 100644
--- a/code/modules/integrated_electronics/subtypes/weaponized.dm
+++ b/code/modules/integrated_electronics/subtypes/weaponized.dm
@@ -215,7 +215,7 @@
The 'fire' activator will cause the mechanism to attempt to launch objects at the coordinates, if possible. Note that the \
projectile needs to be inside the machine, or on an adjacent tile, and must be medium sized or smaller. The assembly \
must also be a gun if you wish to launch something while the assembly is in hand."
- complexity = 75
+ complexity = 50
w_class = WEIGHT_CLASS_SMALL
size = 4
cooldown_per_use = 30
@@ -306,7 +306,7 @@
desc = "Used to stun a target holding the device via electricity."
icon_state = "power_relay"
extended_desc = "Attempts to stun the holder of this device, with the strength input being the strength of the stun, from 1 to 70."
- complexity = 60
+ complexity = 30
size = 4
inputs = list("strength" = IC_PINTYPE_NUMBER)
activators = list("stun" = IC_PINTYPE_PULSE_IN, "on success" = IC_PINTYPE_PULSE_OUT, "on fail" = IC_PINTYPE_PULSE_OUT)
@@ -340,4 +340,4 @@
var/mob/living/carbon/human/H = L
H.forcesay(GLOB.hit_appends)
- return 1
\ No newline at end of file
+ return 1
diff --git a/code/modules/language/slime.dm b/code/modules/language/slime.dm
index 7171c07b39..cca56ca933 100644
--- a/code/modules/language/slime.dm
+++ b/code/modules/language/slime.dm
@@ -5,6 +5,7 @@
ask_verb = "warbles"
exclaim_verb = "warbles"
key = "k"
+ flags = TONGUELESS_SPEECH
syllables = list("qr","qrr","xuq","qil","quum","xuqm","vol","xrim","zaoo","qu-uu","qix","qoo","zix","*","!")
default_priority = 70
diff --git a/code/modules/mining/equipment/resonator.dm b/code/modules/mining/equipment/resonator.dm
index e23c3deb58..f63b459f10 100644
--- a/code/modules/mining/equipment/resonator.dm
+++ b/code/modules/mining/equipment/resonator.dm
@@ -8,10 +8,10 @@
righthand_file = 'icons/mob/inhands/equipment/mining_righthand.dmi'
desc = "A handheld device that creates small fields of energy that resonate until they detonate, crushing rock. It does increased damage in low pressure."
w_class = WEIGHT_CLASS_NORMAL
- force = 15
+ force = 18
throwforce = 10
var/burst_time = 30
- var/fieldlimit = 4
+ var/fieldlimit = 6
var/list/fields = list()
var/quick_burst_mod = 0.8
@@ -20,7 +20,8 @@
desc = "An upgraded version of the resonator that can produce more fields at once, as well as having no damage penalty for bursting a resonance field early."
icon_state = "resonator_u"
item_state = "resonator_u"
- fieldlimit = 6
+ force = 20
+ fieldlimit = 8
quick_burst_mod = 1
/obj/item/resonator/attack_self(mob/user)
diff --git a/code/modules/mob/living/carbon/human/human_defense.dm b/code/modules/mob/living/carbon/human/human_defense.dm
index b66ebdb001..d280c1d311 100644
--- a/code/modules/mob/living/carbon/human/human_defense.dm
+++ b/code/modules/mob/living/carbon/human/human_defense.dm
@@ -256,7 +256,7 @@
var/obj/item/bodypart/affecting = get_bodypart(ran_zone(M.zone_selected))
if(!affecting)
affecting = get_bodypart(BODY_ZONE_CHEST)
- var/armor_block = run_armor_check(affecting, "melee","","",10)
+ var/armor_block = run_armor_check(affecting, "melee", null, null,10)
playsound(loc, 'sound/weapons/slice.ogg', 25, 1, -1)
visible_message("[M] has slashed at [src]!", \
diff --git a/code/modules/mob/living/carbon/human/species.dm b/code/modules/mob/living/carbon/human/species.dm
index 3930d19168..31fff2354a 100644
--- a/code/modules/mob/living/carbon/human/species.dm
+++ b/code/modules/mob/living/carbon/human/species.dm
@@ -1486,6 +1486,24 @@ GLOBAL_LIST_EMPTY(roundstart_races)
user.adjustStaminaLossBuffered(3)
return FALSE
else if(aim_for_groin && (target == user || target.lying || same_dir) && (target_on_help || target_restrained || target_aiming_for_groin))
+ user.do_attack_animation(target, ATTACK_EFFECT_ASS_SLAP)
+ user.adjustStaminaLossBuffered(3)
+ if(target.has_trait(TRAIT_ASSBLASTUSA))
+ var/hit_zone = (user.held_index_to_dir(user.active_hand_index) == "l" ? "l_":"r_") + "arm"
+ user.adjustStaminaLoss(50, affected_zone = hit_zone)
+ var/obj/item/bodypart/affecting = user.get_bodypart(hit_zone)
+ if(affecting)
+ if(affecting.receive_damage(5, 0))
+ user.update_damage_overlays()
+ user.visible_message(\
+ "\The [user] slaps \the [target]'s ass, but their hand bounces off like they hit metal!",\
+ "You slap [user == target ? "your" : "\the [target]'s"] ass, but feel an intense amount of pain as you realise their buns are harder than steel!",\
+ "You hear a slap."
+ )
+ playsound(target.loc, 'sound/weapons/tap.ogg', 50, 1, -1)
+ user.emote("scream")
+ return FALSE
+
playsound(target.loc, 'sound/weapons/slap.ogg', 50, 1, -1)
user.visible_message(\
"\The [user] slaps \the [target]'s ass!",\
@@ -1494,12 +1512,11 @@ GLOBAL_LIST_EMPTY(roundstart_races)
)
if (target.canbearoused)
target.adjustArousalLoss(5)
- if (target.getArousalLoss() >= 100 && ishuman(target) && target.has_trait(TRAIT_NYMPHO) && target.has_dna())
+ if (target.getArousalLoss() >= 100 && ishuman(target) && target.has_trait(TRAIT_MASO) && target.has_dna())
target.mob_climax(forced_climax=TRUE)
if (!target.has_trait(TRAIT_NYMPHO))
stop_wagging_tail(target)
- user.do_attack_animation(target, ATTACK_EFFECT_ASS_SLAP)
- user.adjustStaminaLossBuffered(3)
+
return FALSE
else if(attacker_style && attacker_style.disarm_act(user,target))
return 1
@@ -1666,8 +1683,11 @@ GLOBAL_LIST_EMPTY(roundstart_races)
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/)
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)
@@ -1727,6 +1747,11 @@ GLOBAL_LIST_EMPTY(roundstart_races)
if(BP)
if(damage > 0 ? BP.receive_damage(damage * hit_percent * brutemod * H.physiology.brute_mod, 0) : BP.heal_damage(abs(damage * hit_percent * brutemod * H.physiology.brute_mod), 0))
H.update_damage_overlays()
+ if(H.has_trait(TRAIT_MASO))
+ H.adjustArousalLoss(damage * brutemod * H.physiology.brute_mod)
+ if (H.getArousalLoss() >= 100 && ishuman(H) && H.has_dna())
+ H.mob_climax(forced_climax=TRUE)
+
else//no bodypart, we deal damage with a more general method.
H.adjustBruteLoss(damage * hit_percent * brutemod * H.physiology.brute_mod)
if(BURN)
diff --git a/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm b/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm
index c5a64ebd13..30bf705547 100644
--- a/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm
+++ b/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm
@@ -97,6 +97,7 @@
brutemod = 0.9
/datum/species/lizard/ashwalker/on_species_gain(mob/living/carbon/human/C, datum/species/old_species)
- if((C.dna.features["spines"] != "None" ) && (C.dna.features["tail"] == "None")) //tbh, it's kinda ugly for them not to have a tail yet have floating spines
- C.dna.features["tail"] = "Smooth"
+ if((C.dna.features["spines"] != "None" ) && (C.dna.features["tail_lizard"] == "None")) //tbh, it's kinda ugly for them not to have a tail yet have floating spines
+ C.dna.features["tail_lizard"] = "Smooth"
+ C.update_body()
return ..()
diff --git a/code/modules/mob/living/carbon/human/update_icons.dm b/code/modules/mob/living/carbon/human/update_icons.dm
index 9b5375035e..ef5b9045ca 100644
--- a/code/modules/mob/living/carbon/human/update_icons.dm
+++ b/code/modules/mob/living/carbon/human/update_icons.dm
@@ -660,6 +660,8 @@ generate/load female uniform sprites matching all previously decided variables
. += "-[BP.dmg_overlay_type]"
if(BP.body_markings)
. += "-[BP.body_markings]"
+ else
+ . += "-no_marking"
if(has_trait(TRAIT_HUSK))
. += "-husk"
diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm
index ae662c7628..b6717f2932 100644
--- a/code/modules/mob/living/living.dm
+++ b/code/modules/mob/living/living.dm
@@ -103,21 +103,23 @@
//CIT CHANGES START HERE - makes it so resting stops you from moving through standing folks without a short delay
if(resting && !L.resting)
- if(attemptingcrawl)
- return TRUE
- if(getStaminaLoss() >= STAMINA_SOFTCRIT)
- to_chat(src, "You're too exhausted to crawl under [L].")
- return TRUE
- attemptingcrawl = TRUE
var/origtargetloc = L.loc
- visible_message("[src] is attempting to crawl under [L].", "You are now attempting to crawl under [L].")
- if(do_after(src, CRAWLUNDER_DELAY, target = src))
- if(resting)
- var/src_passmob = (pass_flags & PASSMOB)
- pass_flags |= PASSMOB
- Move(origtargetloc)
- if(!src_passmob)
- pass_flags &= ~PASSMOB
+ if(!pulledby)
+ if(attemptingcrawl)
+ return TRUE
+ if(getStaminaLoss() >= STAMINA_SOFTCRIT)
+ to_chat(src, "You're too exhausted to crawl under [L].")
+ return TRUE
+ attemptingcrawl = TRUE
+ visible_message("[src] is attempting to crawl under [L].", "You are now attempting to crawl under [L].")
+ if(!do_after(src, CRAWLUNDER_DELAY, target = src) || !resting)
+ attemptingcrawl = FALSE
+ return TRUE
+ var/src_passmob = (pass_flags & PASSMOB)
+ pass_flags |= PASSMOB
+ Move(origtargetloc)
+ if(!src_passmob)
+ pass_flags &= ~PASSMOB
attemptingcrawl = FALSE
return TRUE
//END OF CIT CHANGES
diff --git a/code/modules/mob/living/living_defense.dm b/code/modules/mob/living/living_defense.dm
index 643c91b95a..0d74094d81 100644
--- a/code/modules/mob/living/living_defense.dm
+++ b/code/modules/mob/living/living_defense.dm
@@ -1,24 +1,18 @@
-/mob/living/proc/run_armor_check(def_zone = null, attack_flag = "melee", absorb_text = null, soften_text = null, armour_penetration, penetrated_text)
+/mob/living/proc/run_armor_check(def_zone = null, attack_flag = "melee", absorb_text = "Your armor absorbs the blow!", soften_text = "Your armor softens the blow!", armour_penetration, penetrated_text = "Your armor was penetrated!")
var/armor = getarmor(def_zone, attack_flag)
//the if "armor" check is because this is used for everything on /living, including humans
if(armor && armour_penetration)
armor = max(0, armor - armour_penetration)
if(penetrated_text)
- to_chat(src, "[penetrated_text]")
- else
- to_chat(src, "Your armor was penetrated!")
+ to_chat(src, "[penetrated_text]")
else if(armor >= 100)
if(absorb_text)
- to_chat(src, "[absorb_text]")
- else
- to_chat(src, "Your armor absorbs the blow!")
+ to_chat(src, "[absorb_text]")
else if(armor > 0)
if(soften_text)
- to_chat(src, "[soften_text]")
- else
- to_chat(src, "Your armor softens the blow!")
+ to_chat(src, "[soften_text]")
return armor
@@ -43,7 +37,7 @@
return
/mob/living/bullet_act(obj/item/projectile/P, def_zone)
- var/armor = run_armor_check(def_zone, P.flag, "","",P.armour_penetration)
+ var/armor = run_armor_check(def_zone, P.flag, null, null, P.armour_penetration, null)
if(!P.nodamage)
apply_damage(P.damage, P.damage_type, def_zone, armor)
if(P.dismemberment)
diff --git a/code/modules/mob/say_vr.dm b/code/modules/mob/say_vr.dm
index 6cc0ca185f..850566f625 100644
--- a/code/modules/mob/say_vr.dm
+++ b/code/modules/mob/say_vr.dm
@@ -117,6 +117,73 @@ proc/get_top_level_mob(var/mob/S)
message = null
emote_type = EMOTE_VISIBLE
+///////////////// SUBTLE 2: NO GHOST BOOGALOO
+
+/datum/emote/living/subtler
+ key = "subtler"
+ key_third_person = "subtler"
+ message = null
+ mob_type_blacklist_typecache = list(/mob/living/brain)
+
+
+/datum/emote/living/subtler/proc/check_invalid(mob/user, input)
+ . = TRUE
+ if(copytext(input,1,5) == "says")
+ to_chat(user, "Invalid emote.")
+ else if(copytext(input,1,9) == "exclaims")
+ to_chat(user, "Invalid emote.")
+ else if(copytext(input,1,6) == "yells")
+ to_chat(user, "Invalid emote.")
+ else if(copytext(input,1,5) == "asks")
+ to_chat(user, "Invalid emote.")
+ else
+ . = FALSE
+
+/datum/emote/living/subtler/run_emote(mob/user, params, type_override = null)
+ if(jobban_isbanned(user, "emote"))
+ to_chat(user, "You cannot send subtle emotes (banned).")
+ return FALSE
+ else if(user.client && user.client.prefs.muted & MUTE_IC)
+ to_chat(user, "You cannot send IC messages (muted).")
+ return FALSE
+ else if(!params)
+ var/subtle_emote = copytext(sanitize(input("Choose an emote to display.") as message|null), 1, MAX_MESSAGE_LEN)
+ if(subtle_emote && !check_invalid(user, subtle_emote))
+ var/type = input("Is this a visible or hearable emote?") as null|anything in list("Visible", "Hearable")
+ switch(type)
+ if("Visible")
+ emote_type = EMOTE_VISIBLE
+ if("Hearable")
+ emote_type = EMOTE_AUDIBLE
+ else
+ alert("Unable to use this emote, must be either hearable or visible.")
+ return
+ message = subtle_emote
+ else
+ return FALSE
+ else
+ message = params
+ if(type_override)
+ emote_type = type_override
+ . = TRUE
+ if(!can_run_emote(user))
+ return FALSE
+
+ user.log_message(message, INDIVIDUAL_EMOTE_LOG)
+ message = "[user] " + "[message]"
+
+ for(var/mob/M)
+ if(M in list(/mob/living))
+ M.show_message(message)
+
+ if(emote_type == EMOTE_AUDIBLE)
+ user.audible_message(message=message,hearing_distance=1)
+ else
+ user.visible_message(message=message,self_message=message,vision_distance=1)
+ log_emote("[key_name(user)] : [message]")
+
+ message = null
+
///////////////// VERB CODE
/mob/living/verb/subtle()
set name = "Subtle"
@@ -125,3 +192,12 @@ proc/get_top_level_mob(var/mob/S)
to_chat(usr, "Speech is currently admin-disabled.")
return
usr.emote("subtle")
+
+///////////////// VERB CODE 2
+/mob/living/verb/subtler()
+ set name = "Subtler"
+ set category = "IC"
+ if(GLOB.say_disabled) //This is here to try to identify lag problems
+ to_chat(usr, "Speech is currently admin-disabled.")
+ return
+ usr.emote("subtler")
diff --git a/code/modules/power/supermatter/supermatter.dm b/code/modules/power/supermatter/supermatter.dm
index ad94d577ea..99b4a3f27e 100644
--- a/code/modules/power/supermatter/supermatter.dm
+++ b/code/modules/power/supermatter/supermatter.dm
@@ -295,6 +295,16 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal)
E.energy = power
qdel(src)
+/obj/machinery/power/supermatter_crystal/proc/consume_turf(turf/T)
+ var/oldtype = T.type
+ var/turf/newT = T.ScrapeAway()
+ if(newT.type == oldtype)
+ return
+ playsound(T, 'sound/effects/supermatter.ogg', 50, 1)
+ T.visible_message("[T] smacks into [src] and rapidly flashes to ash.",\
+ "You hear a loud crack as you are washed with a wave of heat.")
+ T.CalculateAdjacentTurfs()
+
/obj/machinery/power/supermatter_crystal/process_atmos()
var/turf/T = loc
@@ -303,6 +313,8 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal)
if(!istype(T)) //We are in a crate or somewhere that isn't turf, if we return to turf resume processing but for now.
return //Yeah just stop.
+ if(istype(T, /turf/closed))
+ consume_turf(T)
if(power)
soundloop.volume = min(40, (round(power/100)/50)+1) // 5 +1 volume per 20 power. 2500 power is max
diff --git a/code/modules/projectiles/guns/energy/kinetic_accelerator.dm b/code/modules/projectiles/guns/energy/kinetic_accelerator.dm
index fba355d738..a4ec979a06 100644
--- a/code/modules/projectiles/guns/energy/kinetic_accelerator.dm
+++ b/code/modules/projectiles/guns/energy/kinetic_accelerator.dm
@@ -424,7 +424,7 @@
M.gets_drilled(K.firer)
if(modifier)
for(var/mob/living/L in range(1, target_turf) - K.firer - target)
- var/armor = L.run_armor_check(K.def_zone, K.flag, "", "", K.armour_penetration)
+ var/armor = L.run_armor_check(K.def_zone, K.flag, null, null, K.armour_penetration)
L.apply_damage(K.damage*modifier, K.damage_type, K.def_zone, armor)
to_chat(L, "You're struck by a [K.name]!")
@@ -530,7 +530,7 @@
var/kill_modifier = 1
if(K.pressure_decrease_active)
kill_modifier *= K.pressure_decrease
- var/armor = L.run_armor_check(K.def_zone, K.flag, "", "", K.armour_penetration)
+ var/armor = L.run_armor_check(K.def_zone, K.flag, null, null, K.armour_penetration)
L.apply_damage(bounties_reaped[L.type]*kill_modifier, K.damage_type, K.def_zone, armor)
/obj/item/borg/upgrade/modkit/bounty/proc/get_kill(mob/living/L)
diff --git a/code/modules/reagents/chemistry/reagents/food_reagents.dm b/code/modules/reagents/chemistry/reagents/food_reagents.dm
index 5071150e24..a65a1f4adb 100644
--- a/code/modules/reagents/chemistry/reagents/food_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/food_reagents.dm
@@ -319,7 +319,7 @@
victim.blind_eyes(2)
victim.confused = max(M.confused, 3)
victim.damageoverlaytemp = 60
- victim.Knockdown(60)
+ victim.Knockdown(60, override_stamdmg = min(reac_volume * 3, 15))
return
else if ( eyes_covered ) // Eye cover is better than mouth cover
victim.blur_eyes(3)
@@ -332,7 +332,7 @@
victim.blind_eyes(3)
victim.confused = max(M.confused, 6)
victim.damageoverlaytemp = 75
- victim.Knockdown(100)
+ victim.Knockdown(100, override_stamdmg = min(reac_volume * 5, 25))
victim.update_damage_hud()
/datum/reagent/consumable/condensedcapsaicin/on_mob_life(mob/living/carbon/M)
diff --git a/code/modules/reagents/reagent_containers/spray.dm b/code/modules/reagents/reagent_containers/spray.dm
index a51134f84d..afb985120e 100644
--- a/code/modules/reagents/reagent_containers/spray.dm
+++ b/code/modules/reagents/reagent_containers/spray.dm
@@ -18,6 +18,7 @@
var/spray_range = 3 //the range of tiles the sprayer will reach when in spray mode.
var/stream_range = 1 //the range of tiles the sprayer will reach when in stream mode.
var/stream_amount = 10 //the amount of reagents transfered when in stream mode.
+ var/spray_delay = 3 //The amount of sleep() delay between each chempuff step.
var/can_fill_from_container = TRUE
amount_per_transfer_from_this = 5
volume = 250
@@ -64,7 +65,7 @@
/obj/item/reagent_containers/spray/proc/spray(atom/A)
- var/range = max(min(current_range, get_dist(src, A)), 1)
+ var/range = CLAMP(get_dist(src, A), 1, current_range)
var/obj/effect/decal/chempuff/D = new /obj/effect/decal/chempuff(get_turf(src))
D.create_reagents(amount_per_transfer_from_this)
var/puff_reagent_left = range //how many turf, mob or dense objet we can react with before we consider the chem puff consumed
@@ -74,7 +75,7 @@
else
reagents.trans_to(D, amount_per_transfer_from_this, 1/range)
D.color = mix_color_from_reagents(D.reagents.reagent_list)
- var/wait_step = max(round(2+3/range), 2)
+ var/wait_step = max(round(2+ spray_delay * INVERSE(range)), 2)
do_spray(A, wait_step, D, range, puff_reagent_left)
/obj/item/reagent_containers/spray/proc/do_spray(atom/A, wait_step, obj/effect/decal/chempuff/D, range, puff_reagent_left)
@@ -166,7 +167,7 @@
user.visible_message("[user] decided life was worth living.")
return
-//Drying Agent
+//Drying Agent
/obj/item/reagent_containers/spray/drying_agent
name = "drying agent spray"
desc = "A spray bottle for drying agent."
@@ -194,6 +195,7 @@
righthand_file = 'icons/mob/inhands/equipment/security_righthand.dmi'
volume = 40
stream_range = 4
+ spray_delay = 1
amount_per_transfer_from_this = 5
list_reagents = list("condensedcapsaicin" = 40)
diff --git a/code/modules/research/designs/biogenerator_designs.dm b/code/modules/research/designs/biogenerator_designs.dm
index 3d48dd51ec..05e4c667f6 100644
--- a/code/modules/research/designs/biogenerator_designs.dm
+++ b/code/modules/research/designs/biogenerator_designs.dm
@@ -59,6 +59,14 @@
build_path = /obj/item/reagent_containers/food/snacks/monkeycube
category = list("initial", "Food")
+/datum/design/smeat
+ name = "Biomass Meat Slab"
+ id = "smeat"
+ build_type = BIOGENERATOR
+ materials = list(MAT_BIOMASS = 175)
+ build_path = /obj/item/reagent_containers/food/snacks/meat/slab/synthmeat
+ category = list("initial", "Food")
+
/datum/design/ez_nut
name = "E-Z Nutrient"
id = "ez_nut"
diff --git a/code/modules/shuttle/emergency.dm b/code/modules/shuttle/emergency.dm
index 5c54ee5164..e1a3308ff6 100644
--- a/code/modules/shuttle/emergency.dm
+++ b/code/modules/shuttle/emergency.dm
@@ -521,10 +521,10 @@
name = "emergency space suits"
desc = "A wall mounted safe containing space suits. Will only open in emergencies."
anchored = TRUE
- density = FALSE
icon = 'icons/obj/storage.dmi'
icon_state = "safe"
- var/unlocked = FALSE
+ integrity_failure = 100
+ component_type = /datum/component/storage/concrete/emergency
/obj/item/storage/pod/PopulateContents()
new /obj/item/clothing/head/helmet/space/orange(src)
@@ -540,26 +540,6 @@
new /obj/item/survivalcapsule(src)
new /obj/item/storage/toolbox/emergency(src)
-/obj/item/storage/pod/attackby(obj/item/W, mob/user, params)
- if (can_interact(user))
- return ..()
-
-/obj/item/storage/pod/attack_hand(mob/user)
- if (can_interact(user))
- SEND_SIGNAL(src, COMSIG_TRY_STORAGE_SHOW, user)
- return TRUE
-
-/obj/item/storage/pod/MouseDrop(over_object, src_location, over_location)
- if(can_interact(usr))
- return ..()
-
-/obj/item/storage/pod/can_interact(mob/user)
- if(!..())
- return FALSE
- if(GLOB.security_level == SEC_LEVEL_RED || GLOB.security_level == SEC_LEVEL_DELTA || unlocked)
- return TRUE
- to_chat(user, "The storage unit will only unlock during a Red or Delta security alert.")
-
/obj/docking_port/mobile/emergency/backup
name = "backup shuttle"
id = "backup"
diff --git a/code/modules/shuttle/on_move.dm b/code/modules/shuttle/on_move.dm
index 9942446868..a6904c28cc 100644
--- a/code/modules/shuttle/on_move.dm
+++ b/code/modules/shuttle/on_move.dm
@@ -290,7 +290,8 @@ All ShuttleMove procs go here
// ignores the movement of the shuttle from the staging area on CentCom to
// the station as it is loaded in.
if (oldT && !is_centcom_level(oldT.z))
- unlocked = TRUE
+ GET_COMPONENT(STR, /datum/component/storage/concrete/emergency)
+ STR?.unlock_me()
/************************************Mob move procs************************************/
diff --git a/code/modules/spells/spell_types/aimed.dm b/code/modules/spells/spell_types/aimed.dm
index 35f9a8f50a..73a428af71 100644
--- a/code/modules/spells/spell_types/aimed.dm
+++ b/code/modules/spells/spell_types/aimed.dm
@@ -74,6 +74,8 @@
/obj/effect/proc_holder/spell/aimed/proc/fire_projectile(mob/living/user, atom/target)
current_amount--
+ if(!projectile_type)
+ return
for(var/i in 1 to projectiles_per_fire)
var/obj/item/projectile/P = new projectile_type(user.loc)
P.firer = user
diff --git a/code/modules/spells/spell_types/godhand.dm b/code/modules/spells/spell_types/godhand.dm
index 919d87b4c7..06d1fc3dc5 100644
--- a/code/modules/spells/spell_types/godhand.dm
+++ b/code/modules/spells/spell_types/godhand.dm
@@ -94,3 +94,87 @@
M.Stun(40)
M.petrify()
return ..()
+
+
+/obj/item/melee/touch_attack/megahonk
+ name = "\improper honkmother's blessing"
+ desc = "You've got a feeling they won't be laughing after this one. Honk honk."
+ catchphrase = "HONKDOOOOUKEN!"
+ on_use_sound = 'sound/items/airhorn.ogg'
+ icon = 'icons/mecha/mecha_equipment.dmi'
+ icon_state = "mecha_honker"
+
+/obj/item/melee/touch_attack/megahonk/afterattack(atom/target, mob/living/carbon/user, proximity)
+ if(!proximity || !iscarbon(target) || !iscarbon(user) || user.handcuffed)
+ return
+ user.say(catchphrase, forced = "spell")
+ playsound(get_turf(target), on_use_sound,100,1)
+ for(var/mob/living/carbon/M in (hearers(1, target) - user)) //3x3 around the target, not affecting the user
+ if(ishuman(M))
+ var/mob/living/carbon/human/H = M
+ if(istype(H.ears, /obj/item/clothing/ears/earmuffs))
+ continue
+ var/mul = (M==target ? 1 : 0.5)
+ to_chat(M, "HONK")
+ M.SetSleeping(0)
+ M.stuttering += 20*mul
+ M.adjustEarDamage(0, 30*mul)
+ M.Knockdown(60*mul)
+ if(prob(40))
+ M.Knockdown(200*mul)
+ else
+ M.Jitter(500*mul)
+
+ charges--
+ if(charges <= 0)
+ qdel(src)
+
+/obj/item/melee/touch_attack/megahonk/attack_self(mob/user)
+ . = ..()
+ to_chat(user, "\The [src] disappears, to honk another day.")
+ qdel(src)
+
+/obj/item/melee/touch_attack/bspie
+ name = "\improper bluespace pie"
+ desc = "A thing you can barely comprehend as you hold it in your hand. You're fairly sure you could fit an entire body inside."
+ on_use_sound = 'sound/magic/demon_consume.ogg'
+ icon = 'icons/obj/food/piecake.dmi'
+ icon_state = "frostypie"
+ color = "#000077"
+
+/obj/item/melee/touch_attack/bspie/attack_self(mob/user)
+ . = ..()
+ to_chat(user, "You smear \the [src] on your chest! ")
+ qdel(src)
+
+/obj/item/melee/touch_attack/bspie/afterattack(atom/target, mob/living/carbon/user, proximity)
+ if(!proximity || !iscarbon(target) || !iscarbon(user) || user.handcuffed)
+ return
+ if(target == user)
+ to_chat(user, "You smear \the [src] on your chest!")
+ qdel(src)
+ return
+ var/mob/living/carbon/M = target
+
+ user.visible_message("[user] is trying to stuff [M]\s body into \the [src]!")
+ if(do_mob(user, M, 250))
+ var/name = M.real_name
+ var/obj/item/reagent_containers/food/snacks/pie/cream/body/pie = new(get_turf(M))
+ pie.name = "\improper [name] [pie.name]"
+
+ playsound(get_turf(target), on_use_sound, 50, 1)
+
+ /*
+ var/obj/item/bodypart/head = M.get_bodypart("head")
+ if(head)
+ head.drop_limb()
+ head.throw_at(get_turf(head), 1, 1)
+ qdel(M)
+ */
+ M.forceMove(pie)
+
+
+ charges--
+
+ if(charges <= 0)
+ qdel(src)
diff --git a/code/modules/spells/spell_types/taeclowndo.dm b/code/modules/spells/spell_types/taeclowndo.dm
new file mode 100644
index 0000000000..d2b0782b07
--- /dev/null
+++ b/code/modules/spells/spell_types/taeclowndo.dm
@@ -0,0 +1,86 @@
+/obj/effect/proc_holder/spell/targeted/conjure_item/summon_pie
+ name = "Summon Creampie"
+ desc = "A clown's weapon of choice. Use this to summon a fresh pie, just waiting to acquaintain itself with someone's face."
+ invocation_type = "none"
+ include_user = 1
+ range = -1
+ clothes_req = 0
+ item_type = /obj/item/reagent_containers/food/snacks/pie/cream
+
+ charge_max = 30
+ cooldown_min = 30
+ action_icon = 'icons/obj/food/piecake.dmi'
+ action_icon_state = "pie"
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/obj/effect/proc_holder/spell/aimed/banana_peel
+ name = "Conjure Banana Peel"
+ desc = "Make a banana peel appear out of thin air right under someone's feet!"
+ charge_type = "recharge"
+ charge_max = 100
+ cooldown_min = 100
+ clothes_req = 0
+ invocation_type = "none"
+ range = 7
+ selection_type = "view"
+ projectile_type = null
+
+ active_msg = "You focus, your mind reaching to the clown dimension, ready to make a peel matrialize wherever you want!"
+ deactive_msg = "You relax, the peel remaining right in the \"thin air\" it would appear out of."
+ action_icon = 'icons/obj/hydroponics/harvest.dmi'
+ base_icon_state = "banana_peel"
+ action_icon_state = "banana"
+
+
+/obj/effect/proc_holder/spell/aimed/banana_peel/cast(list/targets, mob/user = usr)
+ var/target = get_turf(targets[1])
+
+ if(get_dist(user,target)>range)
+ to_chat(user, "\The [target] is too far away!")
+ return
+
+ . = ..()
+ new /obj/item/grown/bananapeel(target)
+
+/obj/effect/proc_holder/spell/aimed/banana_peel/update_icon()
+ if(!action)
+ return
+ if(active)
+ action.button_icon_state = base_icon_state
+ else
+ action.button_icon_state = action_icon_state
+
+ action.UpdateButtonIcon()
+ return
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/obj/effect/proc_holder/spell/targeted/touch/megahonk
+ name = "Mega HoNk"
+ desc = "This spell channels your inner clown powers, concentrating them into one massive HONK."
+ hand_path = /obj/item/melee/touch_attack/megahonk
+
+ charge_max = 100
+ clothes_req = 0
+ cooldown_min = 100
+
+ action_icon = 'icons/mecha/mecha_equipment.dmi'
+ action_icon_state = "mecha_honker"
+
+/////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/obj/effect/proc_holder/spell/targeted/touch/bspie
+ name = "Bluespace Banana Pie"
+ desc = "An entire body would fit in there!"
+ hand_path = /obj/item/melee/touch_attack/bspie
+
+ charge_max = 450
+ clothes_req = 0
+ cooldown_min = 450
+
+ action_icon = 'icons/obj/food/piecake.dmi'
+ action_icon_state = "frostypie"
+
+
+
+
diff --git a/code/modules/surgery/bodyparts/bodyparts.dm b/code/modules/surgery/bodyparts/bodyparts.dm
index 71b659b73f..e35b8efbd0 100644
--- a/code/modules/surgery/bodyparts/bodyparts.dm
+++ b/code/modules/surgery/bodyparts/bodyparts.dm
@@ -48,7 +48,7 @@
var/body_markings = "" //for bodypart markings
var/body_markings_icon = 'modular_citadel/icons/mob/mam_markings.dmi'
var/list/markings_color = list()
- var/auxmarking
+ var/auxmarking = ""
var/list/auxmarking_color = list()
var/animal_origin = null //for nonhuman bodypart (e.g. monkey)
@@ -261,7 +261,6 @@
icon = DEFAULT_BODYPART_ICON_ORGANIC
else if(status == BODYPART_ROBOTIC)
icon = DEFAULT_BODYPART_ICON_ROBOTIC
- body_markings = null
if(owner)
owner.updatehealth()
@@ -292,6 +291,7 @@
should_draw_greyscale = FALSE
no_update = TRUE
body_markings = "husk" // reeee
+ auxmarking = "husk"
if(no_update)
return
@@ -336,17 +336,14 @@
if("mam_body_markings" in S.default_features)
var/datum/sprite_accessory/Smark
Smark = GLOB.mam_body_markings_list[H.dna.features["mam_body_markings"]]
+ body_markings_icon = Smark.icon
if(H.dna.features.["mam_body_markings"] != "None")
- body_markings_icon = Smark.icon
body_markings = lowertext(H.dna.features.["mam_body_markings"])
- if(MATRIXED)
- markings_color = list(colorlist)
+ auxmarking = lowertext(H.dna.features.["mam_body_markings"])
else
body_markings = "plain"
- markings_color = (H.dna.features.["mcolor"])
- else
- body_markings = null
- markings_color = ""
+ auxmarking = "plain"
+ markings_color = list(colorlist)
if(!dropping_limb && H.dna.check_mutation(HULK))
mutation_color = "00aa00"
@@ -361,6 +358,7 @@
if(status == BODYPART_ROBOTIC)
dmg_overlay_type = "robotic"
body_markings = null
+ auxmarking = null
if(dropping_limb)
no_update = TRUE //when attached, the limb won't be affected by the appearance changes of its mob owner.
@@ -394,7 +392,8 @@
. += image('icons/mob/dam_mob.dmi', "[dmg_overlay_type]_[body_zone]_[brutestate]0", -DAMAGE_LAYER, image_dir)
if(burnstate)
. += image('icons/mob/dam_mob.dmi', "[dmg_overlay_type]_[body_zone]_0[burnstate]", -DAMAGE_LAYER, image_dir)
- if(body_markings && status != BODYPART_ROBOTIC)
+
+ if(!isnull(body_markings) && status == BODYPART_ORGANIC)
if(!use_digitigrade)
if(BODY_ZONE_CHEST)
. += image(body_markings_icon, "[body_markings]_[body_zone]_[icon_gender]", -MARKING_LAYER, image_dir)
@@ -450,7 +449,7 @@
limb.icon_state = "[species_id]_[body_zone]"
// Body markings
- if(body_markings)
+ if(!isnull(body_markings))
if(species_id == "husk")
marking = image('modular_citadel/icons/mob/markings_notmammals.dmi', "husk_[body_zone]", -MARKING_LAYER, image_dir)
else if(species_id == "husk" && use_digitigrade)
@@ -470,7 +469,7 @@
if(aux_zone)
aux = image(limb.icon, "[species_id]_[aux_zone]", -aux_layer, image_dir)
. += aux
- if(body_markings)
+ if(!isnull(auxmarking))
if(species_id == "husk")
auxmarking = image('modular_citadel/icons/mob/markings_notmammals.dmi', "husk_[aux_zone]", -aux_layer, image_dir)
else
@@ -483,17 +482,18 @@
limb.icon_state = "[body_zone]_[icon_gender]"
else
limb.icon_state = "[body_zone]"
+
if(aux_zone)
- aux = image(limb.icon, "[species_id]_[aux_zone]", -aux_layer, image_dir)
+ aux = image(limb.icon, "[aux_zone]", -aux_layer, image_dir)
. += aux
- if(body_markings)
+ if(!isnull(auxmarking))
if(species_id == "husk")
auxmarking = image('modular_citadel/icons/mob/markings_notmammals.dmi', "husk_[aux_zone]", -aux_layer, image_dir)
else
auxmarking = image(body_markings_icon, "[body_markings]_[aux_zone]", -aux_layer, image_dir)
. += auxmarking
- if(body_markings)
+ if(!isnull(body_markings))
if(species_id == "husk")
marking = image('modular_citadel/icons/mob/markings_notmammals.dmi', "husk_[body_zone]", -MARKING_LAYER, image_dir)
else if(species_id == "husk" && use_digitigrade)
@@ -509,17 +509,16 @@
. += marking
return
-
if(should_draw_greyscale)
var/draw_color = mutation_color || species_color || (skin_tone && skintone2hex(skin_tone))
if(draw_color)
limb.color = "#[draw_color]"
if(aux_zone)
aux.color = "#[draw_color]"
- if(body_markings)
+ if(!isnull(auxmarking))
auxmarking.color = list(markings_color)
- if(body_markings)
+ if(!isnull(body_markings))
if(species_id == "husk")
marking.color = "#141414"
else
diff --git a/code/modules/surgery/organs/tongue.dm b/code/modules/surgery/organs/tongue.dm
index d5b2d16e67..0e672de225 100644
--- a/code/modules/surgery/organs/tongue.dm
+++ b/code/modules/surgery/organs/tongue.dm
@@ -17,6 +17,7 @@
/datum/language/beachbum,
/datum/language/ratvar,
/datum/language/aphasia,
+ /datum/language/slime,
))
/obj/item/organ/tongue/Initialize(mapload)
diff --git a/code/modules/uplink/uplink_items.dm b/code/modules/uplink/uplink_items.dm
index 503b1bd743..a5872b6741 100644
--- a/code/modules/uplink/uplink_items.dm
+++ b/code/modules/uplink/uplink_items.dm
@@ -388,7 +388,7 @@ GLOBAL_LIST_INIT(uplink_items, subtypesof(/datum/uplink_item))
autoinjectors for rapid application on up to two targets each, a syringe, and a bottle containing \
the BVAK solution."
item = /obj/item/storage/box/syndie_kit/tuberculosisgrenade
- cost = 12
+ cost = 8
surplus = 35
include_modes = list(/datum/game_mode/nuclear, /datum/game_mode/nuclear/clown_ops)
restricted = TRUE
@@ -768,7 +768,7 @@ GLOBAL_LIST_INIT(uplink_items, subtypesof(/datum/uplink_item))
The concussive effect from the explosion will knock the recipient out for a short period, and deafen \
them for longer. Beware, it has a chance to detonate your PDA."
item = /obj/item/cartridge/virus/syndicate
- cost = 6
+ cost = 5
restricted = TRUE
/datum/uplink_item/stealthy_weapons/suppressor
@@ -837,6 +837,13 @@ GLOBAL_LIST_INIT(uplink_items, subtypesof(/datum/uplink_item))
surplus = 0
include_modes = list(/datum/game_mode/nuclear/clown_ops)
+datum/uplink_item/stealthy_tools/taeclowndo_shoes
+ name = "Tae-clown-do Shoes"
+ desc = "A pair of shoes for the most elite agents of the honkmotherland. They grant the mastery of taeclowndo with some honk-fu moves as long as they're worn."
+ cost = 12
+ item = /obj/item/clothing/shoes/clown_shoes/taeclowndo
+ include_modes = list(/datum/game_mode/nuclear/clown_ops)
+
/datum/uplink_item/stealthy_tools/frame
name = "F.R.A.M.E. PDA Cartridge"
desc = "When inserted into a personal digital assistant, this cartridge gives you five PDA viruses which \
@@ -894,7 +901,7 @@ GLOBAL_LIST_INIT(uplink_items, subtypesof(/datum/uplink_item))
desc = "Screwed up and have security on your tail? This handy syringe will give you a completely new identity \
and appearance."
item = /obj/item/reagent_containers/syringe/mulligan
- cost = 4
+ cost = 3
surplus = 30
exclude_modes = list(/datum/game_mode/nuclear, /datum/game_mode/nuclear/clown_ops)
@@ -1208,7 +1215,7 @@ GLOBAL_LIST_INIT(uplink_items, subtypesof(/datum/uplink_item))
name = "Syndicate Sentience Potion"
item = /obj/item/slimepotion/slime/sentience/nuclear
desc = "A potion recovered at great risk by undercover syndicate operatives and then subsequently modified with syndicate technology. Using it will make any animal sentient, and bound to serve you, as well as implanting an internal radio for communication and an internal ID card for opening doors."
- cost = 4
+ cost = 2
include_modes = list(/datum/game_mode/nuclear, /datum/game_mode/nuclear/clown_ops)
restricted = TRUE
@@ -1382,6 +1389,13 @@ GLOBAL_LIST_INIT(uplink_items, subtypesof(/datum/uplink_item))
item = /obj/item/dnainjector/clumsymut
restricted_roles = list("Clown")
+/datum/uplink_item/role_restricted/taeclowndo_shoes
+ name = "Tae-clown-do Shoes"
+ desc = "A pair of shoes for the most elite agents of the honkmotherland. They grant the mastery of taeclowndo with some honk-fu moves as long as they're worn."
+ cost = 14
+ item = /obj/item/clothing/shoes/clown_shoes/taeclowndo
+ restricted_roles = list("Clown")
+
/datum/uplink_item/role_restricted/mimery
name = "Guide to Advanced Mimery Series"
desc = "The classical two part series on how to further hone your mime skills. Upon studying the series, the user should be able to make 3x1 invisible walls, and shoot bullets out of their fingers. Obviously only works for Mimes."
@@ -1448,7 +1462,7 @@ GLOBAL_LIST_INIT(uplink_items, subtypesof(/datum/uplink_item))
name = "Gold Toolbox"
desc = "A gold planted plastitanium toolbox loaded with tools. Comes with a set of AI detection multi-tool and a pare of combat gloves."
item = /obj/item/storage/toolbox/gold_real
- cost = 5 // Has synda tools + gloves + a robust weapon
+ cost = 3 // Has synda tools + gloves + a robust weapon
restricted_roles = list("Assistant", "Curator") //Curator do to being made of gold - It fits the theme
/datum/uplink_item/role_restricted/brainwash_disk
@@ -1457,7 +1471,7 @@ GLOBAL_LIST_INIT(uplink_items, subtypesof(/datum/uplink_item))
Insert into an Operating Console to enable the procedure."
item = /obj/item/disk/surgery/brainwashing
restricted_roles = list("Medical Doctor")
- cost = 5
+ cost = 3
/datum/uplink_item/role_restricted/haunted_magic_eightball
name = "Haunted Magic Eightball"
diff --git a/html/changelogs/AutoChangeLog-pr-8387.yml b/html/changelogs/AutoChangeLog-pr-8387.yml
new file mode 100644
index 0000000000..4770fe08fc
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-8387.yml
@@ -0,0 +1,4 @@
+author: "jtgsz"
+delete-after: True
+changes:
+ - rscadd: "ported gang mode"
diff --git a/html/changelogs/AutoChangeLog-pr-8424.yml b/html/changelogs/AutoChangeLog-pr-8424.yml
new file mode 100644
index 0000000000..12295e8a82
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-8424.yml
@@ -0,0 +1,4 @@
+author: "Arturlang"
+delete-after: True
+changes:
+ - rscadd: "You can now use CTRL and ALT click on pumps and filters to toggle them on and off and max their output respectively"
diff --git a/html/changelogs/AutoChangeLog-pr-8434.yml b/html/changelogs/AutoChangeLog-pr-8434.yml
new file mode 100644
index 0000000000..d01858d2b4
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-8434.yml
@@ -0,0 +1,4 @@
+author: "Ghommie"
+delete-after: True
+changes:
+ - bugfix: "Stops pulls of resting mobs breaking off whenever you swap turfs with someone else because of crawling delays."
diff --git a/html/changelogs/AutoChangeLog-pr-8436.yml b/html/changelogs/AutoChangeLog-pr-8436.yml
new file mode 100644
index 0000000000..6d626bf357
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-8436.yml
@@ -0,0 +1,4 @@
+author: "Trilbyspaceclone"
+delete-after: True
+changes:
+ - rscadd: "bio mass meat"
diff --git a/html/changelogs/AutoChangeLog-pr-8449.yml b/html/changelogs/AutoChangeLog-pr-8449.yml
new file mode 100644
index 0000000000..c2c397cdd0
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-8449.yml
@@ -0,0 +1,4 @@
+author: "Trilbyspaceclone"
+delete-after: True
+changes:
+ - tweak: "5->6"
diff --git a/html/changelogs/AutoChangeLog-pr-8452.yml b/html/changelogs/AutoChangeLog-pr-8452.yml
new file mode 100644
index 0000000000..2b4e430757
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-8452.yml
@@ -0,0 +1,4 @@
+author: "Trilbyspaceclone"
+delete-after: True
+changes:
+ - tweak: "mulligan costs 4 - > 3"
diff --git a/html/changelogs/AutoChangeLog-pr-8453.yml b/html/changelogs/AutoChangeLog-pr-8453.yml
new file mode 100644
index 0000000000..cda1843779
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-8453.yml
@@ -0,0 +1,4 @@
+author: "Trilbyspaceclone"
+delete-after: True
+changes:
+ - tweak: "Tuberculosis 12tc - > 8tc"
diff --git a/html/changelogs/AutoChangeLog-pr-8459.yml b/html/changelogs/AutoChangeLog-pr-8459.yml
new file mode 100644
index 0000000000..ebce2f0db1
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-8459.yml
@@ -0,0 +1,4 @@
+author: "Trilbyspaceclone"
+delete-after: True
+changes:
+ - tweak: "2 < - 4"
diff --git a/html/changelogs/AutoChangeLog-pr-8463.yml b/html/changelogs/AutoChangeLog-pr-8463.yml
new file mode 100644
index 0000000000..a6c8d05522
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-8463.yml
@@ -0,0 +1,4 @@
+author: "Trilbyspaceclone"
+delete-after: True
+changes:
+ - rscadd: "shield + crafting"
diff --git a/html/changelogs/AutoChangeLog-pr-8466.yml b/html/changelogs/AutoChangeLog-pr-8466.yml
new file mode 100644
index 0000000000..a3cffc8f59
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-8466.yml
@@ -0,0 +1,4 @@
+author: "Poojawa"
+delete-after: True
+changes:
+ - bugfix: "Atmos and Station alerts should be more alerting."
diff --git a/html/changelogs/AutoChangeLog-pr-8470.yml b/html/changelogs/AutoChangeLog-pr-8470.yml
new file mode 100644
index 0000000000..39399c0724
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-8470.yml
@@ -0,0 +1,5 @@
+author: "Ghommie"
+delete-after: True
+changes:
+ - balance: "Buffs condensed capsaicin, a yet another feature previously dunked by stam combat."
+ - balance: "speeds up pepper spray puffs."
diff --git a/html/changelogs/AutoChangeLog-pr-8473.yml b/html/changelogs/AutoChangeLog-pr-8473.yml
new file mode 100644
index 0000000000..a4743c9a39
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-8473.yml
@@ -0,0 +1,10 @@
+author: "Useroth"
+delete-after: True
+changes:
+ - rscadd: "Taeclowndo shoes which grant the four following abilities:
+- Conjuring a cream pie right into their hand. Every three seconds.
+- Making a banana peel appear out of thin air at the tile of the clown's choice. Every ten seconds.
+- Mega HoNk. A touch-ranged, very small AOE ability, with effect equal to being honked by a Honkerblast on a clown mech, with the effects halved for anyone who isn't its direct target. Every ten seconds.
+- Bluespace Banana Pie. You don't throw this one... not right away at least. This baby can fit an entire body inside. Good for disposal of evidence. 25 second-long action, 45 second cooldown. Also produces a \"[victim's name] cream pie\". The body drops out of the pie if you splat it somewhere or destroy the pie. If you eat it, it will chestburst out of you a'la monkey cube.
+
+It's a 14 TC item for traitor clowns and a 12 TC item for clown-ops."
diff --git a/html/changelogs/AutoChangeLog-pr-8477.yml b/html/changelogs/AutoChangeLog-pr-8477.yml
new file mode 100644
index 0000000000..f5478ed178
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-8477.yml
@@ -0,0 +1,4 @@
+author: "deathride58"
+delete-after: True
+changes:
+ - rscadd: "Ported the zulie cloak and blackredgold coat donor items from RP."
diff --git a/html/changelogs/AutoChangeLog-pr-8482.yml b/html/changelogs/AutoChangeLog-pr-8482.yml
new file mode 100644
index 0000000000..095f93a7a3
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-8482.yml
@@ -0,0 +1,4 @@
+author: "Ghommie"
+delete-after: True
+changes:
+ - bugfix: "fixes eyestabbing people with cutlery while being a pacifist."
diff --git a/html/changelogs/AutoChangeLog-pr-8485.yml b/html/changelogs/AutoChangeLog-pr-8485.yml
new file mode 100644
index 0000000000..abc79f1f30
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-8485.yml
@@ -0,0 +1,5 @@
+author: "Trilbyspaceclone"
+delete-after: True
+changes:
+ - rscadd: "More plushies"
+ - tweak: "attack verbs and descs"
diff --git a/html/changelogs/AutoChangeLog-pr-8489.yml b/html/changelogs/AutoChangeLog-pr-8489.yml
new file mode 100644
index 0000000000..5e90c56d67
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-8489.yml
@@ -0,0 +1,5 @@
+author: "Coolgat3"
+delete-after: True
+changes:
+ - rscadd: "Added combat gloves sprite"
+ - imageadd: "Added said sprite"
diff --git a/html/changelogs/AutoChangeLog-pr-8491.yml b/html/changelogs/AutoChangeLog-pr-8491.yml
new file mode 100644
index 0000000000..0e82e809f7
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-8491.yml
@@ -0,0 +1,5 @@
+author: "Ghommie"
+delete-after: True
+changes:
+ - bugfix: "Reduces goonchat lag from being blasted by pellets and bullets repeatedly whilst wearing armor by properly removing the armor protection texts this times."
+ - spellcheck: "also reduced the size of armor protection messages in general. they clog up the chat box."
diff --git a/html/changelogs/AutoChangeLog-pr-8495.yml b/html/changelogs/AutoChangeLog-pr-8495.yml
new file mode 100644
index 0000000000..92777ef8b0
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-8495.yml
@@ -0,0 +1,4 @@
+author: "BurgerBB"
+delete-after: True
+changes:
+ - tweak: "\"Unwillingly\" eating food now sends a warning message instead of a notice. Unable to stuff food down your throat sends a danger message instead of a warning message."
diff --git a/html/changelogs/AutoChangeLog-pr-8504.yml b/html/changelogs/AutoChangeLog-pr-8504.yml
new file mode 100644
index 0000000000..4e80d02845
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-8504.yml
@@ -0,0 +1,4 @@
+author: "Trilbyspaceclone"
+delete-after: True
+changes:
+ - bugfix: "resonators being so shitty"
diff --git a/html/changelogs/AutoChangeLog-pr-8506.yml b/html/changelogs/AutoChangeLog-pr-8506.yml
new file mode 100644
index 0000000000..6711217fb5
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-8506.yml
@@ -0,0 +1,5 @@
+author: "CalamaBanana"
+delete-after: True
+changes:
+ - rscadd: "Added Deer taur"
+ - rscadd: "Added Elf ears to mammals"
diff --git a/html/changelogs/AutoChangeLog-pr-8515.yml b/html/changelogs/AutoChangeLog-pr-8515.yml
new file mode 100644
index 0000000000..99e414f5e1
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-8515.yml
@@ -0,0 +1,5 @@
+author: "Ghommie"
+delete-after: True
+changes:
+ - bugfix: "Fixes stunbatons icon not properly updating on cell removal and insertion."
+ - tweak: "Allows lower charge cells to be used with stun batons, and thus single use crapshots batons."
diff --git a/html/changelogs/AutoChangeLog-pr-8516.yml b/html/changelogs/AutoChangeLog-pr-8516.yml
new file mode 100644
index 0000000000..f7419f1add
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-8516.yml
@@ -0,0 +1,4 @@
+author: "Ghommie"
+delete-after: True
+changes:
+ - balance: "Adds in a 7 seconds delay to the jackhammer dismantling a superheated clockwork wall."
diff --git a/html/changelogs/AutoChangeLog-pr-8517.yml b/html/changelogs/AutoChangeLog-pr-8517.yml
new file mode 100644
index 0000000000..5e14c1c696
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-8517.yml
@@ -0,0 +1,6 @@
+author: "Poojawa"
+delete-after: True
+changes:
+ - rscadd: "Added visible and hidden testicles"
+ - rscadd: "Added multi-boob support. Now you can have two or three pairs of breasts."
+ - bugfix: "fixed missing vagina and breast sprites"
diff --git a/html/changelogs/AutoChangeLog-pr-8518.yml b/html/changelogs/AutoChangeLog-pr-8518.yml
new file mode 100644
index 0000000000..cbc34893b6
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-8518.yml
@@ -0,0 +1,5 @@
+author: "Ghommie"
+delete-after: True
+changes:
+ - tweak: "escape pods emergency suits storage can now be busted open by emags or excessive damage."
+ - bugfix: "Fixes alt click bypassing the escape pods' suits storage lock."
diff --git a/html/changelogs/AutoChangeLog-pr-8524.yml b/html/changelogs/AutoChangeLog-pr-8524.yml
new file mode 100644
index 0000000000..66a7b6d456
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-8524.yml
@@ -0,0 +1,4 @@
+author: "BurgerBB"
+delete-after: True
+changes:
+ - rscadd: "Adds a new trait \"Buns of Steel\" that makes you immune to the effects of ass slapping, and temporarily makes the user's arm useless like a stun baton hit. It costs 0 points."
diff --git a/html/changelogs/AutoChangeLog-pr-8528.yml b/html/changelogs/AutoChangeLog-pr-8528.yml
new file mode 100644
index 0000000000..0dece1e63d
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-8528.yml
@@ -0,0 +1,7 @@
+author: "Poojawa"
+delete-after: True
+changes:
+ - bugfix: "fixed prosthetic hands being invisible"
+ - bugfix: "male foxes exist again"
+ - bugfix: "female chest markings improved from being too dark in comparison to their other colors, blending better"
+ - bugfix: "Markings behave better on non-organic limbs."
diff --git a/icons/effects/crayondecal.dmi b/icons/effects/crayondecal.dmi
index bb48832025..fcd27698e7 100644
Binary files a/icons/effects/crayondecal.dmi and b/icons/effects/crayondecal.dmi differ
diff --git a/icons/mob/custom_w.dmi b/icons/mob/custom_w.dmi
index 0c872625dd..dcb36e7b47 100644
Binary files a/icons/mob/custom_w.dmi and b/icons/mob/custom_w.dmi differ
diff --git a/icons/mob/head.dmi b/icons/mob/head.dmi
index 3da5fc08e2..09d6fe5374 100644
Binary files a/icons/mob/head.dmi and b/icons/mob/head.dmi differ
diff --git a/icons/mob/hud.dmi b/icons/mob/hud.dmi
index 047f080946..10474f94da 100644
Binary files a/icons/mob/hud.dmi and b/icons/mob/hud.dmi differ
diff --git a/icons/mob/suit.dmi b/icons/mob/suit.dmi
index b518157bb5..0f3438dfc4 100644
Binary files a/icons/mob/suit.dmi and b/icons/mob/suit.dmi differ
diff --git a/icons/obj/clothing/gloves.dmi b/icons/obj/clothing/gloves.dmi
index a90efcdfea..619db9290d 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 d905979c9c..8256c1fc07 100644
Binary files a/icons/obj/clothing/hats.dmi and b/icons/obj/clothing/hats.dmi differ
diff --git a/icons/obj/clothing/suits.dmi b/icons/obj/clothing/suits.dmi
index 8e0359e6b3..2cb473fb00 100644
Binary files a/icons/obj/clothing/suits.dmi and b/icons/obj/clothing/suits.dmi differ
diff --git a/icons/obj/custom.dmi b/icons/obj/custom.dmi
index 9a7d460e14..9faaf8f81a 100644
Binary files a/icons/obj/custom.dmi and b/icons/obj/custom.dmi differ
diff --git a/icons/obj/device.dmi b/icons/obj/device.dmi
index becb58568f..55c33e5e83 100644
Binary files a/icons/obj/device.dmi and b/icons/obj/device.dmi differ
diff --git a/icons/obj/items_and_weapons.dmi b/icons/obj/items_and_weapons.dmi
index 543d57ff56..655d37d4a6 100644
Binary files a/icons/obj/items_and_weapons.dmi and b/icons/obj/items_and_weapons.dmi differ
diff --git a/icons/obj/plushes.dmi b/icons/obj/plushes.dmi
index 7ca7b068b0..68c51bb4c8 100644
Binary files a/icons/obj/plushes.dmi and b/icons/obj/plushes.dmi differ
diff --git a/modular_citadel/code/datums/traits/neutral.dm b/modular_citadel/code/datums/traits/neutral.dm
index 264dbfef0a..05aeb27361 100644
--- a/modular_citadel/code/datums/traits/neutral.dm
+++ b/modular_citadel/code/datums/traits/neutral.dm
@@ -4,6 +4,7 @@
name = "Nymphomania"
desc = "You're always feeling a bit in heat. Also, you get aroused faster than usual."
value = 0
+ mob_trait = TRAIT_NYMPHO
gain_text = "You are feeling extra wild."
lose_text = "You don't feel that burning sensation anymore."
@@ -22,3 +23,11 @@
if(M.canbearoused == FALSE)
to_chat(quirk_holder, "Having high libido is useless when you can't feel arousal at all!")
qdel(src)
+
+/datum/quirk/maso
+ name = "Masochism"
+ desc = "You are aroused by pain."
+ value = 0
+ mob_trait = TRAIT_MASO
+ gain_text = "You desire to be hurt."
+ lose_text = "Pain has become less exciting for you."
diff --git a/modular_citadel/code/game/gamemodes/gangs/dominator.dm b/modular_citadel/code/game/gamemodes/gangs/dominator.dm
new file mode 100644
index 0000000000..0d89c78010
--- /dev/null
+++ b/modular_citadel/code/game/gamemodes/gangs/dominator.dm
@@ -0,0 +1,243 @@
+#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
+#define DOM_REQUIRED_TURFS 32
+#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 = 100
+ 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(100, BURN, "energy", 0)
+ ..()
+
+/obj/machinery/dominator/hulk_damage()
+ return (max_integrity - integrity_failure) / DOM_HULK_HITS_REQUIRED
+
+/obj/machinery/dominator/tesla_act()
+ qdel(src)
+
+/obj/machinery/dominator/update_icon()
+ cut_overlays()
+ if(!(stat & BROKEN))
+ icon_state = "dominator-active"
+ 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)
+ else
+ icon_state = "dominator"
+ if(obj_integrity/max_integrity < 0.66)
+ add_overlay("damage")
+ else
+ icon_state = "dominator-broken"
+
+/obj/machinery/dominator/examine(mob/user)
+ ..()
+ if(stat & BROKEN)
+ return
+
+ if(gang && gang.domination_time != NOT_DOMINATING)
+ if(gang.domination_time > world.time)
+ to_chat(user, "Hostile Takeover in progress. Estimated [gang.domination_time_remaining()] seconds remain.")
+ else
+ to_chat(user, "Hostile Takeover of [station_name()] successful. Have a great day.")
+ else
+ to_chat(user, "System on standby.")
+ to_chat(user, "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(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_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
+ Cinematic(CINEMATIC_MALF,world) //Here is the gang victory trigger on the dominator ending.
+ gang.winner = TRUE
+ SSticker.force_ending = TRUE
+
+ if(!.)
+ STOP_PROCESSING(SSmachines, src)
+
+/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)
+ add_fingerprint(user)
+ ..()
+
+/obj/machinery/dominator/attack_hand(mob/user)
+ 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_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(get_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
\ No newline at end of file
diff --git a/modular_citadel/code/game/gamemodes/gangs/dominator_countdown.dm b/modular_citadel/code/game/gamemodes/gangs/dominator_countdown.dm
new file mode 100644
index 0000000000..c6ae610e37
--- /dev/null
+++ b/modular_citadel/code/game/gamemodes/gangs/dominator_countdown.dm
@@ -0,0 +1,13 @@
+/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"
\ No newline at end of file
diff --git a/modular_citadel/code/game/gamemodes/gangs/gang.dm b/modular_citadel/code/game/gamemodes/gangs/gang.dm
new file mode 100644
index 0000000000..00ce3f81a9
--- /dev/null
+++ b/modular_citadel/code/game/gamemodes/gangs/gang.dm
@@ -0,0 +1,476 @@
+/datum/antagonist/gang
+ name = "Gangster"
+ roundend_category = "gangsters"
+ can_coexist_with_others = FALSE
+ job_rank = ROLE_GANG
+ antagpanel_category = "Gang"
+ 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!")
+
+/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"
+
+/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)
+ 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)
+ 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)
+ 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)
+ 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.
+ to_chat(gangster, "You are now a member of the [name] Gang!")
+ to_chat(gangster, "Help your bosses take over the station by claiming territory with spraycans. Simply spray on any unclaimed area of the station.")
+ to_chat(gangster, "You can also use recruitment pens to recruit more to your cause, If your boss provides you one.")
+ to_chat(gangster, "Their ultimate objective is to take over the station with a Dominator machine.")
+ to_chat(gangster, "You can identify your mates by their large, \[G\] icon.")
+ 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.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
\ No newline at end of file
diff --git a/modular_citadel/code/game/gamemodes/gangs/gang_datums.dm b/modular_citadel/code/game/gamemodes/gangs/gang_datums.dm
new file mode 100644
index 0000000000..334d6bf423
--- /dev/null
+++ b/modular_citadel/code/game/gamemodes/gangs/gang_datums.dm
@@ -0,0 +1,139 @@
+// 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_jacket/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_jacket/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_jacket/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/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/nemes)
+
+/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/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/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/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)
\ No newline at end of file
diff --git a/modular_citadel/code/game/gamemodes/gangs/gang_decals.dm b/modular_citadel/code/game/gamemodes/gangs/gang_decals.dm
new file mode 100644
index 0000000000..6e5cb58891
--- /dev/null
+++ b/modular_citadel/code/game/gamemodes/gangs/gang_decals.dm
@@ -0,0 +1,38 @@
+/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 = GAME_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_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_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)
\ No newline at end of file
diff --git a/modular_citadel/code/game/gamemodes/gangs/gang_hud.dm b/modular_citadel/code/game/gamemodes/gangs/gang_hud.dm
new file mode 100644
index 0000000000..3fde6d4123
--- /dev/null
+++ b/modular_citadel/code/game/gamemodes/gangs/gang_hud.dm
@@ -0,0 +1,34 @@
+/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
+ ..()
\ No newline at end of file
diff --git a/modular_citadel/code/game/gamemodes/gangs/gang_items.dm b/modular_citadel/code/game/gamemodes/gangs/gang_items.dm
new file mode 100644
index 0000000000..0f16b6462b
--- /dev/null
+++ b/modular_citadel/code/game/gamemodes/gangs/gang_items.dm
@@ -0,0 +1,416 @@
+/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 = 22
+ 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
+ name = "Illicit Soporific Cartridges"
+ id = "sniper_ammo"
+ cost = 15
+ 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/syndieminibomb/concussion/frag
+
+/datum/gang_item/equipment/stimpack
+ name = "Black Market Stimulants"
+ id = "stimpack"
+ cost = 12
+ item_path = /obj/item/reagent_containers/syringe/stimulants
+
+/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/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_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)
\ No newline at end of file
diff --git a/modular_citadel/code/game/gamemodes/gangs/gang_pen.dm b/modular_citadel/code/game/gamemodes/gangs/gang_pen.dm
new file mode 100644
index 0000000000..3dfdf4dd9b
--- /dev/null
+++ b/modular_citadel/code/game/gamemodes/gangs/gang_pen.dm
@@ -0,0 +1,59 @@
+/*
+ * 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 && gangster_mind.current.has_trait(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.Knockdown(100)
+ gangster_mind.add_antag_datum(/datum/antagonist/gang, gang)
+ return TRUE
\ No newline at end of file
diff --git a/modular_citadel/code/game/gamemodes/gangs/gangs.dm b/modular_citadel/code/game/gamemodes/gangs/gangs.dm
new file mode 100644
index 0000000000..9151107d6f
--- /dev/null
+++ b/modular_citadel/code/game/gamemodes/gangs/gangs.dm
@@ -0,0 +1,65 @@
+//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
+ restricted_jobs = list("Security Officer", "Warden", "Detective", "AI", "Cyborg","Captain", "Head of Personnel", "Head of Security")
+ 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/modular_citadel/code/game/gamemodes/gangs/gangtool.dm b/modular_citadel/code/game/gamemodes/gangs/gangtool.dm
new file mode 100644
index 0000000000..9e828a7042
--- /dev/null
+++ b/modular_citadel/code/game/gamemodes/gangs/gangtool.dm
@@ -0,0 +1,259 @@
+//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") as null|text
+ 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
\ No newline at end of file
diff --git a/modular_citadel/code/game/gamemodes/gangs/implant_gang.dm b/modular_citadel/code/game/gamemodes/gangs/implant_gang.dm
new file mode 100644
index 0000000000..ee91928845
--- /dev/null
+++ b/modular_citadel/code/game/gamemodes/gangs/implant_gang.dm
@@ -0,0 +1,61 @@
+/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)
+ .=..()
\ No newline at end of file
diff --git a/modular_citadel/code/modules/arousal/arousal.dm b/modular_citadel/code/modules/arousal/arousal.dm
index ef9201af60..846a8cff62 100644
--- a/modular_citadel/code/modules/arousal/arousal.dm
+++ b/modular_citadel/code/modules/arousal/arousal.dm
@@ -40,7 +40,7 @@
adjustArousalLoss(arousal_rate * S.arousal_gain_rate)
if(dna.features["exhibitionist"] && client)
var/amt_nude = 0
- if(is_chest_exposed() && (gender == FEMALE || getorganslot("breasts")))
+ if(is_chest_exposed() && (getorganslot("breasts")))
amt_nude++
if(is_groin_exposed())
if(getorganslot("penis"))
@@ -64,14 +64,14 @@
/mob/living/proc/adjustArousalLoss(amount, updating_arousal=1)
if(status_flags & GODMODE || !canbearoused)
- return 0
+ return FALSE
arousalloss = CLAMP(arousalloss + amount, min_arousal, max_arousal)
if(updating_arousal)
updatearousal()
/mob/living/proc/setArousalLoss(amount, updating_arousal=1)
if(status_flags & GODMODE || !canbearoused)
- return 0
+ return FALSE
arousalloss = CLAMP(amount, min_arousal, max_arousal)
if(updating_arousal)
updatearousal()
@@ -99,6 +99,8 @@
switch(G.type)
if(/obj/item/organ/genital/penis)
S = GLOB.cock_shapes_list[G.shape]
+ if(/obj/item/organ/genital/testicles)
+ S = GLOB.balls_shapes_list[G.shape]
if(/obj/item/organ/genital/vagina)
S = GLOB.vagina_shapes_list[G.shape]
if(/obj/item/organ/genital/breasts)
@@ -112,54 +114,54 @@
G.update_appearance()
/mob/living/proc/update_arousal_hud()
- return 0
+ return FALSE
/datum/species/proc/update_arousal_hud(mob/living/carbon/human/H)
- return 0
+ return FALSE
/mob/living/carbon/human/update_arousal_hud()
if(!client || !hud_used)
- return 0
+ return FALSE
if(dna.species.update_arousal_hud())
- return 0
+ return FALSE
if(!canbearoused)
hud_used.arousal.icon_state = ""
- return 0
+ return FALSE
else
if(hud_used.arousal)
if(stat == DEAD)
hud_used.arousal.icon_state = "arousal0"
- return 1
+ return TRUE
if(getArousalLoss() == max_arousal)
hud_used.arousal.icon_state = "arousal100"
- return 1
+ return TRUE
if(getArousalLoss() >= (max_arousal / 100) * 90)//M O D U L A R , W O W
hud_used.arousal.icon_state = "arousal90"
- return 1
+ return TRUE
if(getArousalLoss() >= (max_arousal / 100) * 80)//M O D U L A R , W O W
hud_used.arousal.icon_state = "arousal80"
- return 1
+ return TRUE
if(getArousalLoss() >= (max_arousal / 100) * 70)//M O D U L A R , W O W
hud_used.arousal.icon_state = "arousal70"
- return 1
+ return TRUE
if(getArousalLoss() >= (max_arousal / 100) * 60)//M O D U L A R , W O W
hud_used.arousal.icon_state = "arousal60"
- return 1
+ return TRUE
if(getArousalLoss() >= (max_arousal / 100) * 50)//M O D U L A R , W O W
hud_used.arousal.icon_state = "arousal50"
- return 1
+ return TRUE
if(getArousalLoss() >= (max_arousal / 100) * 40)//M O D U L A R , W O W
hud_used.arousal.icon_state = "arousal40"
- return 1
+ return TRUE
if(getArousalLoss() >= (max_arousal / 100) * 30)//M O D U L A R , W O W
hud_used.arousal.icon_state = "arousal30"
- return 1
+ return TRUE
if(getArousalLoss() >= (max_arousal / 100) * 20)//M O D U L A R , W O W
hud_used.arousal.icon_state = "arousal10"
- return 1
+ return TRUE
if(getArousalLoss() >= (max_arousal / 100) * 10)//M O D U L A R , W O W
hud_used.arousal.icon_state = "arousal10"
- return 1
+ return TRUE
else
hud_used.arousal.icon_state = "arousal0"
@@ -171,11 +173,11 @@
/obj/screen/arousal/Click()
if(!isliving(usr))
- return 0
+ return FALSE
var/mob/living/M = usr
if(M.canbearoused)
M.mob_climax()
- return 1
+ return TRUE
else
to_chat(M, "Arousal is disabled. Feature is unavailable.")
@@ -196,13 +198,6 @@
"You have relieved yourself.")
SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "orgasm", /datum/mood_event/orgasm)
setArousalLoss(min_arousal)
- /*
- switch(gender)
- if(MALE)
- PoolOrNew(/obj/effect/decal/cleanable/semen, loc)
- if(FEMALE)
- PoolOrNew(/obj/effect/decal/cleanable/femcum, loc)
- */
else
to_chat(src, "You aren't aroused enough for that.")
diff --git a/modular_citadel/code/modules/arousal/organs/breasts.dm b/modular_citadel/code/modules/arousal/organs/breasts.dm
index 1239a515cd..1223f0b616 100644
--- a/modular_citadel/code/modules/arousal/organs/breasts.dm
+++ b/modular_citadel/code/modules/arousal/organs/breasts.dm
@@ -5,7 +5,6 @@
icon = 'modular_citadel/icons/obj/genitals/breasts.dmi'
zone = "chest"
slot = "breasts"
- w_class = 3
size = BREASTS_SIZE_DEF
fluid_id = "milk"
var/amount = 2
@@ -14,7 +13,7 @@
can_masturbate_with = TRUE
masturbation_verb = "massage"
can_climax = TRUE
- fluid_transfer_factor =0.5
+ fluid_transfer_factor = 0.5
/obj/item/organ/genital/breasts/Initialize()
. = ..()
@@ -40,6 +39,10 @@
switch(lowershape)
if("pair")
desc = "You see a pair of breasts."
+ if("quad")
+ desc = "You see two pairs of breast, one just under the other."
+ if("sextuple")
+ desc = "You see three sets of breasts, running from their chest to their belly."
else
desc = "You see some breasts, they seem to be quite exotic."
if (size)
@@ -54,13 +57,11 @@
if(ishuman(owner)) // Check before recasting type, although someone fucked up if you're not human AND have use_skintones somehow...
var/mob/living/carbon/human/H = owner // only human mobs have skin_tone, which we need.
color = "#[skintone2hex(H.skin_tone)]"
- string = "breasts_[lowertext(shape)]_[size]-s"
+ string = "breasts_[GLOB.breasts_shapes_icons[shape]]_[size]-s"
else
color = "#[owner.dna.features["breasts_color"]]"
- string = "breasts_[lowertext(shape)]_[size]"
+ string = "breasts_[GLOB.breasts_shapes_icons[shape]]_[size]"
if(ishuman(owner))
var/mob/living/carbon/human/H = owner
+ icon_state = sanitize_text(string)
H.update_genitals()
-
- icon_state = sanitize_text(string)
-
diff --git a/modular_citadel/code/modules/arousal/organs/eggsack.dm b/modular_citadel/code/modules/arousal/organs/eggsack.dm
index 27104cd36a..402d246e40 100644
--- a/modular_citadel/code/modules/arousal/organs/eggsack.dm
+++ b/modular_citadel/code/modules/arousal/organs/eggsack.dm
@@ -6,7 +6,6 @@
zone = "groin"
slot = "testicles"
color = null //don't use the /genital color since it already is colored
- w_class = 3
internal = TRUE
var/egg_girth = EGG_GIRTH_DEF
var/cum_mult = CUM_RATE_MULT
diff --git a/modular_citadel/code/modules/arousal/organs/genitals.dm b/modular_citadel/code/modules/arousal/organs/genitals.dm
index 591bb6f1de..cbf14e420a 100644
--- a/modular_citadel/code/modules/arousal/organs/genitals.dm
+++ b/modular_citadel/code/modules/arousal/organs/genitals.dm
@@ -1,25 +1,26 @@
/obj/item/organ/genital
color = "#fcccb3"
- var/shape = "human"
- var/sensitivity = 1
- var/list/genital_flags = list()
- var/can_masturbate_with = FALSE
- var/masturbation_verb = "masturbate"
- var/can_climax = FALSE
- var/fluid_transfer_factor = 0.0 //How much would a partner get in them if they climax using this?
- var/size = 2 //can vary between num or text, just used in icon_state strings
- var/fluid_id = null
- var/fluid_max_volume = 50
- var/fluid_efficiency = 1
- var/fluid_rate = 1
- var/fluid_mult = 1
- var/producing = FALSE
- var/aroused_state = FALSE //Boolean used in icon_state strings
- var/aroused_amount = 50 //This is a num from 0 to 100 for arousal percentage for when to use arousal state icons.
+ w_class = WEIGHT_CLASS_NORMAL
+ var/shape = "human"
+ var/sensitivity = AROUSAL_START_VALUE
+ var/list/genital_flags = list()
+ var/can_masturbate_with = FALSE
+ var/masturbation_verb = "masturbate"
+ var/can_climax = FALSE
+ var/fluid_transfer_factor = 0.0 //How much would a partner get in them if they climax using this?
+ var/size = 2 //can vary between num or text, just used in icon_state strings
+ var/fluid_id = null
+ var/fluid_max_volume = 50
+ var/fluid_efficiency = 1
+ var/fluid_rate = 1
+ var/fluid_mult = 1
+ var/producing = FALSE
+ var/aroused_state = FALSE //Boolean used in icon_state strings
+ var/aroused_amount = 50 //This is a num from 0 to 100 for arousal percentage for when to use arousal state icons.
var/obj/item/organ/genital/linked_organ
- var/through_clothes = FALSE
- var/internal = FALSE
- var/hidden = FALSE
+ var/through_clothes = FALSE
+ var/internal = FALSE
+ var/hidden = FALSE
/obj/item/organ/genital/Initialize()
. = ..()
@@ -140,14 +141,14 @@
if (NOGENITALS in dna.species.species_traits)
return
//Order should be very important. FIRST vagina, THEN testicles, THEN penis, as this affects the order they are rendered in.
- if(dna.features["has_breasts"])
- give_breasts()
if(dna.features["has_vag"])
give_vagina()
if(dna.features["has_womb"])
give_womb()
if(dna.features["has_balls"])
give_balls()
+ if(dna.features["has_breasts"]) // since we have multi-boobs as a thing, we'll want to at least draw over these. but not over the pingas.
+ give_breasts()
if(dna.features["has_cock"])
give_penis()
if(dna.features["has_ovi"])
@@ -165,7 +166,7 @@
P.Insert(src)
if(P)
if(dna.species.use_skintones && dna.features["genitals_use_skintone"])
- P.color = skintone2hex(skin_tone)
+ P.color = "#[skintone2hex(skin_tone)]"
else
P.color = "#[dna.features["cock_color"]]"
P.length = dna.features["cock_length"]
@@ -181,13 +182,18 @@
if(!getorganslot("testicles"))
var/obj/item/organ/genital/testicles/T = new
T.Insert(src)
-// if(dna.species.use_skintones && dna.features["genitals_use_skintone"])
-// T.color = skintone2hex(skin_tone)
-// else
-// T.color = "#[dna.features["balls_color"]]"
if(T)
+ if(dna.species.use_skintones && dna.features["genitals_use_skintone"])
+ T.color = "#[skintone2hex(skin_tone)]"
+ else
+ T.color = "#[dna.features["balls_color"]]"
T.size = dna.features["balls_size"]
T.sack_size = dna.features["balls_sack_size"]
+ T.shape = dna.features["balls_shape"]
+ if(dna.features["balls_shape"] == "Hidden")
+ T.internal = TRUE
+ else
+ T.internal = FALSE
T.fluid_id = dna.features["balls_fluid"]
T.fluid_rate = dna.features["balls_cum_rate"]
T.fluid_mult = dna.features["balls_cum_mult"]
@@ -204,7 +210,7 @@
B.Insert(src)
if(B)
if(dna.species.use_skintones && dna.features["genitals_use_skintone"])
- B.color = skintone2hex(skin_tone)
+ B.color = "#[skintone2hex(skin_tone)]"
else
B.color = "#[dna.features["breasts_color"]]"
B.size = dna.features["breasts_size"]
@@ -228,7 +234,7 @@
V.Insert(src)
if(V)
if(dna.species.use_skintones && dna.features["genitals_use_skintone"])
- V.color = skintone2hex(skin_tone)
+ V.color = "#[skintone2hex(skin_tone)]"
else
V.color = "[dna.features["vag_color"]]"
V.shape = "[dna.features["vag_shape"]]"
@@ -311,6 +317,8 @@
switch(G.type)
if(/obj/item/organ/genital/penis)
S = GLOB.cock_shapes_list[G.shape]
+ if(/obj/item/organ/genital/testicles)
+ S = GLOB.balls_shapes_list[G.shape]
if(/obj/item/organ/genital/vagina)
S = GLOB.vagina_shapes_list[G.shape]
if(/obj/item/organ/genital/breasts)
@@ -318,6 +326,7 @@
if(!S || S.icon_state == "none")
continue
+
var/mutable_appearance/genital_overlay = mutable_appearance(S.icon, layer = -layer)
genital_overlay.icon_state = "[G.slot]_[S.icon_state]_[size]_[aroused_state]_[layertext]"
@@ -331,12 +340,15 @@
switch(S.color_src)
if("cock_color")
genital_overlay.color = "#[H.dna.features["cock_color"]]"
+ if("balls_color")
+ genital_overlay.color = "#[H.dna.features["balls_color"]]"
if("breasts_color")
genital_overlay.color = "#[H.dna.features["breasts_color"]]"
if("vag_color")
genital_overlay.color = "#[H.dna.features["vag_color"]]"
standing += genital_overlay
+
if(LAZYLEN(standing))
H.overlays_standing[layer] = standing.Copy()
standing = list()
diff --git a/modular_citadel/code/modules/arousal/organs/genitals_sprite_accessories.dm b/modular_citadel/code/modules/arousal/organs/genitals_sprite_accessories.dm
index e857c0d7ed..b913a90fb6 100644
--- a/modular_citadel/code/modules/arousal/organs/genitals_sprite_accessories.dm
+++ b/modular_citadel/code/modules/arousal/organs/genitals_sprite_accessories.dm
@@ -55,8 +55,24 @@
center = TRUE //Center the image 'cause 2-tile wide.
dimension_x = 64
+//Testicles
+//These ones aren't inert
+/datum/sprite_accessory/testicles
+ icon = 'modular_citadel/icons/obj/genitals/testicles_onmob.dmi'
+ icon_state = "testicle"
+ name = "testicle" //the preview name of the accessory
+ color_src = "balls_color"
+ locked = 0
+/datum/sprite_accessory/testicles/hidden
+ icon_state = "hidden"
+ name = "Hidden"
+ alt_aroused = TRUE
+/datum/sprite_accessory/testicles/single
+ icon_state = "single"
+ name = "Single"
+ alt_aroused = TRUE
//Vaginas
/datum/sprite_accessory/vagina
@@ -109,6 +125,15 @@
name = "Pair"
alt_aroused = TRUE
+/datum/sprite_accessory/breasts/quad
+ icon_state = "quad"
+ name = "Quad"
+ alt_aroused = TRUE
+
+/datum/sprite_accessory/breasts/sextuple
+ icon_state = "sextuple"
+ name = "Sextuple"
+ alt_aroused = TRUE
//OVIPOSITORS BE HERE
/datum/sprite_accessory/ovipositor
diff --git a/modular_citadel/code/modules/arousal/organs/penis.dm b/modular_citadel/code/modules/arousal/organs/penis.dm
index ac812e286d..b6cb8fa4b2 100644
--- a/modular_citadel/code/modules/arousal/organs/penis.dm
+++ b/modular_citadel/code/modules/arousal/organs/penis.dm
@@ -5,14 +5,13 @@
icon = 'modular_citadel/icons/obj/genitals/penis.dmi'
zone = "groin"
slot = ORGAN_SLOT_PENIS
- w_class = 3
can_masturbate_with = TRUE
masturbation_verb = "stroke"
can_climax = TRUE
- fluid_transfer_factor = 0.5
+ fluid_transfer_factor = 0.5
size = 2 //arbitrary value derived from length and girth for sprites.
var/length = 6 //inches
- var/cached_length //used to detect a change in length
+ var/cached_length //used to detect a change in length
var/girth = 0
var/girth_ratio = COCK_GIRTH_RATIO_DEF //0.73; check citadel_defines.dm
var/knot_girth_ratio = KNOT_GIRTH_RATIO_DEF
@@ -52,15 +51,15 @@
string = "penis_[GLOB.cock_shapes_icons[shape]]_[size]"
if(ishuman(owner))
var/mob/living/carbon/human/H = owner
+ icon_state = sanitize_text(string)
H.update_genitals()
- icon_state = sanitize_text(string)
-
/obj/item/organ/genital/penis/update_link()
if(owner)
linked_organ = (owner.getorganslot("testicles"))
if(linked_organ)
linked_organ.linked_organ = src
+ linked_organ.size = size
else
if(linked_organ)
linked_organ.linked_organ = null
diff --git a/modular_citadel/code/modules/arousal/organs/testicles.dm b/modular_citadel/code/modules/arousal/organs/testicles.dm
index 815d8034e7..0cf698392c 100644
--- a/modular_citadel/code/modules/arousal/organs/testicles.dm
+++ b/modular_citadel/code/modules/arousal/organs/testicles.dm
@@ -1,17 +1,20 @@
/obj/item/organ/genital/testicles
- name = "testicles"
- desc = "A male reproductive organ."
- icon_state = "testicles"
- icon = 'modular_citadel/icons/obj/genitals/penis.dmi'
- zone = "groin"
- slot = "testicles"
- w_class = 3
- internal = TRUE
- size = BALLS_SIZE_DEF
- var/sack_size = BALLS_SACK_SIZE_DEF
- fluid_id = "semen"
- producing = TRUE
- var/sent_full_message = 1 //defaults to 1 since they're full to start
+ name = "testicles"
+ desc = "A male reproductive organ."
+ icon_state = "testicles"
+ icon = 'modular_citadel/icons/obj/genitals/testicles.dmi'
+ zone = "groin"
+ slot = "testicles"
+ size = BALLS_SIZE_MIN
+ var/size_name = "average"
+ shape = "single"
+ var/sack_size = BALLS_SACK_SIZE_DEF
+ fluid_id = "semen"
+ producing = TRUE
+ can_masturbate_with = TRUE
+ masturbation_verb = "massage"
+ can_climax = TRUE
+ var/sent_full_message = TRUE //defaults to 1 since they're full to start
/obj/item/organ/genital/testicles/Initialize()
. = ..()
@@ -28,9 +31,9 @@
if(reagents.total_volume >= reagents.maximum_volume)
if(!sent_full_message)
send_full_message()
- sent_full_message = 1
+ sent_full_message = TRUE
return FALSE
- sent_full_message = 0
+ sent_full_message = FALSE
update_link()
if(!linked_organ)
return FALSE
@@ -42,6 +45,7 @@
linked_organ = (owner.getorganslot("penis"))
if(linked_organ)
linked_organ.linked_organ = src
+
else
if(linked_organ)
linked_organ.linked_organ = null
@@ -49,6 +53,34 @@
/obj/item/organ/genital/testicles/proc/send_full_message(msg = "Your balls finally feel full, again.")
if(owner && istext(msg))
- owner << msg
+ to_chat(owner, msg)
return TRUE
+/obj/item/organ/genital/testicles/update_appearance()
+ if(owner)
+ if(size == 0)
+ size_name = "nonexistant"
+ if(size == 1)
+ size_name = "average"
+ if(size == 2)
+ size_name = "enlarged"
+ if(size >= 3)
+ size_name = "engorged"
+
+ if(!internal)
+ desc = "You see an [size_name] pair of testicles dangling."
+ else
+ desc = "They don't have any testicles you can see."
+ var/string
+ if(owner.dna.species.use_skintones && owner.dna.features["genitals_use_skintone"])
+ if(ishuman(owner)) // Check before recasting type, although someone fucked up if you're not human AND have use_skintones somehow...
+ var/mob/living/carbon/human/H = owner // only human mobs have skin_tone, which we need.
+ color = "#[skintone2hex(H.skin_tone)]"
+ string = "testicles_[GLOB.balls_shapes_icons[shape]]_[size]-s"
+ else
+ color = "#[owner.dna.features["balls_color"]]"
+ string = "testicles_[GLOB.balls_shapes_icons[shape]]_[size]"
+ if(ishuman(owner))
+ var/mob/living/carbon/human/H = owner
+ icon_state = sanitize_text(string)
+ H.update_genitals()
diff --git a/modular_citadel/code/modules/arousal/organs/vagina.dm b/modular_citadel/code/modules/arousal/organs/vagina.dm
index b8ef7029b6..8c15aa5437 100644
--- a/modular_citadel/code/modules/arousal/organs/vagina.dm
+++ b/modular_citadel/code/modules/arousal/organs/vagina.dm
@@ -60,10 +60,9 @@
string = "vagina"
if(ishuman(owner))
var/mob/living/carbon/human/H = owner
+ icon_state = sanitize_text(string)
H.update_genitals()
- icon_state = sanitize_text(string)
-
/obj/item/organ/genital/vagina/update_link()
if(owner)
linked_organ = (owner.getorganslot("womb"))
diff --git a/modular_citadel/code/modules/arousal/organs/womb.dm b/modular_citadel/code/modules/arousal/organs/womb.dm
index c59d74e629..686d9059a0 100644
--- a/modular_citadel/code/modules/arousal/organs/womb.dm
+++ b/modular_citadel/code/modules/arousal/organs/womb.dm
@@ -5,12 +5,10 @@
icon_state = "womb"
zone = "groin"
slot = "womb"
- w_class = 3
internal = TRUE
fluid_id = "femcum"
producing = TRUE
-
/obj/item/organ/genital/womb/Initialize()
. = ..()
reagents.add_reagent(fluid_id, fluid_max_volume)
diff --git a/modular_citadel/code/modules/client/loadout/__donator.dm b/modular_citadel/code/modules/client/loadout/__donator.dm
index c0a4df2243..70f58b3159 100644
--- a/modular_citadel/code/modules/client/loadout/__donator.dm
+++ b/modular_citadel/code/modules/client/loadout/__donator.dm
@@ -373,4 +373,16 @@ datum/gear/darksabresheath
name = "French Beret"
category = SLOT_HEAD
path = /obj/item/clothing/head/frenchberet
- ckeywhitelist = list("notazoltan")
\ No newline at end of file
+ ckeywhitelist = list("notazoltan")
+
+/datum/gear/zuliecloak
+ name = "Project: Zul-E"
+ category = SLOT_WEAR_SUIT
+ path = /obj/item/clothing/suit/hooded/cloak/zuliecloak
+ ckeywhitelist = list("asky")
+
+/datum/gear/blackredgold
+ name = "Black, Red, and Gold Coat"
+ category = SLOT_WEAR_SUIT
+ path = /obj/item/clothing/suit/blackredgold
+ ckeywhitelist = list("ttbnc")
\ No newline at end of file
diff --git a/modular_citadel/code/modules/client/preferences_savefile.dm b/modular_citadel/code/modules/client/preferences_savefile.dm
index 209d46db61..2921f70684 100644
--- a/modular_citadel/code/modules/client/preferences_savefile.dm
+++ b/modular_citadel/code/modules/client/preferences_savefile.dm
@@ -56,6 +56,7 @@
WRITE_FILE(S["feature_has_balls"], features["has_balls"])
WRITE_FILE(S["feature_balls_color"], features["balls_color"])
WRITE_FILE(S["feature_balls_size"], features["balls_size"])
+ WRITE_FILE(S["feature_balls_shape"], features["balls_shape"])
WRITE_FILE(S["feature_balls_sack_size"], features["balls_sack_size"])
WRITE_FILE(S["feature_balls_fluid"], features["balls_fluid"])
//breasts features
diff --git a/modular_citadel/code/modules/custom_loadout/custom_items.dm b/modular_citadel/code/modules/custom_loadout/custom_items.dm
index b7f7aea0e8..e1b1110cf8 100644
--- a/modular_citadel/code/modules/custom_loadout/custom_items.dm
+++ b/modular_citadel/code/modules/custom_loadout/custom_items.dm
@@ -454,3 +454,34 @@
icon_state = "moveralls"
mutantrace_variation = NO_MUTANTRACE_VARIATION
+/obj/item/clothing/suit/hooded/cloak/zuliecloak
+ name = "Project: Zul-E"
+ desc = "A standard version of a prototype cloak given out by Nanotrasen higher ups. It's surprisingly thick and heavy for a cloak despite having most of it's tech stripped. It also comes with a bluespace trinket which calls it's accompanying hat onto the user. A worn inscription on the inside of the cloak reads 'Fleuret' ...the rest is faded away."
+ icon_state = "zuliecloak"
+ item_state = "zuliecloak"
+ icon = 'icons/obj/custom.dmi'
+ alternate_worn_icon = 'icons/mob/custom_w.dmi'
+ hoodtype = /obj/item/clothing/head/hooded/cloakhood/zuliecloak
+ body_parts_covered = CHEST|GROIN|ARMS
+ slot_flags = SLOT_WEAR_SUIT | ITEM_SLOT_NECK //it's a cloak. it's cosmetic. so why the hell not? what could possibly go wrong?
+ mutantrace_variation = NO_MUTANTRACE_VARIATION
+
+/obj/item/clothing/head/hooded/cloakhood/zuliecloak
+ name = "NT Special Issue"
+ desc = "This hat is unquestionably the best one, bluespaced to and from CentComm. It smells of Fish and Tea with a hint of antagonism"
+ icon_state = "zuliecap"
+ item_state = "zuliecap"
+ icon = 'icons/obj/custom.dmi'
+ alternate_worn_icon = 'icons/mob/custom_w.dmi'
+ flags_inv = HIDEEARS|HIDEHAIR
+ mutantrace_variation = NO_MUTANTRACE_VARIATION
+
+/obj/item/clothing/suit/blackredgold
+ name = "Multicolor Coat"
+ desc = "An oddly special looking coat with black, red, and gold"
+ icon = 'icons/obj/custom.dmi'
+ alternate_worn_icon = 'icons/mob/custom_w.dmi'
+ icon_state = "redgoldjacket"
+ item_state = "redgoldjacket"
+ body_parts_covered = CHEST|GROIN|LEGS|ARMS
+ mutantrace_variation = NO_MUTANTRACE_VARIATION
\ No newline at end of file
diff --git a/modular_citadel/code/modules/mob/dead/new_player/sprite_accessories.dm b/modular_citadel/code/modules/mob/dead/new_player/sprite_accessories.dm
index a4532d0820..e552c2cbdd 100644
--- a/modular_citadel/code/modules/mob/dead/new_player/sprite_accessories.dm
+++ b/modular_citadel/code/modules/mob/dead/new_player/sprite_accessories.dm
@@ -959,6 +959,13 @@
name = "Eevee"
icon_state = "eevee"
+
+/datum/sprite_accessory/mam_ears/elf
+ name = "Elf"
+ icon_state = "elf"
+ color_src = MUTCOLORS3
+
+
/datum/sprite_accessory/mam_ears/elephant
name = "Elephant"
icon_state = "elephant"
@@ -1497,6 +1504,12 @@ datum/sprite_accessory/mam_tails/insect
icon_state = "cow"
taur_mode = HOOF_TAURIC
+/datum/sprite_accessory/taur/deer
+ name = "Deer"
+ icon_state = "deer"
+ taur_mode = HOOF_TAURIC
+ color_src = MUTCOLORS
+
/datum/sprite_accessory/taur/drake
name = "Drake"
icon_state = "drake"
diff --git a/modular_citadel/icons/mob/mam_markings.dmi b/modular_citadel/icons/mob/mam_markings.dmi
index 3575f38e1c..8327be6eaf 100644
Binary files a/modular_citadel/icons/mob/mam_markings.dmi and b/modular_citadel/icons/mob/mam_markings.dmi differ
diff --git a/modular_citadel/icons/mob/mam_taur.dmi b/modular_citadel/icons/mob/mam_taur.dmi
index 3d0d4a0e84..84367ebf0a 100644
Binary files a/modular_citadel/icons/mob/mam_taur.dmi and b/modular_citadel/icons/mob/mam_taur.dmi differ
diff --git a/modular_citadel/icons/mob/muzzled_helmet.dmi b/modular_citadel/icons/mob/muzzled_helmet.dmi
index e1284679b3..a96f18c3ce 100644
Binary files a/modular_citadel/icons/mob/muzzled_helmet.dmi and b/modular_citadel/icons/mob/muzzled_helmet.dmi differ
diff --git a/modular_citadel/icons/mob/suit_digi.dmi b/modular_citadel/icons/mob/suit_digi.dmi
index c19fb01161..1f1f506d9f 100644
Binary files a/modular_citadel/icons/mob/suit_digi.dmi and b/modular_citadel/icons/mob/suit_digi.dmi differ
diff --git a/modular_citadel/icons/mob/taur_naga.dmi b/modular_citadel/icons/mob/taur_naga.dmi
index f4d6a6139f..1ca4509db7 100644
Binary files a/modular_citadel/icons/mob/taur_naga.dmi and b/modular_citadel/icons/mob/taur_naga.dmi differ
diff --git a/modular_citadel/icons/obj/genitals/breasts.dmi b/modular_citadel/icons/obj/genitals/breasts.dmi
index e864d09a70..8c76891396 100644
Binary files a/modular_citadel/icons/obj/genitals/breasts.dmi and b/modular_citadel/icons/obj/genitals/breasts.dmi differ
diff --git a/modular_citadel/icons/obj/genitals/breasts_onmob.dmi b/modular_citadel/icons/obj/genitals/breasts_onmob.dmi
index b0dc1d7aef..69a531bd11 100644
Binary files a/modular_citadel/icons/obj/genitals/breasts_onmob.dmi and b/modular_citadel/icons/obj/genitals/breasts_onmob.dmi differ
diff --git a/modular_citadel/icons/obj/genitals/penis.dmi b/modular_citadel/icons/obj/genitals/penis.dmi
index 5ea37a722d..397fa335e5 100644
Binary files a/modular_citadel/icons/obj/genitals/penis.dmi and b/modular_citadel/icons/obj/genitals/penis.dmi differ
diff --git a/modular_citadel/icons/obj/genitals/testicles.dmi b/modular_citadel/icons/obj/genitals/testicles.dmi
new file mode 100644
index 0000000000..3d7a5f4f48
Binary files /dev/null and b/modular_citadel/icons/obj/genitals/testicles.dmi differ
diff --git a/modular_citadel/icons/obj/genitals/testicles_onmob.dmi b/modular_citadel/icons/obj/genitals/testicles_onmob.dmi
new file mode 100644
index 0000000000..581bcb0583
Binary files /dev/null and b/modular_citadel/icons/obj/genitals/testicles_onmob.dmi differ
diff --git a/modular_citadel/icons/obj/genitals/vagina.dmi b/modular_citadel/icons/obj/genitals/vagina.dmi
index 00891888a4..39bdd48e89 100644
Binary files a/modular_citadel/icons/obj/genitals/vagina.dmi and b/modular_citadel/icons/obj/genitals/vagina.dmi differ
diff --git a/modular_citadel/icons/obj/genitals/vagina_onmob.dmi b/modular_citadel/icons/obj/genitals/vagina_onmob.dmi
index c22c12ded3..f5daa3fea1 100644
Binary files a/modular_citadel/icons/obj/genitals/vagina_onmob.dmi and b/modular_citadel/icons/obj/genitals/vagina_onmob.dmi differ
diff --git a/tgstation.dme b/tgstation.dme
index dd6db2a5e4..739176a457 100755
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -380,6 +380,7 @@
#include "code\datums\components\storage\concrete\_concrete.dm"
#include "code\datums\components\storage\concrete\bag_of_holding.dm"
#include "code\datums\components\storage\concrete\bluespace.dm"
+#include "code\datums\components\storage\concrete\emergency.dm"
#include "code\datums\components\storage\concrete\implant.dm"
#include "code\datums\components\storage\concrete\pockets.dm"
#include "code\datums\components\storage\concrete\rped.dm"
@@ -1495,6 +1496,7 @@
#include "code\modules\clothing\shoes\colour.dm"
#include "code\modules\clothing\shoes\magboots.dm"
#include "code\modules\clothing\shoes\miscellaneous.dm"
+#include "code\modules\clothing\shoes\taeclowndo.dm"
#include "code\modules\clothing\shoes\vg_shoes.dm"
#include "code\modules\clothing\spacesuits\_spacesuits.dm"
#include "code\modules\clothing\spacesuits\chronosuit.dm"
@@ -2662,6 +2664,7 @@
#include "code\modules\spells\spell_types\shapeshift.dm"
#include "code\modules\spells\spell_types\spacetime_distortion.dm"
#include "code\modules\spells\spell_types\summonitem.dm"
+#include "code\modules\spells\spell_types\taeclowndo.dm"
#include "code\modules\spells\spell_types\the_traps.dm"
#include "code\modules\spells\spell_types\touch_attacks.dm"
#include "code\modules\spells\spell_types\trigger.dm"
@@ -2828,6 +2831,17 @@
#include "modular_citadel\code\datums\wires\airlock.dm"
#include "modular_citadel\code\datums\wires\autoylathe.dm"
#include "modular_citadel\code\game\area\cit_areas.dm"
+#include "modular_citadel\code\game\gamemodes\gangs\dominator.dm"
+#include "modular_citadel\code\game\gamemodes\gangs\dominator_countdown.dm"
+#include "modular_citadel\code\game\gamemodes\gangs\gang.dm"
+#include "modular_citadel\code\game\gamemodes\gangs\gang_datums.dm"
+#include "modular_citadel\code\game\gamemodes\gangs\gang_decals.dm"
+#include "modular_citadel\code\game\gamemodes\gangs\gang_hud.dm"
+#include "modular_citadel\code\game\gamemodes\gangs\gang_items.dm"
+#include "modular_citadel\code\game\gamemodes\gangs\gang_pen.dm"
+#include "modular_citadel\code\game\gamemodes\gangs\gangs.dm"
+#include "modular_citadel\code\game\gamemodes\gangs\gangtool.dm"
+#include "modular_citadel\code\game\gamemodes\gangs\implant_gang.dm"
#include "modular_citadel\code\game\gamemodes\miniantags\bot_swarm\swarmer_event.dm"
#include "modular_citadel\code\game\gamemodes\revolution\revolution.dm"
#include "modular_citadel\code\game\machinery\cryopod.dm"