diff --git a/code/__DEFINES/atmospherics.dm b/code/__DEFINES/atmospherics.dm
index 41b5deb302..74a6796bd2 100644
--- a/code/__DEFINES/atmospherics.dm
+++ b/code/__DEFINES/atmospherics.dm
@@ -9,12 +9,13 @@
#define META_GAS_FUSION_POWER 7
//ATMOS
//stuff you should probably leave well alone!
-#define R_IDEAL_GAS_EQUATION 8.31 //kPa*L/(K*mol)
-#define ONE_ATMOSPHERE 101.325 //kPa
-#define TCMB 2.7 // -270.3degC
-#define TCRYO 225 // -48.15degC
-#define T0C 273.15 // 0degC
-#define T20C 293.15 // 20degC
+#define R_IDEAL_GAS_EQUATION 8.31446261815324 //kPa*L/(K*mol)
+#define ONE_ATMOSPHERE 101.325 //kPa
+#define TCMB 2.7 // -270.3degC
+#define TCRYO 225 // -48.15degC
+#define T0C 273.15 // 0degC
+#define T20C 293.15 // 20degC
+#define STEFANBOLTZMANN (5.670373*10e-8) // W/(m^2*K^4)
#define MOLES_CELLSTANDARD (ONE_ATMOSPHERE*CELL_VOLUME/(T20C*R_IDEAL_GAS_EQUATION)) //moles in a 2.5 m^3 cell at 101.325 Pa and 20 degC
#define M_CELL_WITH_RATIO (MOLES_CELLSTANDARD * 0.005) //compared against for superconductivity
@@ -149,9 +150,9 @@
//OPEN TURF ATMOS
#define OPENTURF_DEFAULT_ATMOS "o2=22;n2=82;TEMP=293.15" //the default air mix that open turfs spawn
-#define TCOMMS_ATMOS "n2=100;TEMP=80" //-193,15°C telecommunications. also used for xenobiology slime killrooms
+#define TCOMMS_ATMOS "n2=100;TEMP=80" //-193,15°C telecommunications. also used for xenobiology slime killrooms
#define AIRLESS_ATMOS "TEMP=2.7" //space
-#define FROZEN_ATMOS "o2=22;n2=82;TEMP=180" //-93.15°C snow and ice turfs
+#define FROZEN_ATMOS "o2=22;n2=82;TEMP=180" //-93.15°C snow and ice turfs
#define BURNMIX_ATMOS "o2=2500;plasma=5000;TEMP=370" //used in the holodeck burn test program
//ATMOSPHERICS DEPARTMENT GAS TANK TURFS
diff --git a/code/__DEFINES/is_helpers.dm b/code/__DEFINES/is_helpers.dm
index 6f433374e7..09ef814f68 100644
--- a/code/__DEFINES/is_helpers.dm
+++ b/code/__DEFINES/is_helpers.dm
@@ -118,6 +118,8 @@ GLOBAL_LIST_INIT(turfs_without_ground, typecacheof(list(
#define ismouse(A) (istype(A, /mob/living/simple_animal/mouse))
+#define iscow(A) (istype(A, /mob/living/simple_animal/cow))
+
#define isslime(A) (istype(A, /mob/living/simple_animal/slime))
#define isdrone(A) (istype(A, /mob/living/simple_animal/drone))
diff --git a/code/__DEFINES/movespeed_modification.dm b/code/__DEFINES/movespeed_modification.dm
index 50e1a10fa1..1883df6e8e 100644
--- a/code/__DEFINES/movespeed_modification.dm
+++ b/code/__DEFINES/movespeed_modification.dm
@@ -57,6 +57,7 @@
#define MOVESPEED_ID_PRONE_DRAGGING "PRONE_DRAG"
#define MOVESPEED_ID_HUMAN_CARRYING "HUMAN_CARRY"
+#define MOVESPEED_ID_SHRINK_RAY "SHRUNKEN_SPEED_MODIFIER"
#define MOVESPEED_ID_TASED_STATUS "TASED"
diff --git a/code/__HELPERS/roundend.dm b/code/__HELPERS/roundend.dm
index b899d7f6ab..27f1a81fef 100644
--- a/code/__HELPERS/roundend.dm
+++ b/code/__HELPERS/roundend.dm
@@ -320,6 +320,8 @@
parts += "[FOURSPACES][FOURSPACES][str]"
for(var/entry in mode.threat_tallies)
parts += "[FOURSPACES][FOURSPACES][entry] added [mode.threat_tallies[entry]]"
+ SSblackbox.record_feedback("tally","dynamic_threat",mode.threat_level,"Final threat level")
+ SSblackbox.record_feedback("tally","dynamic_threat",mode.threat,"Threat left")
return parts.Join("
")
/client/proc/roundend_report_file()
diff --git a/code/_onclick/hud/alert.dm b/code/_onclick/hud/alert.dm
index 391e86f390..c42956bbaa 100644
--- a/code/_onclick/hud/alert.dm
+++ b/code/_onclick/hud/alert.dm
@@ -213,6 +213,16 @@ or something covering your eyes."
desc = "Whoa man, you're tripping balls! Careful you don't get addicted... if you aren't already."
icon_state = "high"
+/obj/screen/alert/mind_control
+ name = "Mind Control"
+ desc = "Your mind has been hijacked! Click to view the mind control command."
+ icon_state = "mind_control"
+ var/command
+
+/obj/screen/alert/mind_control/Click()
+ var/mob/living/L = usr
+ to_chat(L, "[command]")
+
/obj/screen/alert/hypnosis
name = "Hypnosis"
desc = "Something's hypnotizing you, but you're not really sure about what."
diff --git a/code/controllers/subsystem/persistence.dm b/code/controllers/subsystem/persistence.dm
index c3db8552c6..8329493818 100644
--- a/code/controllers/subsystem/persistence.dm
+++ b/code/controllers/subsystem/persistence.dm
@@ -12,6 +12,7 @@ SUBSYSTEM_DEF(persistence)
var/list/obj/structure/chisel_message/chisel_messages = list()
var/list/saved_messages = list()
var/list/saved_modes = list(1,2,3)
+ var/list/saved_dynamic_rules = list(list(),list(),list())
var/list/saved_threat_levels = list(1,1,1)
var/list/saved_maps
var/list/saved_trophies = list()
@@ -227,7 +228,10 @@ SUBSYSTEM_DEF(persistence)
CollectSecretSatchels()
CollectTrophies()
CollectRoundtype()
- CollectThreatLevel()
+ if(istype(SSticker.mode, /datum/game_mode/dynamic))
+ var/datum/game_mode/dynamic/mode = SSticker.mode
+ CollectThreatLevel(mode)
+ CollectRulesets(mode)
RecordMaps()
SavePhotoPersistence() //THIS IS PERSISTENCE, NOT THE LOGGING PORTION.
if(CONFIG_GET(flag/use_antag_rep))
@@ -384,17 +388,27 @@ SUBSYSTEM_DEF(persistence)
fdel(json_file)
WRITE_FILE(json_file, json_encode(file_data))
-/datum/controller/subsystem/persistence/proc/CollectThreatLevel()
- if(istype(SSticker.mode, /datum/game_mode/dynamic))
- var/datum/game_mode/dynamic/mode = SSticker.mode
- saved_threat_levels[3] = saved_threat_levels[2]
- saved_threat_levels[2] = saved_threat_levels [1]
- saved_threat_levels[1] = mode.threat_level
- var/json_file = file("data/RecentThreatLevels.json")
- var/list/file_data = list()
- file_data["data"] = saved_threat_levels
- fdel(json_file)
- WRITE_FILE(json_file, json_encode(file_data))
+/datum/controller/subsystem/persistence/proc/CollectThreatLevel(var/datum/game_mode/dynamic/mode)
+ saved_threat_levels[3] = saved_threat_levels[2]
+ saved_threat_levels[2] = saved_threat_levels [1]
+ saved_threat_levels[1] = mode.threat_level
+ var/json_file = file("data/RecentThreatLevels.json")
+ var/list/file_data = list()
+ file_data["data"] = saved_threat_levels
+ fdel(json_file)
+ WRITE_FILE(json_file, json_encode(file_data))
+
+/datum/controller/subsystem/persistence/proc/CollectRulesets(var/datum/game_mode/dynamic/mode)
+ saved_dynamic_rules[3] = saved_dynamic_rules[2]
+ saved_dynamic_rules[2] = saved_dynamic_rules[1]
+ saved_dynamic_rules[1] = list()
+ for(var/datum/dynamic_ruleset/ruleset in mode.executed_rules)
+ saved_dynamic_rules[1] += ruleset.config_tag
+ var/json_file = file("data/RecentRulesets.json")
+ var/list/file_data = list()
+ file_data["data"] = saved_dynamic_rules
+ fdel(json_file)
+ WRITE_FILE(json_file, json_encode(file_data))
/datum/controller/subsystem/persistence/proc/RecordMaps()
saved_maps = saved_maps?.len ? list("[SSmapping.config.map_name]") | saved_maps : list("[SSmapping.config.map_name]")
diff --git a/code/controllers/subsystem/traumas.dm b/code/controllers/subsystem/traumas.dm
index ef63338a67..1ad4a15ee7 100644
--- a/code/controllers/subsystem/traumas.dm
+++ b/code/controllers/subsystem/traumas.dm
@@ -93,7 +93,7 @@ SUBSYSTEM_DEF(traumas)
/obj/item/clothing/under/rank/head_of_security/grey, /obj/item/clothing/under/rank/head_of_security/alt,
/obj/item/clothing/under/rank/research_director/alt, /obj/item/clothing/under/rank/research_director/turtleneck,
/obj/item/clothing/under/captainparade, /obj/item/clothing/under/hosparademale, /obj/item/clothing/under/hosparadefem,
- /obj/item/clothing/head/helmet/abductor, /obj/item/clothing/suit/armor/abductor/vest, /obj/item/abductor_baton,
+ /obj/item/clothing/head/helmet/abductor, /obj/item/clothing/suit/armor/abductor/vest, /obj/item/abductor/baton,
/obj/item/storage/belt/military/abductor, /obj/item/gun/energy/alien, /obj/item/abductor/silencer,
/obj/item/abductor/gizmo, /obj/item/clothing/under/rank/centcom_officer,
/obj/item/clothing/suit/space/hardsuit/ert, /obj/item/clothing/suit/space/hardsuit/ert/sec,
@@ -136,7 +136,7 @@ SUBSYSTEM_DEF(traumas)
"aliens" = typecacheof(list(/obj/item/clothing/mask/facehugger, /obj/item/organ/body_egg/alien_embryo,
/obj/structure/alien, /obj/item/toy/toy_xeno,
/obj/item/clothing/suit/armor/abductor, /obj/item/abductor, /obj/item/gun/energy/alien,
- /obj/item/abductor_baton, /obj/item/radio/headset/abductor, /obj/item/scalpel/alien, /obj/item/hemostat/alien,
+ /obj/item/abductor/baton, /obj/item/radio/headset/abductor, /obj/item/scalpel/alien, /obj/item/hemostat/alien,
/obj/item/retractor/alien, /obj/item/circular_saw/alien, /obj/item/surgicaldrill/alien, /obj/item/cautery/alien,
/obj/item/clothing/head/helmet/abductor, /obj/structure/bed/abductor, /obj/structure/table_frame/abductor,
/obj/structure/table/abductor, /obj/structure/table/optable/abductor, /obj/structure/closet/abductor, /obj/item/organ/heart/gland,
diff --git a/code/datums/components/shrink.dm b/code/datums/components/shrink.dm
new file mode 100644
index 0000000000..f070d9b22f
--- /dev/null
+++ b/code/datums/components/shrink.dm
@@ -0,0 +1,42 @@
+/datum/component/shrink
+ var/olddens
+ var/oldopac
+ dupe_mode = COMPONENT_DUPE_HIGHLANDER
+
+/datum/component/shrink/Initialize(shrink_time)
+ if(!isatom(parent))
+ return COMPONENT_INCOMPATIBLE
+ var/atom/parent_atom = parent
+ parent_atom.transform = parent_atom.transform.Scale(0.5,0.5)
+ olddens = parent_atom.density
+ oldopac = parent_atom.opacity
+ parent_atom.density = 0
+ parent_atom.opacity = 0
+ if(isliving(parent_atom))
+ var/mob/living/L = parent_atom
+ L.add_movespeed_modifier(MOVESPEED_ID_SHRINK_RAY, update=TRUE, priority=100, multiplicative_slowdown=4)
+ if(iscarbon(L))
+ var/mob/living/carbon/C = L
+ C.unequip_everything()
+ C.visible_message("[C]'s belongings fall off of [C.p_them()] as they shrink down!",
+ "Your belongings fall away as everything grows bigger!")
+ if(ishuman(C))
+ var/mob/living/carbon/human/H = C
+ H.physiology.damage_resistance -= 100//carbons take double damage while shrunk
+ parent_atom.visible_message("[parent_atom] shrinks down to a tiny size!",
+ "Everything grows bigger!")
+ QDEL_IN(src, shrink_time)
+
+
+/datum/component/shrink/Destroy()
+ var/atom/parent_atom = parent
+ parent_atom.transform = parent_atom.transform.Scale(2,2)
+ parent_atom.density = olddens
+ parent_atom.opacity = oldopac
+ if(isliving(parent_atom))
+ var/mob/living/L = parent_atom
+ L.remove_movespeed_modifier(MOVESPEED_ID_SHRINK_RAY)
+ if(ishuman(L))
+ var/mob/living/carbon/human/H = L
+ H.physiology.damage_resistance += 100
+ ..()
\ No newline at end of file
diff --git a/code/datums/mutations/speech.dm b/code/datums/mutations/speech.dm
index f02b7f185f..82138e6e34 100644
--- a/code/datums/mutations/speech.dm
+++ b/code/datums/mutations/speech.dm
@@ -95,6 +95,7 @@
message = replacetext(message," ugly "," beautiful ")
message = replacetext(message," douchbag "," nice guy ")
message = replacetext(message," whore "," lady ")
+ message = replacetext(message," gamer "," intellectual ")
message = replacetext(message," nerd "," smarty pants ")
message = replacetext(message," moron "," fun person ")
message = replacetext(message," IT'S LOOSE "," EVERYTHING IS FINE ")
diff --git a/code/datums/weather/weather_types/radiation_storm.dm b/code/datums/weather/weather_types/radiation_storm.dm
index 6765cdf2cd..0d763aa5ad 100644
--- a/code/datums/weather/weather_types/radiation_storm.dm
+++ b/code/datums/weather/weather_types/radiation_storm.dm
@@ -22,27 +22,17 @@
target_trait = ZTRAIT_STATION
immunity_type = "rad"
+
+ var/radiation_intensity = 100
/datum/weather/rad_storm/telegraph()
..()
status_alarm(TRUE)
-
/datum/weather/rad_storm/weather_act(mob/living/L)
var/resist = L.getarmor(null, "rad")
- if(prob(40))
- if(ishuman(L))
- var/mob/living/carbon/human/H = L
- if(H.dna && !HAS_TRAIT(H, TRAIT_RADIMMUNE))
- if(prob(max(0,100-resist)))
- H.randmuti()
- if(prob(50))
- if(prob(90))
- H.randmutb()
- else
- H.randmutg()
- H.domutcheck()
- L.rad_act(20)
+ var/ratio = 1 - (min(resist, 100) / 100)
+ L.rad_act(radiation_intensity * ratio)
/datum/weather/rad_storm/end()
if(..())
diff --git a/code/game/gamemodes/dynamic/dynamic.dm b/code/game/gamemodes/dynamic/dynamic.dm
index 67ea855466..075184046d 100644
--- a/code/game/gamemodes/dynamic/dynamic.dm
+++ b/code/game/gamemodes/dynamic/dynamic.dm
@@ -300,6 +300,7 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
/datum/game_mode/dynamic/proc/log_threat(var/log_str,var/verbose = FALSE)
threat_log_verbose += ("[worldtime2text()]: "+log_str)
+ SSblackbox.record_feedback("tally","dynamic_threat_log",1,log_str)
if(!verbose)
threat_log += log_str
@@ -329,6 +330,10 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
peaceful_percentage = round(LORENTZ_CUMULATIVE_DISTRIBUTION(relative_threat, GLOB.dynamic_curve_centre, GLOB.dynamic_curve_width), 0.01)*100
threat = threat_level
+ SSblackbox.record_feedback("tally","dynamic_threat",threat_level,"Initial threat level")
+ SSblackbox.record_feedback("tally","dynamic_threat",GLOB.dynamic_curve_centre,"Curve centre")
+ SSblackbox.record_feedback("tally","dynamic_threat",GLOB.dynamic_curve_width,"Curve width")
+ SSblackbox.record_feedback("tally","dynamic_threat",peaceful_percentage,"Percent of same-vote rounds that are more peaceful")
/datum/game_mode/dynamic/can_start()
message_admins("Dynamic mode parameters for the round:")
@@ -340,6 +345,7 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
if(GLOB.dynamic_forced_threat_level >= 0)
threat_level = round(GLOB.dynamic_forced_threat_level, 0.1)
threat = threat_level
+ SSblackbox.record_feedback("tally","dynamic_threat",threat_level,"Threat level (forced by admins)")
else
generate_threat()
@@ -384,7 +390,8 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
if (roundstart_rules.len <= 0)
log_game("DYNAMIC: [roundstart_rules.len] rules.")
return TRUE
-
+ SSblackbox.record_feedback("tally","dynamic",roundstart_rules.len,"Roundstart rules considered")
+ SSblackbox.record_feedback("tally","dynamic",roundstart_rules.len,"Players readied up")
if(GLOB.dynamic_forced_roundstart_ruleset.len > 0)
rigged_roundstart()
else
@@ -537,6 +544,7 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
if(rule.execute())
if(rule.persistent)
current_rules += rule
+ SSblackbox.record_feedback("associative","dynamic_rulesets",1,rule.get_blackbox_info())
return TRUE
rule.clean_up() // Refund threat, delete teams and so on.
executed_rules -= rule
@@ -615,6 +623,7 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
else if(new_rule.flags & ONLY_RULESET)
only_ruleset_executed = TRUE
log_game("DYNAMIC: Making a call to a specific ruleset...[new_rule.name]!")
+ SSblackbox.record_feedback("associative","dynamic_rulesets",1,new_rule.get_blackbox_info())
executed_rules += new_rule
if (new_rule.persistent)
current_rules += new_rule
@@ -639,6 +648,7 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
message_admins("[key_name(M)] joined the station, and was selected by the [rule.name] ruleset.")
log_game("DYNAMIC: [key_name(M)] joined the station, and was selected by the [rule.name] ruleset.")
executed_rules += rule
+ SSblackbox.record_feedback("associative","dynamic_rulesets",1,rule.get_blackbox_info())
rule.candidates.Cut()
if (rule.persistent)
current_rules += rule
@@ -655,6 +665,8 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
for (var/datum/dynamic_ruleset/rule in current_rules)
if(rule.rule_process() == RULESET_STOP_PROCESSING) // If rule_process() returns 1 (RULESET_STOP_PROCESSING), stop processing.
current_rules -= rule
+ SSblackbox.record_feedback("tally","dynamic",1,"Rulesets finished")
+ SSblackbox.record_feedback("associative","dynamic_rulesets_finished",1,rule.get_blackbox_info())
if (midround_injection_cooldown < world.time)
if (GLOB.dynamic_forced_extended)
@@ -673,6 +685,7 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
update_playercounts()
if (get_injection_chance())
+ SSblackbox.record_feedback("tally","dynamic",1,"Attempted midround injections")
var/cur_threat_frac = threat/threat_level
var/list/drafted_rules = list()
var/antag_num = current_players[CURRENT_LIVING_ANTAGS].len
@@ -688,12 +701,16 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
drafted_rules[rule] = round(rule.get_weight() + (rule.cost * cur_threat_frac))
else
drafted_rules[rule] = rule.get_weight()
+ else if(threat < rule.cost)
+ SSblackbox.record_feedback("tally","dynamic",1,"Times rulesets rejected due to not enough threat to spend")
if (drafted_rules.len > 0)
+ SSblackbox.record_feedback("tally","dynamic",1,"Successful midround injections")
picking_midround_latejoin_rule(drafted_rules)
else
midround_injection_cooldown = (midround_injection_cooldown + world.time)/2
if(event_injection_cooldown < world.time)
+ SSblackbox.record_feedback("tally","dynamic",1,"Attempted event injections")
var/event_injection_cooldown_middle = 0.5*(GLOB.dynamic_event_delay_max + GLOB.dynamic_event_delay_min)
event_injection_cooldown = (round(CLAMP(EXP_DISTRIBUTION(event_injection_cooldown_middle), GLOB.dynamic_event_delay_min, GLOB.dynamic_event_delay_max)) + world.time)
message_admins("DYNAMIC: Doing event injection.")
@@ -704,7 +721,10 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
if(rule.acceptable(current_players[CURRENT_LIVING_PLAYERS].len, threat_level) && threat >= rule.cost)
if(rule.ready())
drafted_rules[rule] = rule.get_weight()
+ else if(threat < rule.cost)
+ SSblackbox.record_feedback("tally","dynamic",1,"Times rulesets rejected due to not enough threat to spend")
if(drafted_rules.len > 0)
+ SSblackbox.record_feedback("tally","dynamic",1,"Successful event injections")
picking_midround_latejoin_rule(drafted_rules)
/// Updates current_players.
@@ -795,6 +815,7 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
forced_latejoin_rule = null
else if (latejoin_injection_cooldown < world.time && prob(get_injection_chance()))
+ SSblackbox.record_feedback("tally","dynamic",1,"Attempted latejoin injections")
var/list/drafted_rules = list()
for (var/datum/dynamic_ruleset/latejoin/rule in latejoin_rules)
if (rule.acceptable(current_players[CURRENT_LIVING_PLAYERS].len, threat_level) && threat >= rule.cost)
@@ -812,12 +833,14 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
drafted_rules[rule] = rule.get_weight()
if (drafted_rules.len > 0 && picking_midround_latejoin_rule(drafted_rules))
+ SSblackbox.record_feedback("tally","dynamic",1,"Successful latejoin injections")
var/latejoin_injection_cooldown_middle = 0.5*(GLOB.dynamic_latejoin_delay_max + GLOB.dynamic_latejoin_delay_min)
latejoin_injection_cooldown = round(CLAMP(EXP_DISTRIBUTION(latejoin_injection_cooldown_middle), GLOB.dynamic_latejoin_delay_min, GLOB.dynamic_latejoin_delay_max)) + world.time
/// Refund threat, but no more than threat_level.
/datum/game_mode/dynamic/proc/refund_threat(regain)
threat = min(threat_level,threat+regain)
+ SSblackbox.record_feedback("tally","dynamic_threat",regain,"Refunded threat")
log_threat("[regain] refunded. Threat is now [threat].", verbose = TRUE)
/// Generate threat and increase the threat_level if it goes beyond, capped at 100
@@ -825,11 +848,13 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
threat = min(100, threat+gain)
if(threat > threat_level)
threat_level = threat
+ SSblackbox.record_feedback("tally","dynamic_threat",gain,"Created threat")
log_threat("[gain] created. Threat is now [threat] and threat level is now [threat_level].", verbose = TRUE)
/// Expend threat, can't fall under 0.
/datum/game_mode/dynamic/proc/spend_threat(cost)
threat = max(threat-cost,0)
+ SSblackbox.record_feedback("tally","dynamic_threat",cost,"Threat spent")
log_threat("[cost] spent. Threat is now [threat].", verbose = TRUE)
/// Turns the value generated by lorentz distribution to threat value between 0 and 100.
diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets.dm b/code/game/gamemodes/dynamic/dynamic_rulesets.dm
index 0c8ec0a2b8..7f6ea088a1 100644
--- a/code/game/gamemodes/dynamic/dynamic_rulesets.dm
+++ b/code/game/gamemodes/dynamic/dynamic_rulesets.dm
@@ -80,6 +80,9 @@
/// Delay for when execute will get called from the time of post_setup (roundstart) or process (midround/latejoin).
/// Make sure your ruleset works with execute being called during the game when using this, and that the clean_up proc reverts it properly in case of faliure.
var/delay = 0
+ /// Whether or not recent-round weight values are taken into account for this ruleset.
+ /// Weight reduction uses the same values as secret's recent-round mode weight reduction.
+ var/always_max_weight = FALSE
/datum/dynamic_ruleset/New()
..()
@@ -91,8 +94,15 @@
var/costs = CONFIG_GET(keyed_list/dynamic_cost)
var/requirementses = CONFIG_GET(keyed_list/dynamic_requirements) // can't damn well use requirements
var/high_population_requirements = CONFIG_GET(keyed_list/dynamic_high_population_requirement)
+ var/list/repeated_mode_adjust = CONFIG_GET(number_list/repeated_mode_adjust)
if(config_tag in weights)
- weight = weights[config_tag]
+ var/weight_mult = 1
+ if(!always_max_weight && SSpersistence.saved_dynamic_rules.len == 3 && repeated_mode_adjust.len == 3)
+ var/saved_dynamic_rules = SSpersistence.saved_dynamic_rules
+ for(var/i in 1 to 3)
+ if(config_tag in saved_dynamic_rules[i])
+ weight_mult -= (repeated_mode_adjust[i]/100)
+ weight = weights[config_tag] * weight_mult
if(config_tag in costs)
cost = costs[config_tag]
if(config_tag in requirementses)
@@ -115,12 +125,15 @@
/// If your rule has extra checks, such as counting security officers, do that in ready() instead
/datum/dynamic_ruleset/proc/acceptable(population = 0, threat_level = 0)
if(minimum_players > population)
+ SSblackbox.record_feedback("tally","dynamic",1,"Times rulesets rejected due to low pop")
return FALSE
if(maximum_players > 0 && population > maximum_players)
+ SSblackbox.record_feedback("tally","dynamic",1,"Times rulesets rejected due to high pop")
return FALSE
if (population >= GLOB.dynamic_high_pop_limit)
indice_pop = 10
if(threat_level < high_population_requirement)
+ SSblackbox.record_feedback("tally","dynamic",1,"Times rulesets rejected due to not enough threat level")
log_game("DYNAMIC: [name] did not reach threat level threshold: [threat_level]/[high_population_requirement]")
return FALSE
else
@@ -132,6 +145,7 @@
log_game("DYNAMIC: requirements and antag_cap lists have different lengths in ruleset [name]. Likely config issue, report this.")
indice_pop = min(requirements.len,round(population/pop_per_requirement)+1)
if(threat_level < requirements[indice_pop])
+ SSblackbox.record_feedback("tally","dynamic",1,"Times rulesets rejected due to not enough threat level")
log_game("DYNAMIC: [name] did not reach threat level threshold: [threat_level]/[requirements[indice_pop]]")
return FALSE
else
@@ -178,6 +192,7 @@
/// IMPORTANT: If ready() returns TRUE, that means pre_execute() or execute() should never fail!
/datum/dynamic_ruleset/proc/ready(forced = 0)
if (required_candidates > candidates.len)
+ SSblackbox.record_feedback("tally","dynamic",1,"Times rulesets rejected due to not enough candidates")
return FALSE
return TRUE
@@ -212,6 +227,24 @@
/datum/dynamic_ruleset/proc/check_finished()
return FALSE
+/// Returns a list to be displayed on statbus.
+/datum/dynamic_ruleset/proc/get_blackbox_info()
+ var/list/ruleset_data = list()
+ ruleset_data["name"] = name
+ ruleset_data["rule_type"] = ruletype
+ ruleset_data["cost"] = total_cost
+ ruleset_data["weight"] = weight
+ ruleset_data["scaled_times"] = scaled_times
+ ruleset_data["antagonist_type"] = antag_datum
+ ruleset_data["population_tier"] = indice_pop
+ ruleset_data["assigned"] = list()
+ for (var/datum/mind/M in assigned)
+ var/assigned_data = list()
+ assigned_data["key"] = M.key
+ assigned_data["name"] = M.name
+ ruleset_data["assigned"] += list(assigned_data)
+ return ruleset_data
+
//////////////////////////////////////////////
// //
// ROUNDSTART RULESETS //
diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_events.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_events.dm
index 4ac8cc91d3..b6cd313bfc 100644
--- a/code/game/gamemodes/dynamic/dynamic_rulesets_events.dm
+++ b/code/game/gamemodes/dynamic/dynamic_rulesets_events.dm
@@ -3,6 +3,17 @@
var/typepath // typepath of the event
var/triggering
+/datum/dynamic_ruleset/event/get_blackbox_info()
+ var/list/ruleset_data = list()
+ ruleset_data["name"] = name
+ ruleset_data["rule_type"] = ruletype
+ ruleset_data["cost"] = total_cost
+ ruleset_data["weight"] = weight
+ ruleset_data["scaled_times"] = scaled_times
+ ruleset_data["event_type"] = typepath
+ ruleset_data["population_tier"] = indice_pop
+ return ruleset_data
+
/datum/dynamic_ruleset/event/execute()
var/datum/round_event/E = new typepath()
E.current_players = get_active_player_count(alive_check = 1, afk_check = 1, human_check = 1)
@@ -26,6 +37,7 @@
var/threat = round(mode.threat_level/10)
if (job_check < required_enemies[threat])
+ SSblackbox.record_feedback("tally","dynamic",1,"Times rulesets rejected due to not enough enemy roles")
return FALSE
return TRUE
@@ -125,6 +137,7 @@
requirements = list(5,5,5,5,5,5,5,5,5,5)
high_population_requirement = 5
repeatable = TRUE
+ always_max_weight = TRUE
//////////////////////////////////////////////
// //
@@ -146,12 +159,15 @@
/datum/dynamic_ruleset/event/meteor_wave/ready()
if(mode.threat_level > 40 && mode.threat >= 25 && prob(20))
+ name = "Meteor Wave: Threatening"
cost = 25
typepath = /datum/round_event/meteor_wave/threatening
else if(mode.threat_level > 50 && mode.threat >= 40 && prob(30))
+ name = "Meteor Wave: Catastrophic"
cost = 40
typepath = /datum/round_event/meteor_wave/catastrophic
else
+ name = "Meteor Wave: Normal"
cost = 15
typepath = /datum/round_event/meteor_wave
return ..()
@@ -280,6 +296,7 @@
requirements = list(5,5,5,5,5,5,5,5,5,5)
high_population_requirement = 5
repeatable = TRUE
+ always_max_weight = TRUE
/datum/dynamic_ruleset/event/space_dust
name = "Minor Space Dust"
@@ -293,6 +310,7 @@
requirements = list(5,5,5,5,5,5,5,5,5,5)
high_population_requirement = 5
repeatable = TRUE
+ always_max_weight = TRUE
/datum/dynamic_ruleset/event/major_dust
name = "Major Space Dust"
@@ -332,6 +350,7 @@
requirements = list(101,101,101,5,5,5,5,5,5,5)
high_population_requirement = 5
repeatable = TRUE
+ always_max_weight = TRUE
/datum/dynamic_ruleset/event/radiation_storm
name = "Radiation Storm"
diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm
index 5810fd0ae0..fe9347d9d5 100644
--- a/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm
+++ b/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm
@@ -36,9 +36,9 @@
continue // Dead players cannot count as opponents
if (M.mind && M.mind.assigned_role && (M.mind.assigned_role in enemy_roles) && (!(M in candidates) || (M.mind.assigned_role in restricted_roles)))
job_check++ // Checking for "enemies" (such as sec officers). To be counters, they must either not be candidates to that rule, or have a job that restricts them from it
-
var/threat = round(mode.threat_level/10)
if (job_check < required_enemies[threat])
+ SSblackbox.record_feedback("tally","dynamic",1,"Times rulesets rejected due to not enough enemy roles")
return FALSE
return ..()
@@ -69,6 +69,7 @@
high_population_requirement = 15
repeatable = TRUE
flags = TRAITOR_RULESET
+ always_max_weight = TRUE
//////////////////////////////////////////////
// //
diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm
index 01c11d8b0b..91ac73d142 100644
--- a/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm
+++ b/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm
@@ -87,6 +87,7 @@
var/threat = round(mode.threat_level/10)
if (job_check < required_enemies[threat])
+ SSblackbox.record_feedback("tally","dynamic",1,"Times rulesets rejected due to not enough enemy roles")
return FALSE
return TRUE
@@ -179,6 +180,7 @@
repeatable = TRUE
high_population_requirement = 15
flags = TRAITOR_RULESET
+ always_max_weight = TRUE
/datum/dynamic_ruleset/midround/autotraitor/acceptable(population = 0, threat = 0)
var/player_count = mode.current_players[CURRENT_LIVING_PLAYERS].len
@@ -291,6 +293,7 @@
/datum/dynamic_ruleset/midround/from_ghosts/wizard/ready(forced = FALSE)
if (required_candidates > (dead_players.len + list_observers.len))
+ SSblackbox.record_feedback("tally","dynamic",1,"Times rulesets rejected due to not enough ghosts")
return FALSE
if(GLOB.wizardstart.len == 0)
log_admin("Cannot accept Wizard ruleset. Couldn't find any wizard spawn points.")
@@ -353,6 +356,7 @@
/datum/dynamic_ruleset/midround/from_ghosts/nuclear/ready(forced = FALSE)
if (required_candidates > (dead_players.len + list_observers.len))
+ SSblackbox.record_feedback("tally","dynamic",1,"Times rulesets rejected due to not enough ghosts")
return FALSE
return ..()
@@ -389,6 +393,7 @@
/datum/dynamic_ruleset/midround/from_ghosts/blob/ready(forced = FALSE)
if (required_candidates > (dead_players.len + list_observers.len))
+ SSblackbox.record_feedback("tally","dynamic",1,"Times rulesets rejected due to not enough ghosts")
return FALSE
return ..()
@@ -420,6 +425,7 @@
/datum/dynamic_ruleset/midround/from_ghosts/xenomorph/ready(forced = FALSE)
if (required_candidates > (dead_players.len + list_observers.len))
+ SSblackbox.record_feedback("tally","dynamic",1,"Times rulesets rejected due to not enough ghosts")
return FALSE
return ..()
@@ -519,6 +525,7 @@
/datum/dynamic_ruleset/midround/from_ghosts/sentient_disease/ready(forced = FALSE)
if (required_candidates > (dead_players.len + list_observers.len))
+ SSblackbox.record_feedback("tally","dynamic",1,"Times rulesets rejected due to not enough ghosts")
return FALSE
return ..()
@@ -557,6 +564,7 @@
if(deadMobs < REVENANT_SPAWN_THRESHOLD)
return FALSE
if(required_candidates > (dead_players.len + list_observers.len))
+ SSblackbox.record_feedback("tally","dynamic",1,"Times rulesets rejected due to not enough ghosts")
return FALSE
for(var/mob/living/L in GLOB.dead_mob_list) //look for any dead bodies
var/turf/T = get_turf(L)
@@ -604,6 +612,7 @@
/datum/dynamic_ruleset/midround/from_ghosts/slaughter_demon/ready(forced = FALSE)
if(required_candidates > (dead_players.len + list_observers.len))
+ SSblackbox.record_feedback("tally","dynamic",1,"Times rulesets rejected due to not enough ghosts")
return FALSE
for(var/obj/effect/landmark/carpspawn/L in GLOB.landmarks_list)
if(isturf(L.loc))
@@ -655,6 +664,7 @@
/datum/dynamic_ruleset/midround/from_ghosts/abductors/ready(forced = FALSE)
if(required_candidates > (dead_players.len + list_observers.len))
+ SSblackbox.record_feedback("tally","dynamic",1,"Times rulesets rejected due to not enough ghosts")
return FALSE
team = new /datum/team/abductor_team
if(team.team_number > ABDUCTOR_MAX_TEAMS)
@@ -694,6 +704,7 @@
/datum/dynamic_ruleset/midround/from_ghosts/ninja/ready(forced = FALSE)
if(required_candidates > (dead_players.len + list_observers.len))
+ SSblackbox.record_feedback("tally","dynamic",1,"Times rulesets rejected due to not enough ghosts")
return FALSE
if(!spawn_loc)
var/list/spawn_locs = list()
diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm
index 2d476eab19..e5bb0af464 100644
--- a/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm
+++ b/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm
@@ -21,6 +21,7 @@
requirements = list(50,50,50,50,50,50,50,50,50,50)
high_population_requirement = 40
antag_cap = list(1,1,1,1,2,2,2,2,3,3)
+ always_max_weight = TRUE
var/autotraitor_cooldown = 450 // 15 minutes (ticks once per 2 sec)
/datum/dynamic_ruleset/roundstart/traitor/pre_execute()
diff --git a/code/game/machinery/computer/communications.dm b/code/game/machinery/computer/communications.dm
index a8b4608b36..74694113c0 100755
--- a/code/game/machinery/computer/communications.dm
+++ b/code/game/machinery/computer/communications.dm
@@ -279,7 +279,7 @@
// OMG CENTCOM LETTERHEAD
if("MessageCentCom")
- if(authenticated==2)
+ if(authenticated)
if(!checkCCcooldown())
to_chat(usr, "Arrays recycling. Please stand by.")
return
diff --git a/code/game/machinery/suit_storage_unit.dm b/code/game/machinery/suit_storage_unit.dm
index f35caf514c..adc0e5bf1e 100644
--- a/code/game/machinery/suit_storage_unit.dm
+++ b/code/game/machinery/suit_storage_unit.dm
@@ -326,6 +326,9 @@
/obj/machinery/suit_storage_unit/attackby(obj/item/I, mob/user, params)
if(state_open && is_operational())
+ if(istype(I, /obj/item/clothing/head/mob_holder))
+ to_chat(user, "You can't quite fit that in while you hold it!")
+ return
if(istype(I, /obj/item/clothing/suit))
if(suit)
to_chat(user, "The unit already contains a suit!.")
@@ -437,4 +440,4 @@
if(I)
I.forceMove(loc)
. = TRUE
- update_icon()
\ No newline at end of file
+ update_icon()
diff --git a/code/game/machinery/washing_machine.dm b/code/game/machinery/washing_machine.dm
index 2ca0df75ae..6990a7a4ba 100644
--- a/code/game/machinery/washing_machine.dm
+++ b/code/game/machinery/washing_machine.dm
@@ -219,6 +219,10 @@
update_icon()
return
+ if(istype(W, /obj/item/clothing/head/mob_holder))
+ to_chat(user, "It's too unwieldly to put in this way.")
+ return 1
+
else if(user.a_intent != INTENT_HARM)
if (!state_open)
diff --git a/code/game/objects/effects/spawners/lootdrop.dm b/code/game/objects/effects/spawners/lootdrop.dm
index bdb949a570..4540e48ebc 100644
--- a/code/game/objects/effects/spawners/lootdrop.dm
+++ b/code/game/objects/effects/spawners/lootdrop.dm
@@ -165,7 +165,7 @@
/obj/item/organ/heart/gland/chem = 5,
/obj/item/organ/heart/gland/mindshock = 5,
/obj/item/organ/heart/gland/plasma = 7,
- /obj/item/organ/heart/gland/pop = 5,
+ /obj/item/organ/heart/gland/transform = 5,
/obj/item/organ/heart/gland/slime = 4,
/obj/item/organ/heart/gland/spiderman = 5,
/obj/item/organ/heart/gland/ventcrawling = 1,
diff --git a/code/game/objects/effects/temporary_visuals/miscellaneous.dm b/code/game/objects/effects/temporary_visuals/miscellaneous.dm
index a103c0a54f..1b85a41f7c 100644
--- a/code/game/objects/effects/temporary_visuals/miscellaneous.dm
+++ b/code/game/objects/effects/temporary_visuals/miscellaneous.dm
@@ -349,6 +349,10 @@
icon_state = "impact_laser_purple"
duration = 4
+/obj/effect/temp_visual/impact_effect/shrink
+ icon_state = "m_shield"
+ duration = 10
+
/obj/effect/temp_visual/impact_effect/ion
icon_state = "shieldsparkles"
duration = 6
diff --git a/code/game/objects/items/circuitboards/machine_circuitboards.dm b/code/game/objects/items/circuitboards/machine_circuitboards.dm
index 2392973b22..10a2b2c807 100644
--- a/code/game/objects/items/circuitboards/machine_circuitboards.dm
+++ b/code/game/objects/items/circuitboards/machine_circuitboards.dm
@@ -665,6 +665,13 @@
name = "Booze Dispenser (Machine Board)"
build_path = /obj/machinery/chem_dispenser/drinks/beer
+/obj/item/circuitboard/machine/chem_dispenser/abductor
+ name = "Reagent Synthetizer (Abductor Machine Board)"
+ icon_state = "abductor_mod"
+ build_path = /obj/machinery/chem_dispenser/abductor
+ def_components = list(/obj/item/stock_parts/cell = /obj/item/stock_parts/cell/high)
+ needs_anchored = FALSE
+
/obj/item/circuitboard/machine/smoke_machine
name = "Smoke Machine (Machine Board)"
build_path = /obj/machinery/smoke_machine
diff --git a/code/game/objects/items/grenades/flashbang.dm b/code/game/objects/items/grenades/flashbang.dm
index 48ecf8273a..66897fa722 100644
--- a/code/game/objects/items/grenades/flashbang.dm
+++ b/code/game/objects/items/grenades/flashbang.dm
@@ -14,23 +14,31 @@
do_sparks(rand(5, 9), FALSE, src)
playsound(flashbang_turf, 'sound/weapons/flashbang.ogg', 100, TRUE, 8, 0.9)
new /obj/effect/dummy/lighting_obj (flashbang_turf, LIGHT_COLOR_WHITE, (flashbang_range + 2), 4, 2)
- for(var/mob/living/M in get_hearers_in_view(flashbang_range, flashbang_turf))
- bang(get_turf(M), M)
+ flashbang_mobs(flashbang_turf, flashbang_range)
qdel(src)
-/obj/item/grenade/flashbang/proc/bang(turf/T , mob/living/M)
+/obj/item/grenade/flashbang/proc/flashbang_mobs(turf/source, range)
+ var/list/banged = get_hearers_in_view(range, source)
+ var/list/flashed = viewers(range, source)
+ for(var/i in banged)
+ bang(i, source)
+ for(var/i in flashed)
+ flash(i, source)
+
+/obj/item/grenade/flashbang/proc/bang(mob/living/M, turf/source)
if(M.stat == DEAD) //They're dead!
return
M.show_message("BANG", MSG_AUDIBLE)
- var/distance = max(0,get_dist(get_turf(src),T))
-
-//Flash
- if(M.flash_act(affect_silicon = 1))
- M.Knockdown(max(200/max(1,distance), 60))
-//Bang
+ var/distance = get_dist(get_turf(M), source)
if(!distance || loc == M || loc == M.loc) //Stop allahu akbarring rooms with this.
M.Knockdown(200)
M.soundbang_act(1, 200, 10, 15)
-
else
M.soundbang_act(1, max(200/max(1,distance), 60), rand(0, 5))
+
+/obj/item/grenade/flashbang/proc/flash(mob/living/M, turf/source)
+ if(M.stat == DEAD) //They're dead!
+ return
+ var/distance = get_dist(get_turf(M), source)
+ if(M.flash_act(affect_silicon = 1))
+ M.Knockdown(max(200/max(1,distance), 60))
diff --git a/code/modules/antagonists/abductor/abductor.dm b/code/modules/antagonists/abductor/abductor.dm
index e8d30e8dbe..92504641a9 100644
--- a/code/modules/antagonists/abductor/abductor.dm
+++ b/code/modules/antagonists/abductor/abductor.dm
@@ -65,6 +65,8 @@
//Equip
var/mob/living/carbon/human/H = owner.current
H.set_species(/datum/species/abductor)
+ var/obj/item/organ/tongue/abductor/T = H.getorganslot(ORGAN_SLOT_TONGUE)
+ T.mothership = "[team.name]"
H.real_name = "[team.name] [sub_role]"
H.equipOutfit(outfit)
diff --git a/code/modules/antagonists/abductor/equipment/abduction_gear.dm b/code/modules/antagonists/abductor/equipment/abduction_gear.dm
index b708c59ef7..433f52306b 100644
--- a/code/modules/antagonists/abductor/equipment/abduction_gear.dm
+++ b/code/modules/antagonists/abductor/equipment/abduction_gear.dm
@@ -17,7 +17,7 @@
actions_types = list(/datum/action/item_action/hands_free/activate)
allowed = list(
/obj/item/abductor,
- /obj/item/abductor_baton,
+ /obj/item/abductor/baton,
/obj/item/melee/baton,
/obj/item/gun/energy,
/obj/item/restraints/handcuffs
@@ -57,7 +57,7 @@
/obj/item/clothing/suit/armor/abductor/vest/item_action_slot_check(slot, mob/user, datum/action/A)
if(slot == SLOT_WEAR_SUIT) //we only give the mob the ability to activate the vest if he's actually wearing it.
- return 1
+ return TRUE
/obj/item/clothing/suit/armor/abductor/vest/proc/SetDisguise(datum/icon_snapshot/entry)
disguise = entry
@@ -89,11 +89,9 @@
/obj/item/clothing/suit/armor/abductor/vest/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK)
DeactivateStealth()
- return 0
/obj/item/clothing/suit/armor/abductor/vest/IsReflect()
DeactivateStealth()
- return 0
/obj/item/clothing/suit/armor/abductor/vest/ui_action_click()
switch(mode)
@@ -111,7 +109,10 @@
to_chat(loc, "Combat injection is still recharging.")
return
var/mob/living/carbon/human/M = loc
- M.do_adrenaline(150, FALSE, 0, 0, TRUE, list("inaprovaline" = 3, "synaptizine" = 10, "omnizine" = 10), "You feel a sudden surge of energy!")
+ M.adjustStaminaLoss(-75)
+ M.SetUnconscious(0)
+ M.SetStun(0)
+ M.SetKnockdown(0)
combat_cooldown = 0
START_PROCESSING(SSobj, src)
@@ -131,9 +132,11 @@
/obj/item/abductor
icon = 'icons/obj/abductor.dmi'
+ lefthand_file = 'icons/mob/inhands/antag/abductor_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/antag/abductor_righthand.dmi'
/obj/item/abductor/proc/AbductorCheck(mob/user)
- if(HAS_TRAIT(user, TRAIT_ABDUCTOR_TRAINING))
+ if (HAS_TRAIT(user, TRAIT_ABDUCTOR_TRAINING))
return TRUE
if (istype(user) && user.mind && HAS_TRAIT(user.mind, TRAIT_ABDUCTOR_TRAINING))
return TRUE
@@ -158,8 +161,6 @@
desc = "A dual-mode tool for retrieving specimens and scanning appearances. Scanning can be done through cameras."
icon_state = "gizmo_scan"
item_state = "silencer"
- lefthand_file = 'icons/mob/inhands/antag/abductor_lefthand.dmi'
- righthand_file = 'icons/mob/inhands/antag/abductor_righthand.dmi'
var/mode = GIZMO_SCAN
var/mob/living/marked = null
var/obj/machinery/abductor/console/console
@@ -218,12 +219,9 @@
if(marked == target)
to_chat(user, "This specimen is already marked!")
return
- if(ishuman(target))
- if(isabductor(target))
- marked = target
- to_chat(user, "You mark [target] for future retrieval.")
- else
- prepare(target,user)
+ if(isabductor(target) || iscow(target))
+ marked = target
+ to_chat(user, "You mark [target] for future retrieval.")
else
prepare(target,user)
@@ -247,8 +245,6 @@
desc = "A compact device used to shut down communications equipment."
icon_state = "silencer"
item_state = "gizmo"
- lefthand_file = 'icons/mob/inhands/antag/abductor_lefthand.dmi'
- righthand_file = 'icons/mob/inhands/antag/abductor_righthand.dmi'
/obj/item/abductor/silencer/attack(mob/living/M, mob/user)
if(!AbductorCheck(user))
@@ -292,8 +288,6 @@
or to send a command to a test subject with a charged gland."
icon_state = "mind_device_message"
item_state = "silencer"
- lefthand_file = 'icons/mob/inhands/antag/abductor_lefthand.dmi'
- righthand_file = 'icons/mob/inhands/antag/abductor_righthand.dmi'
var/mode = MIND_DEVICE_MESSAGE
/obj/item/abductor/mind_device/attack_self(mob/user)
@@ -389,6 +383,17 @@
item_state = "alienpistol"
trigger_guard = TRIGGER_GUARD_ALLOW_ALL
+/obj/item/gun/energy/shrink_ray
+ name = "shrink ray blaster"
+ desc = "This is a piece of frightening alien tech that enhances the magnetic pull of atoms in a localized space to temporarily make an object shrink. \
+ That or it's just space magic. Either way, it shrinks stuff."
+ ammo_type = list(/obj/item/ammo_casing/energy/shrink)
+ item_state = "shrink_ray"
+ icon_state = "shrink_ray"
+ fire_delay = 30
+ selfcharge = 1//shot costs 200 energy, has a max capacity of 1000 for 5 shots. self charge returns 25 energy every couple ticks, so about 1 shot charged every 12~ seconds
+ trigger_guard = TRIGGER_GUARD_ALLOW_ALL// variable-size trigger, get it? (abductors need this to be set so the gun is usable for them)
+
/obj/item/paper/guides/antag/abductor
name = "Dissection Guide"
icon_state = "alienpaper_words"
@@ -422,21 +427,18 @@
#define BATON_PROBE 3
#define BATON_MODES 4
-/obj/item/abductor_baton
+/obj/item/abductor/baton
name = "advanced baton"
desc = "A quad-mode baton used for incapacitation and restraining of specimens."
var/mode = BATON_STUN
- icon = 'icons/obj/abductor.dmi'
icon_state = "wonderprodStun"
item_state = "wonderprod"
- lefthand_file = 'icons/mob/inhands/antag/abductor_lefthand.dmi'
- righthand_file = 'icons/mob/inhands/antag/abductor_righthand.dmi'
slot_flags = ITEM_SLOT_BELT
force = 7
w_class = WEIGHT_CLASS_NORMAL
actions_types = list(/datum/action/item_action/toggle_mode)
-/obj/item/abductor_baton/proc/toggle(mob/living/user=usr)
+/obj/item/abductor/baton/proc/toggle(mob/living/user=usr)
mode = (mode+1)%BATON_MODES
var/txt
switch(mode)
@@ -452,7 +454,7 @@
to_chat(usr, "You switch the baton to [txt] mode.")
update_icon()
-/obj/item/abductor_baton/update_icon()
+/obj/item/abductor/baton/update_icon()
switch(mode)
if(BATON_STUN)
icon_state = "wonderprodStun"
@@ -467,8 +469,8 @@
icon_state = "wonderprodProbe"
item_state = "wonderprodProbe"
-/obj/item/abductor_baton/attack(mob/target, mob/living/user)
- if(!isabductor(user))
+/obj/item/abductor/baton/attack(mob/target, mob/living/user)
+ if(!AbductorCheck(user))
return
if(iscyborg(target))
@@ -485,8 +487,8 @@
if(ishuman(L))
var/mob/living/carbon/human/H = L
if(H.check_shields(src, 0, "[user]'s [name]", MELEE_ATTACK))
- playsound(L, 'sound/weapons/genhit.ogg', 50, 1)
- return 0
+ playsound(H, 'sound/weapons/genhit.ogg', 50, TRUE)
+ return FALSE
switch (mode)
if(BATON_STUN)
@@ -498,10 +500,10 @@
if(BATON_PROBE)
ProbeAttack(L,user)
-/obj/item/abductor_baton/attack_self(mob/living/user)
+/obj/item/abductor/baton/attack_self(mob/living/user)
toggle(user)
-/obj/item/abductor_baton/proc/StunAttack(mob/living/L,mob/living/user)
+/obj/item/abductor/baton/proc/StunAttack(mob/living/L,mob/living/user)
L.lastattacker = user.real_name
L.lastattackerckey = user.ckey
@@ -513,7 +515,7 @@
L.visible_message("[user] has stunned [L] with [src]!", \
"[user] has stunned you with [src]!")
- playsound(loc, 'sound/weapons/egloves.ogg', 50, 1, -1)
+ playsound(src, 'sound/weapons/egloves.ogg', 50, TRUE, -1)
if(ishuman(L))
var/mob/living/carbon/human/H = L
@@ -521,7 +523,7 @@
log_combat(user, L, "stunned")
-/obj/item/abductor_baton/proc/SleepAttack(mob/living/L,mob/living/user)
+/obj/item/abductor/baton/proc/SleepAttack(mob/living/L,mob/living/user)
if(L.incapacitated(TRUE, TRUE))
if(L.anti_magic_check(FALSE, FALSE, TRUE, 0))
to_chat(user, "The specimen's tinfoil protection is interfering with the sleep inducement!")
@@ -531,7 +533,7 @@
return
L.visible_message("[user] has induced sleep in [L] with [src]!", \
"You suddenly feel very drowsy!")
- playsound(loc, 'sound/weapons/egloves.ogg', 50, 1, -1)
+ playsound(src, 'sound/weapons/egloves.ogg', 50, TRUE, -1)
L.Sleeping(1200)
log_combat(user, L, "put to sleep")
else
@@ -545,13 +547,13 @@
L.visible_message("[user] tried to induce sleep in [L] with [src]!", \
"You suddenly feel drowsy!")
-/obj/item/abductor_baton/proc/CuffAttack(mob/living/L,mob/living/user)
+/obj/item/abductor/baton/proc/CuffAttack(mob/living/L,mob/living/user)
if(!iscarbon(L))
return
var/mob/living/carbon/C = L
if(!C.handcuffed)
if(C.get_num_arms(FALSE) >= 2 || C.get_arm_ignore())
- playsound(loc, 'sound/weapons/cablecuff.ogg', 30, 1, -2)
+ playsound(src, 'sound/weapons/cablecuff.ogg', 30, TRUE, -2)
C.visible_message("[user] begins restraining [C] with [src]!", \
"[user] begins shaping an energy field around your hands!")
if(do_mob(user, C, 30) && (C.get_num_arms(FALSE) >= 2 || C.get_arm_ignore()))
@@ -565,7 +567,7 @@
else
to_chat(user, "[C] doesn't have two hands...")
-/obj/item/abductor_baton/proc/ProbeAttack(mob/living/L,mob/living/user)
+/obj/item/abductor/baton/proc/ProbeAttack(mob/living/L,mob/living/user)
L.visible_message("[user] probes [L] with [src]!", \
"[user] probes you!")
@@ -610,7 +612,7 @@
S.start()
. = ..()
-/obj/item/abductor_baton/examine(mob/user)
+/obj/item/abductor/baton/examine(mob/user)
. = ..()
switch(mode)
if(BATON_STUN)
@@ -639,10 +641,44 @@
AddComponent(/datum/component/wearertargeting/earprotection, list(SLOT_EARS))
/obj/item/radio/headset/abductor/attackby(obj/item/W, mob/user, params)
- if(istype(W, /obj/item/screwdriver))
+ if(W.tool_behaviour == TOOL_SCREWDRIVER)
return // Stops humans from disassembling abductor headsets.
return ..()
+/obj/item/abductor_machine_beacon
+ name = "machine beacon"
+ desc = "A beacon designed to instantly tele-construct abductor machinery."
+ icon = 'icons/obj/abductor.dmi'
+ icon_state = "beacon"
+ w_class = WEIGHT_CLASS_TINY
+ var/obj/machinery/spawned_machine
+
+/obj/item/abductor_machine_beacon/attack_self(mob/user)
+ ..()
+ user.visible_message("[user] places down [src] and activates it.", "You place down [src] and activate it.")
+ user.dropItemToGround(src)
+ playsound(src, 'sound/machines/terminal_alert.ogg', 50)
+ addtimer(CALLBACK(src, .proc/try_spawn_machine), 30)
+
+/obj/item/abductor_machine_beacon/proc/try_spawn_machine()
+ var/viable = FALSE
+ if(isfloorturf(loc))
+ var/turf/T = loc
+ viable = TRUE
+ for(var/obj/thing in T.contents)
+ if(thing.density || ismachinery(thing) || isstructure(thing))
+ viable = FALSE
+ if(viable)
+ playsound(src, 'sound/effects/phasein.ogg', 50, TRUE)
+ var/new_machine = new spawned_machine(loc)
+ visible_message("[new_machine] warps on top of the beacon!")
+ qdel(src)
+ else
+ playsound(src, 'sound/machines/buzz-two.ogg', 50)
+
+/obj/item/abductor_machine_beacon/chem_dispenser
+ name = "beacon - Reagent Synthesizer"
+ spawned_machine = /obj/machinery/chem_dispenser/abductor
/obj/item/scalpel/alien
name = "alien scalpel"
@@ -706,11 +742,11 @@
framestackamount = 1
/obj/structure/table_frame/abductor/attackby(obj/item/I, mob/user, params)
- if(istype(I, /obj/item/wrench))
+ if(I.tool_behaviour == TOOL_WRENCH)
to_chat(user, "You start disassembling [src]...")
I.play_tool_sound(src)
if(I.use_tool(src, user, 30))
- playsound(src.loc, 'sound/items/deconstruct.ogg', 50, 1)
+ playsound(src, 'sound/items/deconstruct.ogg', 50, TRUE)
for(var/i = 1, i <= framestackamount, i++)
new framestack(get_turf(src))
qdel(src)
@@ -760,7 +796,6 @@
icon = 'icons/obj/abductor.dmi'
icon_state = "bed"
can_buckle = 1
- buckle_lying = 1
var/static/list/injected_reagents = list("corazone")
@@ -798,3 +833,11 @@
airlock_type = /obj/machinery/door/airlock/abductor
material_type = /obj/item/stack/sheet/mineral/abductor
noglass = TRUE
+
+/obj/item/clothing/under/abductor
+ desc = "The most advanced form of jumpsuit known to reality, looks uncomfortable."
+ name = "alien jumpsuit"
+ icon_state = "abductor"
+ item_state = "bl_suit"
+ armor = list(melee = 0, bullet = 0, laser = 0, energy = 0, bomb = 10, bio = 10, rad = 0, fire = 0, acid = 0)
+ can_adjust = 0
diff --git a/code/modules/antagonists/abductor/equipment/abduction_outfits.dm b/code/modules/antagonists/abductor/equipment/abduction_outfits.dm
index 7bfadf6f3b..ec76f61ec6 100644
--- a/code/modules/antagonists/abductor/equipment/abduction_outfits.dm
+++ b/code/modules/antagonists/abductor/equipment/abduction_outfits.dm
@@ -1,6 +1,6 @@
/datum/outfit/abductor
name = "Abductor Basic"
- uniform = /obj/item/clothing/under/color/grey //they're greys gettit
+ uniform = /obj/item/clothing/under/abductor
shoes = /obj/item/clothing/shoes/combat
back = /obj/item/storage/backpack
ears = /obj/item/radio/headset/abductor
@@ -34,7 +34,7 @@
name = "Abductor Agent"
head = /obj/item/clothing/head/helmet/abductor
suit = /obj/item/clothing/suit/armor/abductor/vest
- suit_store = /obj/item/abductor_baton
+ suit_store = /obj/item/abductor/baton
belt = /obj/item/storage/belt/military/abductor/full
backpack_contents = list(
diff --git a/code/modules/antagonists/abductor/equipment/gland.dm b/code/modules/antagonists/abductor/equipment/gland.dm
index f7b49e1cf4..82170444d0 100644
--- a/code/modules/antagonists/abductor/equipment/gland.dm
+++ b/code/modules/antagonists/abductor/equipment/gland.dm
@@ -5,18 +5,23 @@
icon_state = "gland"
status = ORGAN_ROBOTIC
beating = TRUE
+ organ_flags = ORGAN_NO_SPOIL
var/true_name = "baseline placebo referencer"
var/cooldown_low = 300
var/cooldown_high = 300
var/next_activation = 0
var/uses // -1 For infinite
- var/human_only = 0
- var/active = 0
+ var/human_only = FALSE
+ var/active = FALSE
var/mind_control_uses = 1
var/mind_control_duration = 1800
var/active_mind_control = FALSE
+/obj/item/organ/heart/gland/Initialize()
+ . = ..()
+ icon_state = pick(list("health", "spider", "slime", "emp", "species", "egg", "vent", "mindshock", "viral"))
+
/obj/item/organ/heart/gland/examine(mob/user)
. = ..()
if((user.mind && HAS_TRAIT(user.mind, TRAIT_ABDUCTOR_SCIENTIST_TRAINING)) || isobserver(user))
@@ -55,14 +60,18 @@
active_mind_control = TRUE
message_admins("[key_name(user)] sent an abductor mind control message to [key_name(owner)]: [command]")
update_gland_hud()
-
+ var/obj/screen/alert/mind_control/mind_alert = owner.throw_alert("mind_control", /obj/screen/alert/mind_control)
+ mind_alert.command = command
addtimer(CALLBACK(src, .proc/clear_mind_control), mind_control_duration)
+ return TRUE
/obj/item/organ/heart/gland/proc/clear_mind_control()
if(!ownerCheck() || !active_mind_control)
return FALSE
- to_chat(owner, "You feel the compulsion fade, and you completely forget about your previous orders.")
+ to_chat(owner, "You feel the compulsion fade, and you completely forget about your previous orders.")
+ owner.clear_alert("mind_control")
active_mind_control = FALSE
+ return TRUE
/obj/item/organ/heart/gland/Remove(mob/living/carbon/M, special = 0)
active = 0
@@ -98,257 +107,4 @@
active = 0
/obj/item/organ/heart/gland/proc/activate()
- return
-
-/obj/item/organ/heart/gland/heals
- true_name = "coherency harmonizer"
- cooldown_low = 200
- cooldown_high = 400
- uses = -1
- icon_state = "health"
- mind_control_uses = 3
- mind_control_duration = 3000
-
-/obj/item/organ/heart/gland/heals/activate()
- to_chat(owner, "You feel curiously revitalized.")
- owner.adjustToxLoss(-20, FALSE, TRUE)
- owner.heal_bodypart_damage(20, 20, 0, TRUE)
- owner.adjustOxyLoss(-20)
-
-/obj/item/organ/heart/gland/slime
- true_name = "gastric animation galvanizer"
- cooldown_low = 600
- cooldown_high = 1200
- uses = -1
- icon_state = "slime"
- mind_control_uses = 1
- mind_control_duration = 2400
-
-/obj/item/organ/heart/gland/slime/Insert(mob/living/carbon/M, special = 0, drop_if_replaced = TRUE)
- ..()
- owner.faction |= "slime"
- owner.grant_language(/datum/language/slime)
-
-/obj/item/organ/heart/gland/slime/activate()
- to_chat(owner, "You feel nauseated!")
- owner.vomit(20)
-
- var/mob/living/simple_animal/slime/Slime = new(get_turf(owner), "grey")
- Slime.Friends = list(owner)
- Slime.Leader = owner
-
-/obj/item/organ/heart/gland/mindshock
- true_name = "neural crosstalk uninhibitor"
- cooldown_low = 400
- cooldown_high = 700
- uses = -1
- icon_state = "mindshock"
- mind_control_uses = 1
- mind_control_duration = 6000
-
-/obj/item/organ/heart/gland/mindshock/activate()
- to_chat(owner, "You get a headache.")
-
- var/turf/T = get_turf(owner)
- for(var/mob/living/carbon/H in orange(4,T))
- if(H == owner)
- continue
- switch(pick(1,3))
- if(1)
- to_chat(H, "You hear a loud buzz in your head, silencing your thoughts!")
- H.Stun(50)
- if(2)
- to_chat(H, "You hear an annoying buzz in your head.")
- H.confused += 15
- H.adjustOrganLoss(ORGAN_SLOT_BRAIN, 10, 160)
- if(3)
- H.hallucination += 60
-
-/obj/item/organ/heart/gland/pop
- true_name = "anthropmorphic translocator"
- cooldown_low = 900
- cooldown_high = 1800
- uses = -1
- human_only = TRUE
- icon_state = "species"
- mind_control_uses = 5
- mind_control_duration = 300
-
-/obj/item/organ/heart/gland/pop/activate()
- to_chat(owner, "You feel unlike yourself.")
- randomize_human(owner)
- var/species = pick(list(/datum/species/human, /datum/species/lizard, /datum/species/insect, /datum/species/fly))
- owner.set_species(species)
-
-/obj/item/organ/heart/gland/ventcrawling
- true_name = "pliant cartilage enabler"
- cooldown_low = 1800
- cooldown_high = 2400
- uses = 1
- icon_state = "vent"
- mind_control_uses = 4
- mind_control_duration = 1800
-
-/obj/item/organ/heart/gland/ventcrawling/activate()
- to_chat(owner, "You feel very stretchy.")
- owner.ventcrawler = VENTCRAWLER_ALWAYS
-
-/obj/item/organ/heart/gland/viral
- true_name = "contamination incubator"
- cooldown_low = 1800
- cooldown_high = 2400
- uses = 1
- icon_state = "viral"
- mind_control_uses = 1
- mind_control_duration = 1800
-
-/obj/item/organ/heart/gland/viral/activate()
- to_chat(owner, "You feel sick.")
- var/datum/disease/advance/A = random_virus(pick(2,6),6)
- A.carrier = TRUE
- owner.ForceContractDisease(A, FALSE, TRUE)
-
-/obj/item/organ/heart/gland/viral/proc/random_virus(max_symptoms, max_level)
- if(max_symptoms > VIRUS_SYMPTOM_LIMIT)
- max_symptoms = VIRUS_SYMPTOM_LIMIT
- var/datum/disease/advance/A = new /datum/disease/advance()
- var/list/datum/symptom/possible_symptoms = list()
- for(var/symptom in subtypesof(/datum/symptom))
- var/datum/symptom/S = symptom
- if(initial(S.level) > max_level)
- continue
- if(initial(S.level) <= 0) //unobtainable symptoms
- continue
- possible_symptoms += S
- for(var/i in 1 to max_symptoms)
- var/datum/symptom/chosen_symptom = pick_n_take(possible_symptoms)
- if(chosen_symptom)
- var/datum/symptom/S = new chosen_symptom
- A.symptoms += S
- A.Refresh() //just in case someone already made and named the same disease
- return A
-
-/obj/item/organ/heart/gland/trauma
- true_name = "white matter randomiser"
- cooldown_low = 800
- cooldown_high = 1200
- uses = 5
- icon_state = "emp"
- mind_control_uses = 3
- mind_control_duration = 1800
-
-/obj/item/organ/heart/gland/trauma/activate()
- to_chat(owner, "You feel a spike of pain in your head.")
- if(prob(33))
- owner.gain_trauma_type(BRAIN_TRAUMA_SPECIAL, rand(TRAUMA_RESILIENCE_BASIC, TRAUMA_RESILIENCE_LOBOTOMY))
- else
- if(prob(20))
- owner.gain_trauma_type(BRAIN_TRAUMA_SEVERE, rand(TRAUMA_RESILIENCE_BASIC, TRAUMA_RESILIENCE_LOBOTOMY))
- else
- owner.gain_trauma_type(BRAIN_TRAUMA_MILD, rand(TRAUMA_RESILIENCE_BASIC, TRAUMA_RESILIENCE_LOBOTOMY))
-
-/obj/item/organ/heart/gland/spiderman
- true_name = "araneae cloister accelerator"
- cooldown_low = 450
- cooldown_high = 900
- uses = -1
- icon_state = "spider"
- mind_control_uses = 2
- mind_control_duration = 2400
-
-/obj/item/organ/heart/gland/spiderman/activate()
- to_chat(owner, "You feel something crawling in your skin.")
- owner.faction |= "spiders"
- var/obj/structure/spider/spiderling/S = new(owner.drop_location())
- S.directive = "Protect your nest inside [owner.real_name]."
-
-/obj/item/organ/heart/gland/egg
- true_name = "roe/enzymatic synthesizer"
- cooldown_low = 300
- cooldown_high = 400
- uses = -1
- icon_state = "egg"
- lefthand_file = 'icons/mob/inhands/misc/food_lefthand.dmi'
- righthand_file = 'icons/mob/inhands/misc/food_righthand.dmi'
- mind_control_uses = 2
- mind_control_duration = 1800
-
-/obj/item/organ/heart/gland/egg/activate()
- owner.visible_message("[owner] [pick(EGG_LAYING_MESSAGES)]")
- var/turf/T = owner.drop_location()
- new /obj/item/reagent_containers/food/snacks/egg/gland(T)
-
-/obj/item/organ/heart/gland/electric
- true_name = "electron accumulator/discharger"
- cooldown_low = 800
- cooldown_high = 1200
- uses = -1
- mind_control_uses = 2
- mind_control_duration = 900
-
-/obj/item/organ/heart/gland/electric/Insert(mob/living/carbon/M, special = 0, drop_if_replaced = TRUE)
- ..()
- ADD_TRAIT(owner, TRAIT_SHOCKIMMUNE, ORGAN_TRAIT)
-
-/obj/item/organ/heart/gland/electric/Remove(mob/living/carbon/M, special = 0)
- REMOVE_TRAIT(owner, TRAIT_SHOCKIMMUNE, ORGAN_TRAIT)
- ..()
-
-/obj/item/organ/heart/gland/electric/activate()
- owner.visible_message("[owner]'s skin starts emitting electric arcs!",\
- "You feel electric energy building up inside you!")
- playsound(get_turf(owner), "sparks", 100, 1, -1)
- addtimer(CALLBACK(src, .proc/zap), rand(30, 100))
-
-/obj/item/organ/heart/gland/electric/proc/zap()
- tesla_zap(owner, 4, 8000, TESLA_MOB_DAMAGE | TESLA_OBJ_DAMAGE | TESLA_MOB_STUN)
- playsound(get_turf(owner), 'sound/magic/lightningshock.ogg', 50, 1)
-
-/obj/item/organ/heart/gland/chem
- true_name = "intrinsic pharma-provider"
- cooldown_low = 50
- cooldown_high = 50
- uses = -1
- mind_control_uses = 3
- mind_control_duration = 1200
- var/list/possible_reagents = list()
-
-/obj/item/organ/heart/gland/chem/Initialize()
- ..()
- for(var/X in subtypesof(/datum/reagent/drug))
- var/datum/reagent/R = X
- possible_reagents += initial(R.id)
- for(var/X in subtypesof(/datum/reagent/medicine))
- var/datum/reagent/R = X
- possible_reagents += initial(R.id)
- for(var/X in typesof(/datum/reagent/toxin))
- var/datum/reagent/R = X
- possible_reagents += initial(R.id)
-
-/obj/item/organ/heart/gland/chem/activate()
- var/chem_to_add = pick(possible_reagents)
- owner.reagents.add_reagent(chem_to_add, 2)
- owner.adjustToxLoss(-2, TRUE, TRUE)
- ..()
-
-/obj/item/organ/heart/gland/plasma
- true_name = "effluvium sanguine-synonym emitter"
- cooldown_low = 1200
- cooldown_high = 1800
- uses = -1
- mind_control_uses = 1
- mind_control_duration = 800
-
-/obj/item/organ/heart/gland/plasma/activate()
- to_chat(owner, "You feel bloated.")
- addtimer(CALLBACK(GLOBAL_PROC, .proc/to_chat, owner, "A massive stomachache overcomes you."), 150)
- addtimer(CALLBACK(src, .proc/vomit_plasma), 200)
-
-/obj/item/organ/heart/gland/plasma/proc/vomit_plasma()
- if(!owner)
- return
- owner.visible_message("[owner] vomits a cloud of plasma!")
- var/turf/open/T = get_turf(owner)
- if(istype(T))
- T.atmos_spawn_air("plasma=50;TEMP=[T20C]")
- owner.vomit()
+ return
\ No newline at end of file
diff --git a/code/modules/antagonists/abductor/equipment/glands/access.dm b/code/modules/antagonists/abductor/equipment/glands/access.dm
new file mode 100644
index 0000000000..548650d4e3
--- /dev/null
+++ b/code/modules/antagonists/abductor/equipment/glands/access.dm
@@ -0,0 +1,19 @@
+/obj/item/organ/heart/gland/access
+ true_name = "anagraphic electro-scrambler"
+ cooldown_low = 600
+ cooldown_high = 1200
+ uses = 1
+ icon_state = "mindshock"
+ mind_control_uses = 3
+ mind_control_duration = 900
+
+/obj/item/organ/heart/gland/access/activate()
+ to_chat(owner, "You feel like a VIP for some reason.")
+ RegisterSignal(owner, COMSIG_MOB_ALLOWED, .proc/free_access)
+
+/obj/item/organ/heart/gland/access/proc/free_access(datum/source, obj/O)
+ return TRUE
+
+/obj/item/organ/heart/gland/access/Remove(mob/living/carbon/M, special = 0)
+ UnregisterSignal(owner, COMSIG_MOB_ALLOWED)
+ ..()
\ No newline at end of file
diff --git a/code/modules/antagonists/abductor/equipment/glands/blood.dm b/code/modules/antagonists/abductor/equipment/glands/blood.dm
new file mode 100644
index 0000000000..06b8249484
--- /dev/null
+++ b/code/modules/antagonists/abductor/equipment/glands/blood.dm
@@ -0,0 +1,18 @@
+/obj/item/organ/heart/gland/blood
+ true_name = "pseudonuclear hemo-destabilizer"
+ cooldown_low = 1200
+ cooldown_high = 1800
+ uses = -1
+ icon_state = "egg"
+ lefthand_file = 'icons/mob/inhands/misc/food_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/misc/food_righthand.dmi'
+ mind_control_uses = 3
+ mind_control_duration = 1500
+
+/obj/item/organ/heart/gland/blood/activate()
+ if(!ishuman(owner) || !owner.dna.species)
+ return
+ var/mob/living/carbon/human/H = owner
+ var/datum/species/species = H.dna.species
+ to_chat(H, "You feel your blood heat up for a moment.")
+ species.exotic_blood = get_random_reagent_id()
\ No newline at end of file
diff --git a/code/modules/antagonists/abductor/equipment/glands/chem.dm b/code/modules/antagonists/abductor/equipment/glands/chem.dm
new file mode 100644
index 0000000000..e7b6fda85f
--- /dev/null
+++ b/code/modules/antagonists/abductor/equipment/glands/chem.dm
@@ -0,0 +1,20 @@
+/obj/item/organ/heart/gland/chem
+ true_name = "intrinsic pharma-provider"
+ cooldown_low = 50
+ cooldown_high = 50
+ uses = -1
+ icon_state = "viral"
+ mind_control_uses = 3
+ mind_control_duration = 1200
+ var/list/possible_reagents = list()
+
+/obj/item/organ/heart/gland/chem/Initialize()
+ . = ..()
+ for(var/R in subtypesof(/datum/reagent/drug) + subtypesof(/datum/reagent/medicine) + typesof(/datum/reagent/toxin))
+ possible_reagents += R
+
+/obj/item/organ/heart/gland/chem/activate()
+ var/chem_to_add = pick(possible_reagents)
+ owner.reagents.add_reagent(chem_to_add, 2)
+ owner.adjustToxLoss(-5, TRUE, TRUE)
+ ..()
\ No newline at end of file
diff --git a/code/modules/antagonists/abductor/equipment/glands/egg.dm b/code/modules/antagonists/abductor/equipment/glands/egg.dm
new file mode 100644
index 0000000000..429a24b19c
--- /dev/null
+++ b/code/modules/antagonists/abductor/equipment/glands/egg.dm
@@ -0,0 +1,15 @@
+/obj/item/organ/heart/gland/egg
+ true_name = "roe/enzymatic synthesizer"
+ cooldown_low = 300
+ cooldown_high = 400
+ uses = -1
+ icon_state = "egg"
+ lefthand_file = 'icons/mob/inhands/misc/food_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/misc/food_righthand.dmi'
+ mind_control_uses = 2
+ mind_control_duration = 1800
+
+/obj/item/organ/heart/gland/egg/activate()
+ owner.visible_message("[owner] [pick(EGG_LAYING_MESSAGES)]")
+ var/turf/T = owner.drop_location()
+ new /obj/item/reagent_containers/food/snacks/egg/gland(T)
\ No newline at end of file
diff --git a/code/modules/antagonists/abductor/equipment/glands/electric.dm b/code/modules/antagonists/abductor/equipment/glands/electric.dm
new file mode 100644
index 0000000000..63d95f8b1f
--- /dev/null
+++ b/code/modules/antagonists/abductor/equipment/glands/electric.dm
@@ -0,0 +1,26 @@
+/obj/item/organ/heart/gland/electric
+ true_name = "electron accumulator/discharger"
+ cooldown_low = 800
+ cooldown_high = 1200
+ icon_state = "species"
+ uses = -1
+ mind_control_uses = 2
+ mind_control_duration = 900
+
+/obj/item/organ/heart/gland/electric/Insert(mob/living/carbon/M, special = 0, drop_if_replaced = TRUE)
+ ..()
+ ADD_TRAIT(owner, TRAIT_SHOCKIMMUNE, "abductor_gland")
+
+/obj/item/organ/heart/gland/electric/Remove(mob/living/carbon/M, special = 0)
+ REMOVE_TRAIT(owner, TRAIT_SHOCKIMMUNE, "abductor_gland")
+ ..()
+
+/obj/item/organ/heart/gland/electric/activate()
+ owner.visible_message("[owner]'s skin starts emitting electric arcs!",\
+ "You feel electric energy building up inside you!")
+ playsound(get_turf(owner), "sparks", 100, TRUE, -1)
+ addtimer(CALLBACK(src, .proc/zap), rand(30, 100))
+
+/obj/item/organ/heart/gland/electric/proc/zap()
+ tesla_zap(owner, 4, 8000, TESLA_MOB_DAMAGE | TESLA_OBJ_DAMAGE | TESLA_MOB_STUN)
+ playsound(get_turf(owner), 'sound/magic/lightningshock.ogg', 50, TRUE)
\ No newline at end of file
diff --git a/code/modules/antagonists/abductor/equipment/glands/heal.dm b/code/modules/antagonists/abductor/equipment/glands/heal.dm
new file mode 100644
index 0000000000..bf9a00e13c
--- /dev/null
+++ b/code/modules/antagonists/abductor/equipment/glands/heal.dm
@@ -0,0 +1,178 @@
+/obj/item/organ/heart/gland/heal
+ true_name = "organic replicator"
+ cooldown_low = 200
+ cooldown_high = 400
+ uses = -1
+ human_only = TRUE
+ icon_state = "health"
+ mind_control_uses = 3
+ mind_control_duration = 3000
+
+/obj/item/organ/heart/gland/heal/activate()
+ if(!(owner.mob_biotypes & MOB_ORGANIC))
+ return
+
+ for(var/organ in owner.internal_organs)
+ if(istype(organ, /obj/item/organ/cyberimp))
+ reject_implant(organ)
+ return
+
+ var/obj/item/organ/liver/liver = owner.getorganslot(ORGAN_SLOT_LIVER)
+ if((!liver/* && !HAS_TRAIT(owner, TRAIT_NOMETABOLISM)*/) || (liver && ((liver.damage > (liver.maxHealth / 2)) || (istype(liver, /obj/item/organ/liver/cybernetic)))))
+ replace_liver(liver)
+ return
+
+ var/obj/item/organ/lungs/lungs = owner.getorganslot(ORGAN_SLOT_LUNGS)
+ if((!lungs && !HAS_TRAIT(owner, TRAIT_NOBREATH)) || (lungs && (istype(lungs, /obj/item/organ/lungs/cybernetic))))
+ replace_lungs(lungs)
+ return
+
+ var/obj/item/organ/eyes/eyes = owner.getorganslot(ORGAN_SLOT_EYES)
+ if(!eyes || (eyes && ((HAS_TRAIT_FROM(owner, TRAIT_NEARSIGHT, EYE_DAMAGE)) || (HAS_TRAIT_FROM(owner, TRAIT_BLIND, EYE_DAMAGE)) || (istype(eyes, /obj/item/organ/eyes/robotic)))))
+ replace_eyes(eyes)
+ return
+
+ var/obj/item/bodypart/limb
+ var/list/limb_list = list(BODY_ZONE_L_ARM, BODY_ZONE_R_ARM, BODY_ZONE_L_LEG, BODY_ZONE_R_LEG)
+ for(var/zone in limb_list)
+ limb = owner.get_bodypart(zone)
+ if(!limb)
+ replace_limb(zone)
+ return
+ if((limb.get_damage() >= (limb.max_damage / 2)) || (limb.status == BODYPART_ROBOTIC))
+ replace_limb(zone, limb)
+ return
+
+ if(owner.getToxLoss() > 40)
+ replace_blood()
+ return
+ var/tox_amount = 0
+ for(var/datum/reagent/toxin/T in owner.reagents.reagent_list)
+ tox_amount += owner.reagents.get_reagent_amount(T.type)
+ if(tox_amount > 10)
+ replace_blood()
+ return
+ if(owner.blood_volume < BLOOD_VOLUME_OKAY)
+ owner.blood_volume = BLOOD_VOLUME_NORMAL
+ to_chat(owner, "You feel your blood pulsing within you.")
+ return
+
+ var/obj/item/bodypart/chest/chest = owner.get_bodypart(BODY_ZONE_CHEST)
+ if((chest.get_damage() >= (chest.max_damage / 4)) || (chest.status == BODYPART_ROBOTIC))
+ replace_chest(chest)
+ return
+
+/obj/item/organ/heart/gland/heal/proc/reject_implant(obj/item/organ/cyberimp/implant)
+ owner.visible_message("[owner] vomits up his [implant.name]!", "You suddenly vomit up your [implant.name]!")
+ owner.vomit(0, TRUE, TRUE, 1, FALSE, FALSE, FALSE, TRUE)
+ implant.Remove(owner)
+ implant.forceMove(owner.drop_location())
+
+/obj/item/organ/heart/gland/heal/proc/replace_liver(obj/item/organ/liver/liver)
+ if(liver)
+ owner.visible_message("[owner] vomits up his [liver.name]!", "You suddenly vomit up your [liver.name]!")
+ owner.vomit(0, TRUE, TRUE, 1, FALSE, FALSE, FALSE, TRUE)
+ liver.Remove(owner)
+ liver.forceMove(owner.drop_location())
+ else
+ to_chat(owner, "You feel a weird rumble in your bowels...")
+
+ var/liver_type = /obj/item/organ/liver
+ if(owner?.dna?.species?.mutantliver)
+ liver_type = owner.dna.species.mutantliver
+ var/obj/item/organ/liver/new_liver = new liver_type()
+ new_liver.Insert(owner)
+
+/obj/item/organ/heart/gland/heal/proc/replace_lungs(obj/item/organ/lungs/lungs)
+ if(lungs)
+ owner.visible_message("[owner] vomits up his [lungs.name]!", "You suddenly vomit up your [lungs.name]!")
+ owner.vomit(0, TRUE, TRUE, 1, FALSE, FALSE, FALSE, TRUE)
+ lungs.Remove(owner)
+ lungs.forceMove(owner.drop_location())
+ else
+ to_chat(owner, "You feel a weird rumble inside your chest...")
+
+ var/lung_type = /obj/item/organ/lungs
+ if(owner.dna.species && owner.dna.species.mutantlungs)
+ lung_type = owner.dna.species.mutantlungs
+ var/obj/item/organ/lungs/new_lungs = new lung_type()
+ new_lungs.Insert(owner)
+
+/obj/item/organ/heart/gland/heal/proc/replace_eyes(obj/item/organ/eyes/eyes)
+ if(eyes)
+ owner.visible_message("[owner]'s [eyes.name] fall out of their sockets!", "Your [eyes.name] fall out of their sockets!")
+ playsound(owner, 'sound/effects/splat.ogg', 50, TRUE)
+ eyes.Remove(owner)
+ eyes.forceMove(owner.drop_location())
+ else
+ to_chat(owner, "You feel a weird rumble behind your eye sockets...")
+
+ addtimer(CALLBACK(src, .proc/finish_replace_eyes), rand(100, 200))
+
+/obj/item/organ/heart/gland/heal/proc/finish_replace_eyes()
+ var/eye_type = /obj/item/organ/eyes
+ if(owner.dna.species && owner.dna.species.mutanteyes)
+ eye_type = owner.dna.species.mutanteyes
+ var/obj/item/organ/eyes/new_eyes = new eye_type()
+ new_eyes.Insert(owner)
+ owner.visible_message("A pair of new eyes suddenly inflates into [owner]'s eye sockets!", "A pair of new eyes suddenly inflates into your eye sockets!")
+
+/obj/item/organ/heart/gland/heal/proc/replace_limb(body_zone, obj/item/bodypart/limb)
+ if(limb)
+ owner.visible_message("[owner]'s [limb.name] suddenly detaches from [owner.p_their()] body!", "Your [limb.name] suddenly detaches from your body!")
+ playsound(owner, "desceration", 50, TRUE, -1)
+ limb.drop_limb()
+ else
+ to_chat(owner, "You feel a weird tingle in your [parse_zone(body_zone)]... even if you don't have one.")
+
+ addtimer(CALLBACK(src, .proc/finish_replace_limb, body_zone), rand(150, 300))
+
+/obj/item/organ/heart/gland/heal/proc/finish_replace_limb(body_zone)
+ owner.visible_message("With a loud snap, [owner]'s [parse_zone(body_zone)] rapidly grows back from [owner.p_their()] body!",
+ "With a loud snap, your [parse_zone(body_zone)] rapidly grows back from your body!",
+ "Your hear a loud snap.")
+ playsound(owner, 'sound/magic/demon_consume.ogg', 50, TRUE)
+ owner.regenerate_limb(body_zone)
+
+/obj/item/organ/heart/gland/heal/proc/replace_blood()
+ owner.visible_message("[owner] starts vomiting huge amounts of blood!", "You suddenly start vomiting huge amounts of blood!")
+ keep_replacing_blood()
+
+/obj/item/organ/heart/gland/heal/proc/keep_replacing_blood()
+ var/keep_going = FALSE
+ owner.vomit(0, TRUE, FALSE, 3, FALSE, FALSE, FALSE, TRUE)
+ owner.Stun(15)
+ owner.adjustToxLoss(-15, TRUE, TRUE)
+
+ owner.blood_volume = min(BLOOD_VOLUME_NORMAL, owner.blood_volume + 20)
+ if(owner.blood_volume < BLOOD_VOLUME_NORMAL)
+ keep_going = TRUE
+
+ if(owner.getToxLoss())
+ keep_going = TRUE
+ for(var/datum/reagent/toxin/R in owner.reagents.reagent_list)
+ owner.reagents.remove_reagent(R.type, 4)
+ if(owner.reagents.has_reagent(R.type))
+ keep_going = TRUE
+ if(keep_going)
+ addtimer(CALLBACK(src, .proc/keep_replacing_blood), 30)
+
+/obj/item/organ/heart/gland/heal/proc/replace_chest(obj/item/bodypart/chest/chest)
+ if(chest.status == BODYPART_ROBOTIC)
+ owner.visible_message("[owner]'s [chest.name] rapidly expels its mechanical components, replacing them with flesh!", "Your [chest.name] rapidly expels its mechanical components, replacing them with flesh!")
+ playsound(owner, 'sound/magic/clockwork/anima_fragment_attack.ogg', 50, TRUE)
+ var/list/dirs = GLOB.alldirs.Copy()
+ for(var/i in 1 to 3)
+ var/obj/effect/decal/cleanable/robot_debris/debris = new(get_turf(owner))
+ debris.streak(dirs)
+ else
+ owner.visible_message("[owner]'s [chest.name] sheds off its damaged flesh, rapidly replacing it!", "Your [chest.name] sheds off its damaged flesh, rapidly replacing it!")
+ playsound(owner, 'sound/effects/splat.ogg', 50, TRUE)
+ var/list/dirs = GLOB.alldirs.Copy()
+ for(var/i in 1 to 3)
+ var/obj/effect/decal/cleanable/blood/gibs/gibs = new(get_turf(owner))
+ gibs.streak(dirs)
+
+ var/obj/item/bodypart/chest/new_chest = new(null)
+ new_chest.replace_limb(owner, TRUE)
+ qdel(chest)
\ No newline at end of file
diff --git a/code/modules/antagonists/abductor/equipment/glands/mindshock.dm b/code/modules/antagonists/abductor/equipment/glands/mindshock.dm
new file mode 100644
index 0000000000..f8b91343f2
--- /dev/null
+++ b/code/modules/antagonists/abductor/equipment/glands/mindshock.dm
@@ -0,0 +1,64 @@
+/obj/item/organ/heart/gland/mindshock
+ true_name = "neural crosstalk uninhibitor"
+ cooldown_low = 400
+ cooldown_high = 700
+ uses = -1
+ icon_state = "mindshock"
+ mind_control_uses = 1
+ mind_control_duration = 6000
+ var/list/mob/living/carbon/human/broadcasted_mobs = list()
+
+/obj/item/organ/heart/gland/mindshock/activate()
+ to_chat(owner, "You get a headache.")
+
+ var/turf/T = get_turf(owner)
+ for(var/mob/living/carbon/H in orange(4,T))
+ if(H == owner)
+ continue
+ switch(pick(1,3))
+ if(1)
+ to_chat(H, "You hear a loud buzz in your head, silencing your thoughts!")
+ H.Stun(50)
+ if(2)
+ to_chat(H, "You hear an annoying buzz in your head.")
+ H.confused += 15
+ H.adjustOrganLoss(ORGAN_SLOT_BRAIN, 10, 160)
+ if(3)
+ H.hallucination += 60
+
+/obj/item/organ/heart/gland/mindshock/mind_control(command, mob/living/user)
+ if(!ownerCheck() || !mind_control_uses || active_mind_control)
+ return FALSE
+ mind_control_uses--
+ for(var/mob/M in oview(7, owner))
+ if(!ishuman(M))
+ continue
+ var/mob/living/carbon/human/H = M
+ if(H.stat)
+ continue
+
+ broadcasted_mobs += H
+ to_chat(H, "You suddenly feel an irresistible compulsion to follow an order...")
+ to_chat(H, "[command]")
+
+ message_admins("[key_name(user)] broadcasted an abductor mind control message from [key_name(owner)] to [key_name(H)]: [command]")
+
+ var/obj/screen/alert/mind_control/mind_alert = H.throw_alert("mind_control", /obj/screen/alert/mind_control)
+ mind_alert.command = command
+
+ if(LAZYLEN(broadcasted_mobs))
+ active_mind_control = TRUE
+ addtimer(CALLBACK(src, .proc/clear_mind_control), mind_control_duration)
+
+ update_gland_hud()
+ return TRUE
+
+/obj/item/organ/heart/gland/mindshock/clear_mind_control()
+ if(!active_mind_control || !LAZYLEN(broadcasted_mobs))
+ return FALSE
+ for(var/M in broadcasted_mobs)
+ var/mob/living/carbon/human/H = M
+ to_chat(H, "You feel the compulsion fade, and you completely forget about your previous orders.")
+ H.clear_alert("mind_control")
+ active_mind_control = FALSE
+ return TRUE
\ No newline at end of file
diff --git a/code/modules/antagonists/abductor/equipment/glands/plasma.dm b/code/modules/antagonists/abductor/equipment/glands/plasma.dm
new file mode 100644
index 0000000000..4a30d99d44
--- /dev/null
+++ b/code/modules/antagonists/abductor/equipment/glands/plasma.dm
@@ -0,0 +1,22 @@
+/obj/item/organ/heart/gland/plasma
+ true_name = "effluvium sanguine-synonym emitter"
+ cooldown_low = 1200
+ cooldown_high = 1800
+ icon_state = "slime"
+ uses = -1
+ mind_control_uses = 1
+ mind_control_duration = 800
+
+/obj/item/organ/heart/gland/plasma/activate()
+ to_chat(owner, "You feel bloated.")
+ addtimer(CALLBACK(GLOBAL_PROC, .proc/to_chat, owner, "A massive stomachache overcomes you."), 150)
+ addtimer(CALLBACK(src, .proc/vomit_plasma), 200)
+
+/obj/item/organ/heart/gland/plasma/proc/vomit_plasma()
+ if(!owner)
+ return
+ owner.visible_message("[owner] vomits a cloud of plasma!")
+ var/turf/open/T = get_turf(owner)
+ if(istype(T))
+ T.atmos_spawn_air("plasma=50;TEMP=[T20C]")
+ owner.vomit()
\ No newline at end of file
diff --git a/code/modules/antagonists/abductor/equipment/glands/quantum.dm b/code/modules/antagonists/abductor/equipment/glands/quantum.dm
new file mode 100644
index 0000000000..a5b8815437
--- /dev/null
+++ b/code/modules/antagonists/abductor/equipment/glands/quantum.dm
@@ -0,0 +1,47 @@
+/obj/item/organ/heart/gland/quantum
+ true_name = "quantic de-observation matrix"
+ cooldown_low = 150
+ cooldown_high = 150
+ uses = -1
+ icon_state = "emp"
+ mind_control_uses = 2
+ mind_control_duration = 1200
+ var/mob/living/carbon/entangled_mob
+
+/obj/item/organ/heart/gland/quantum/activate()
+ if(entangled_mob)
+ return
+ for(var/mob/M in oview(owner, 7))
+ if(!iscarbon(M))
+ continue
+ entangled_mob = M
+ addtimer(CALLBACK(src, .proc/quantum_swap), rand(600, 2400))
+ return
+
+/obj/item/organ/heart/gland/quantum/proc/quantum_swap()
+ if(QDELETED(entangled_mob))
+ entangled_mob = null
+ return
+ var/turf/T = get_turf(owner)
+ do_teleport(owner, get_turf(entangled_mob),null,TRUE,channel = TELEPORT_CHANNEL_QUANTUM)
+ do_teleport(entangled_mob, T,null,TRUE,channel = TELEPORT_CHANNEL_QUANTUM)
+ to_chat(owner, "You suddenly find yourself somewhere else!")
+ to_chat(entangled_mob, "You suddenly find yourself somewhere else!")
+ if(!active_mind_control) //Do not reset entangled mob while mind control is active
+ entangled_mob = null
+
+/obj/item/organ/heart/gland/quantum/mind_control(command, mob/living/user)
+ if(..())
+ if(entangled_mob && ishuman(entangled_mob) && (entangled_mob.stat < DEAD))
+ to_chat(entangled_mob, "You suddenly feel an irresistible compulsion to follow an order...")
+ to_chat(entangled_mob, "[command]")
+ var/obj/screen/alert/mind_control/mind_alert = entangled_mob.throw_alert("mind_control", /obj/screen/alert/mind_control)
+ mind_alert.command = command
+ message_admins("[key_name(owner)] mirrored an abductor mind control message to [key_name(entangled_mob)]: [command]")
+ update_gland_hud()
+
+/obj/item/organ/heart/gland/quantum/clear_mind_control()
+ if(active_mind_control)
+ to_chat(entangled_mob, "You feel the compulsion fade, and you completely forget about your previous orders.")
+ entangled_mob.clear_alert("mind_control")
+ ..()
\ No newline at end of file
diff --git a/code/modules/antagonists/abductor/equipment/glands/slime.dm b/code/modules/antagonists/abductor/equipment/glands/slime.dm
new file mode 100644
index 0000000000..53b8133528
--- /dev/null
+++ b/code/modules/antagonists/abductor/equipment/glands/slime.dm
@@ -0,0 +1,21 @@
+/obj/item/organ/heart/gland/slime
+ true_name = "gastric animation galvanizer"
+ cooldown_low = 600
+ cooldown_high = 1200
+ uses = -1
+ icon_state = "slime"
+ mind_control_uses = 1
+ mind_control_duration = 2400
+
+/obj/item/organ/heart/gland/slime/Insert(mob/living/carbon/M, special = 0, drop_if_replaced = TRUE)
+ ..()
+ owner.faction |= "slime"
+ owner.grant_language(/datum/language/slime)
+
+/obj/item/organ/heart/gland/slime/activate()
+ to_chat(owner, "You feel nauseated!")
+ owner.vomit(20)
+
+ var/mob/living/simple_animal/slime/Slime = new(get_turf(owner), "grey")
+ Slime.Friends = list(owner)
+ Slime.Leader = owner
\ No newline at end of file
diff --git a/code/modules/antagonists/abductor/equipment/glands/spider.dm b/code/modules/antagonists/abductor/equipment/glands/spider.dm
new file mode 100644
index 0000000000..f0421b23b2
--- /dev/null
+++ b/code/modules/antagonists/abductor/equipment/glands/spider.dm
@@ -0,0 +1,14 @@
+/obj/item/organ/heart/gland/spiderman
+ true_name = "araneae cloister accelerator"
+ cooldown_low = 450
+ cooldown_high = 900
+ uses = -1
+ icon_state = "spider"
+ mind_control_uses = 2
+ mind_control_duration = 2400
+
+/obj/item/organ/heart/gland/spiderman/activate()
+ to_chat(owner, "You feel something crawling in your skin.")
+ owner.faction |= "spiders"
+ var/obj/structure/spider/spiderling/S = new(owner.drop_location())
+ S.directive = "Protect your nest inside [owner.real_name]."
\ No newline at end of file
diff --git a/code/modules/antagonists/abductor/equipment/glands/transform.dm b/code/modules/antagonists/abductor/equipment/glands/transform.dm
new file mode 100644
index 0000000000..05c4760d37
--- /dev/null
+++ b/code/modules/antagonists/abductor/equipment/glands/transform.dm
@@ -0,0 +1,15 @@
+/obj/item/organ/heart/gland/transform
+ true_name = "anthropmorphic transmorphosizer"
+ cooldown_low = 900
+ cooldown_high = 1800
+ uses = -1
+ human_only = TRUE
+ icon_state = "species"
+ mind_control_uses = 7
+ mind_control_duration = 300
+
+/obj/item/organ/heart/gland/transform/activate()
+ to_chat(owner, "You feel unlike yourself.")
+ randomize_human(owner)
+ var/species = pick(list(/datum/species/human, /datum/species/lizard, /datum/species/insect, /datum/species/fly))
+ owner.set_species(species)
\ No newline at end of file
diff --git a/code/modules/antagonists/abductor/equipment/glands/trauma.dm b/code/modules/antagonists/abductor/equipment/glands/trauma.dm
new file mode 100644
index 0000000000..b6280ec017
--- /dev/null
+++ b/code/modules/antagonists/abductor/equipment/glands/trauma.dm
@@ -0,0 +1,18 @@
+/obj/item/organ/heart/gland/trauma
+ true_name = "white matter randomiser"
+ cooldown_low = 800
+ cooldown_high = 1200
+ uses = 5
+ icon_state = "emp"
+ mind_control_uses = 3
+ mind_control_duration = 1800
+
+/obj/item/organ/heart/gland/trauma/activate()
+ to_chat(owner, "You feel a spike of pain in your head.")
+ if(prob(33))
+ owner.gain_trauma_type(BRAIN_TRAUMA_SPECIAL, rand(TRAUMA_RESILIENCE_BASIC, TRAUMA_RESILIENCE_LOBOTOMY))
+ else
+ if(prob(20))
+ owner.gain_trauma_type(BRAIN_TRAUMA_SEVERE, rand(TRAUMA_RESILIENCE_BASIC, TRAUMA_RESILIENCE_LOBOTOMY))
+ else
+ owner.gain_trauma_type(BRAIN_TRAUMA_MILD, rand(TRAUMA_RESILIENCE_BASIC, TRAUMA_RESILIENCE_LOBOTOMY))
\ No newline at end of file
diff --git a/code/modules/antagonists/abductor/equipment/glands/ventcrawl.dm b/code/modules/antagonists/abductor/equipment/glands/ventcrawl.dm
new file mode 100644
index 0000000000..d1ea135497
--- /dev/null
+++ b/code/modules/antagonists/abductor/equipment/glands/ventcrawl.dm
@@ -0,0 +1,12 @@
+/obj/item/organ/heart/gland/ventcrawling
+ true_name = "pliant cartilage enabler"
+ cooldown_low = 1800
+ cooldown_high = 2400
+ uses = 1
+ icon_state = "vent"
+ mind_control_uses = 4
+ mind_control_duration = 1800
+
+/obj/item/organ/heart/gland/ventcrawling/activate()
+ to_chat(owner, "You feel very stretchy.")
+ owner.ventcrawler = VENTCRAWLER_ALWAYS
\ No newline at end of file
diff --git a/code/modules/antagonists/abductor/equipment/glands/viral.dm b/code/modules/antagonists/abductor/equipment/glands/viral.dm
new file mode 100644
index 0000000000..4d4f865a7c
--- /dev/null
+++ b/code/modules/antagonists/abductor/equipment/glands/viral.dm
@@ -0,0 +1,34 @@
+/obj/item/organ/heart/gland/viral
+ true_name = "contamination incubator"
+ cooldown_low = 1800
+ cooldown_high = 2400
+ uses = 1
+ icon_state = "viral"
+ mind_control_uses = 1
+ mind_control_duration = 1800
+
+/obj/item/organ/heart/gland/viral/activate()
+ to_chat(owner, "You feel sick.")
+ var/datum/disease/advance/A = random_virus(pick(2,6),6)
+ A.carrier = TRUE
+ owner.ForceContractDisease(A, FALSE, TRUE)
+
+/obj/item/organ/heart/gland/viral/proc/random_virus(max_symptoms, max_level)
+ if(max_symptoms > VIRUS_SYMPTOM_LIMIT)
+ max_symptoms = VIRUS_SYMPTOM_LIMIT
+ var/datum/disease/advance/A = new /datum/disease/advance()
+ var/list/datum/symptom/possible_symptoms = list()
+ for(var/symptom in subtypesof(/datum/symptom))
+ var/datum/symptom/S = symptom
+ if(initial(S.level) > max_level)
+ continue
+ if(initial(S.level) <= 0) //unobtainable symptoms
+ continue
+ possible_symptoms += S
+ for(var/i in 1 to max_symptoms)
+ var/datum/symptom/chosen_symptom = pick_n_take(possible_symptoms)
+ if(chosen_symptom)
+ var/datum/symptom/S = new chosen_symptom
+ A.symptoms += S
+ A.Refresh() //just in case someone already made and named the same disease
+ return A
\ No newline at end of file
diff --git a/code/modules/antagonists/abductor/machinery/console.dm b/code/modules/antagonists/abductor/machinery/console.dm
index bcf02bda01..47294bc766 100644
--- a/code/modules/antagonists/abductor/machinery/console.dm
+++ b/code/modules/antagonists/abductor/machinery/console.dm
@@ -43,12 +43,15 @@
dat += "Collected Samples : [points]
"
dat += "Gear Credits: [credits]
"
dat += "Transfer data in exchange for supplies:
"
- dat += "Advanced Baton
"
- dat += "Agent Helmet
"
- dat += "Agent Vest
"
- dat += "Radio Silencer
"
- dat += "Science Tool
"
- dat += "Mental Interface Device
"
+ dat += "Advanced Baton (2 Credits)
"
+ dat += "Mental Interface Device (2 Credits)
"
+ dat += "Reagent Synthesizer (2 Credits)
"
+ dat += "Shrink Ray Blaster (2 Credits)
"
+ dat += "Agent Helmet (1 Credit)
"
+ dat += "Agent Vest (1 Credit)
"
+ dat += "Radio Silencer (1 Credit)
"
+ dat += "Science Tool (1 Credit)
"
+ dat += "Superlingual Matrix (1 Credit)
"
else
dat += "NO EXPERIMENT MACHINE DETECTED
"
@@ -101,7 +104,7 @@
else if(href_list["dispense"])
switch(href_list["dispense"])
if("baton")
- Dispense(/obj/item/abductor_baton,cost=2)
+ Dispense(/obj/item/abductor/baton,cost=2)
if("helmet")
Dispense(/obj/item/clothing/head/helmet/abductor)
if("silencer")
@@ -112,6 +115,12 @@
Dispense(/obj/item/clothing/suit/armor/abductor/vest)
if("mind_device")
Dispense(/obj/item/abductor/mind_device,cost=2)
+ if("chem_dispenser")
+ Dispense(/obj/item/abductor_machine_beacon/chem_dispenser,cost=2)
+ if("tongue")
+ Dispense(/obj/item/organ/tongue/abductor)
+ if("shrink_ray")
+ Dispense(/obj/item/gun/energy/shrink_ray,cost=2)
updateUsrDialog()
/obj/machinery/abductor/console/proc/TeleporterRetrieve()
@@ -136,9 +145,9 @@
var/entry_name
if(remote)
- entry_name = show_radial_menu(usr, camera.eyeobj, disguises2)
+ entry_name = show_radial_menu(usr, camera.eyeobj, disguises2, tooltips = TRUE)
else
- entry_name = show_radial_menu(usr, src, disguises2)
+ entry_name = show_radial_menu(usr, src, disguises2, require_near = TRUE, tooltips = TRUE)
var/datum/icon_snapshot/chosen = disguises[entry_name]
if(chosen && vest && (remote || in_range(usr,src)))
@@ -236,4 +245,4 @@
new item(drop_location)
else
- say("Insufficent data!")
+ say("Insufficent data!")
\ No newline at end of file
diff --git a/code/modules/atmospherics/machinery/datum_pipeline.dm b/code/modules/atmospherics/machinery/datum_pipeline.dm
index 38178a4339..19f3f58708 100644
--- a/code/modules/atmospherics/machinery/datum_pipeline.dm
+++ b/code/modules/atmospherics/machinery/datum_pipeline.dm
@@ -154,6 +154,11 @@
var/partial_heat_capacity = total_heat_capacity*(share_volume/air.volume)
var/target_temperature
var/target_heat_capacity
+ // first calculate heat from radiation. there's an implied "* 1 tick" here.
+ // 0.05 magic multiplicand is, first, 0.1 deciseconds; second, half of the radiation's going right back into the gas.
+ var/share_constant = STEFANBOLTZMANN*(share_volume**(2/3))*0.05
+ // Minimizing temp to 4 billion is mostly to prevent -infinity temperatures.
+ var/heat = share_constant*(min(air.temperature,4000000000)**4)
if(isopenturf(target))
@@ -165,8 +170,8 @@
if((modeled_location.heat_capacity>0) && (partial_heat_capacity>0))
var/delta_temperature = air.temperature - target_temperature
-
- var/heat = thermal_conductivity*delta_temperature* \
+ heat -= share_constant*(min(target_temperature,4000000000)**4)
+ heat += thermal_conductivity*delta_temperature* \
(partial_heat_capacity*target_heat_capacity/(partial_heat_capacity+target_heat_capacity))
air.temperature -= heat/total_heat_capacity
@@ -183,7 +188,8 @@
var/sharer_temperature_delta = 0
if((sharer_heat_capacity>0) && (partial_heat_capacity>0))
- var/heat = thermal_conductivity*delta_temperature* \
+ heat -= share_constant*(min(target_temperature,4000000000)**4)
+ heat += thermal_conductivity*delta_temperature* \
(partial_heat_capacity*sharer_heat_capacity/(partial_heat_capacity+sharer_heat_capacity))
self_temperature_delta = -heat/total_heat_capacity
@@ -199,10 +205,12 @@
if((target.heat_capacity>0) && (partial_heat_capacity>0))
var/delta_temperature = air.temperature - target.temperature
- var/heat = thermal_conductivity*delta_temperature* \
+ heat -= share_constant*(min(target.temperature,4000000000)**4)
+ heat += thermal_conductivity*delta_temperature* \
(partial_heat_capacity*target.heat_capacity/(partial_heat_capacity+target.heat_capacity))
air.temperature -= heat/total_heat_capacity
+ air.temperature = CLAMP(air.temperature,TCMB,INFINITY) // i have no idea why TCMB needs to be the min but i ain't changing it
update = TRUE
/datum/pipeline/proc/return_air()
diff --git a/code/modules/atmospherics/machinery/pipes/heat_exchange/he_pipes.dm b/code/modules/atmospherics/machinery/pipes/heat_exchange/he_pipes.dm
index b087859a47..79aa957d97 100644
--- a/code/modules/atmospherics/machinery/pipes/heat_exchange/he_pipes.dm
+++ b/code/modules/atmospherics/machinery/pipes/heat_exchange/he_pipes.dm
@@ -26,9 +26,7 @@
var/turf/T = loc
if(istype(T))
- if(islava(T))
- environment_temperature = 5000
- else if(T.blocks_air)
+ if(T.blocks_air)
environment_temperature = T.temperature
else
var/turf/open/OT = T
diff --git a/code/modules/client/preferences.dm b/code/modules/client/preferences.dm
index b35e996bc6..284d442413 100644
--- a/code/modules/client/preferences.dm
+++ b/code/modules/client/preferences.dm
@@ -14,7 +14,7 @@ GLOBAL_LIST_EMPTY(preferences_datums)
//doohickeys for savefiles
var/path
var/default_slot = 1 //Holder so it doesn't default to slot 1, rather the last one used
- var/max_save_slots = 8
+ var/max_save_slots = 16
//non-preference stuff
var/muted = 0
@@ -217,7 +217,7 @@ GLOBAL_LIST_EMPTY(preferences_datums)
load_path(C.ckey)
unlock_content = C.IsByondMember()
if(unlock_content)
- max_save_slots = 16
+ max_save_slots = 24
var/loaded_preferences_successfully = load_preferences()
if(loaded_preferences_successfully)
if(load_character())
diff --git a/code/modules/clothing/head/misc_special.dm b/code/modules/clothing/head/misc_special.dm
index fc82d038ce..127bf4c773 100644
--- a/code/modules/clothing/head/misc_special.dm
+++ b/code/modules/clothing/head/misc_special.dm
@@ -309,4 +309,4 @@
//The "pocket" for the M1 helmet so you can tuck things into the elastic band
/datum/component/storage/concrete/pockets/tiny/spacenam
- attack_hand_interact = TRUE //So you can actually see what you stuff in there
\ No newline at end of file
+ attack_hand_interact = TRUE //So you can actually see what you stuff in there
diff --git a/code/modules/clothing/spacesuits/hardsuit.dm b/code/modules/clothing/spacesuits/hardsuit.dm
index c6eac4ac56..0d22b16388 100644
--- a/code/modules/clothing/spacesuits/hardsuit.dm
+++ b/code/modules/clothing/spacesuits/hardsuit.dm
@@ -212,7 +212,7 @@
icon_state = "hardsuit0-white"
item_state = "ce_helm"
item_color = "white"
- armor = list("melee" = 40, "bullet" = 5, "laser" = 10, "energy" = 5, "bomb" = 50, "bio" = 100, "rad" = 90, "fire" = 100, "acid" = 90)
+ armor = list("melee" = 40, "bullet" = 5, "laser" = 10, "energy" = 5, "bomb" = 50, "bio" = 100, "rad" = 100, "fire" = 100, "acid" = 90)
heat_protection = HEAD
max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT
@@ -221,7 +221,7 @@
name = "advanced hardsuit"
desc = "An advanced suit that protects against hazardous, low pressure environments. Shines with a high polish."
item_state = "ce_hardsuit"
- armor = list("melee" = 40, "bullet" = 5, "laser" = 10, "energy" = 5, "bomb" = 50, "bio" = 100, "rad" = 90, "fire" = 100, "acid" = 90)
+ armor = list("melee" = 40, "bullet" = 5, "laser" = 10, "energy" = 5, "bomb" = 50, "bio" = 100, "rad" = 95, "fire" = 100, "acid" = 90)
heat_protection = CHEST|GROIN|LEGS|FEET|ARMS|HANDS
max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT
helmettype = /obj/item/clothing/head/helmet/space/hardsuit/engine/elite
diff --git a/code/modules/crafting/recipes/recipes_clothing.dm b/code/modules/crafting/recipes/recipes_clothing.dm
index 4f06a44811..8cb35df9d1 100644
--- a/code/modules/crafting/recipes/recipes_clothing.dm
+++ b/code/modules/crafting/recipes/recipes_clothing.dm
@@ -163,26 +163,7 @@
name = "Cosmic Winter Coat"
result = /obj/item/clothing/suit/hooded/wintercoat/cosmic
reqs = list(/obj/item/clothing/suit/hooded/wintercoat = 1,
- /obj/item/clothing/suit/hooded/wintercoat/captain = 1,
- /obj/item/clothing/suit/hooded/wintercoat/hop = 1,
- /obj/item/clothing/suit/hooded/wintercoat/hos = 1,
- /obj/item/clothing/suit/hooded/wintercoat/rd = 1,
- /obj/item/clothing/suit/hooded/wintercoat/ce = 1,
- /obj/item/clothing/suit/hooded/wintercoat/cmo = 1,
- /obj/item/clothing/suit/hooded/wintercoat/qm = 1,
- /obj/item/clothing/suit/hooded/wintercoat/robotics = 1,
- /obj/item/clothing/suit/hooded/wintercoat/engineering/atmos = 1,
- /obj/item/clothing/suit/hooded/wintercoat/engineering = 1,
- /obj/item/clothing/suit/hooded/wintercoat/science = 1,
- /obj/item/clothing/suit/hooded/wintercoat/genetics = 1,
- /obj/item/clothing/suit/hooded/wintercoat/chemistry = 1,
- /obj/item/clothing/suit/hooded/wintercoat/medical = 1,
- /obj/item/clothing/suit/hooded/wintercoat/viro = 1,
- /obj/item/clothing/suit/hooded/wintercoat/janitor = 1,
- /obj/item/clothing/suit/hooded/wintercoat/security = 1,
- /obj/item/clothing/suit/hooded/wintercoat/cargo = 1,
- /obj/item/clothing/suit/hooded/wintercoat/hydro = 1,
- /obj/item/clothing/suit/hooded/wintercoat/miner = 1)
+ /obj/item/bedsheet/cosmos = 1)
time = 60
always_availible = TRUE
category = CAT_CLOTHING
diff --git a/code/modules/food_and_drinks/kitchen_machinery/deep_fryer.dm b/code/modules/food_and_drinks/kitchen_machinery/deep_fryer.dm
index 3183b3b2b4..1bc2e4bb89 100644
--- a/code/modules/food_and_drinks/kitchen_machinery/deep_fryer.dm
+++ b/code/modules/food_and_drinks/kitchen_machinery/deep_fryer.dm
@@ -80,6 +80,9 @@ God bless America.
I.reagents.trans_to(src, I.reagents.total_volume)
qdel(I)
return
+ if(istype(I,/obj/item/clothing/head/mob_holder))
+ to_chat(user, "This does not fit in the fryer.") // TODO: Deepfrying instakills mobs, spawns a whole deep-fried mob.
+ return
if(!reagents.has_reagent("cooking_oil"))
to_chat(user, "[src] has no cooking oil to fry with!")
return
diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm
index 3a9495a5cb..f2746823cb 100644
--- a/code/modules/mob/living/carbon/carbon.dm
+++ b/code/modules/mob/living/carbon/carbon.dm
@@ -159,23 +159,34 @@
return
//END OF CIT CHANGES
- var/atom/movable/thrown_thing
var/obj/item/I = src.get_active_held_item()
- if(!I)
- if(pulling && isliving(pulling) && grab_state >= GRAB_AGGRESSIVE)
- var/mob/living/throwable_mob = pulling
- if(!throwable_mob.buckled)
- thrown_thing = throwable_mob
+ var/atom/movable/thrown_thing
+ var/mob/living/throwable_mob
+
+ if(istype(I, /obj/item/clothing/head/mob_holder))
+ var/obj/item/clothing/head/mob_holder/holder = I
+ if(holder.held_mob)
+ throwable_mob = holder.held_mob
+ holder.release()
+
+ if(!I || throwable_mob)
+ if(!throwable_mob && pulling && isliving(pulling) && grab_state >= GRAB_AGGRESSIVE)
+ throwable_mob = pulling
+
+ if(throwable_mob && !throwable_mob.buckled)
+ thrown_thing = throwable_mob
+ if(pulling)
stop_pulling()
- if(HAS_TRAIT(src, TRAIT_PACIFISM))
- to_chat(src, "You gently let go of [throwable_mob].")
- return
- adjustStaminaLossBuffered(25)//CIT CHANGE - throwing an entire person shall be very tiring
- var/turf/start_T = get_turf(loc) //Get the start and target tile for the descriptors
- var/turf/end_T = get_turf(target)
- if(start_T && end_T)
- log_combat(src, throwable_mob, "thrown", addition="grab from tile in [AREACOORD(start_T)] towards tile at [AREACOORD(end_T)]")
+ if(HAS_TRAIT(src, TRAIT_PACIFISM))
+ to_chat(src, "You gently let go of [throwable_mob].")
+ return
+
+ adjustStaminaLossBuffered(25)//CIT CHANGE - throwing an entire person shall be very tiring
+ var/turf/start_T = get_turf(loc) //Get the start and target tile for the descriptors
+ var/turf/end_T = get_turf(target)
+ if(start_T && end_T)
+ log_combat(src, throwable_mob, "thrown", addition="grab from tile in [AREACOORD(start_T)] towards tile at [AREACOORD(end_T)]")
else if(!CHECK_BITFIELD(I.item_flags, ABSTRACT) && !HAS_TRAIT(I, TRAIT_NODROP))
thrown_thing = I
@@ -195,6 +206,8 @@
newtonian_move(get_dir(target, src))
thrown_thing.throw_at(target, thrown_thing.throw_range, thrown_thing.throw_speed, src)
+
+
/mob/living/carbon/restrained(ignore_grab)
. = (handcuffed || (!ignore_grab && pulledby && pulledby.grab_state >= GRAB_AGGRESSIVE))
diff --git a/code/modules/mob/living/carbon/examine.dm b/code/modules/mob/living/carbon/examine.dm
index 9e87e7626d..34c268578c 100644
--- a/code/modules/mob/living/carbon/examine.dm
+++ b/code/modules/mob/living/carbon/examine.dm
@@ -15,7 +15,11 @@
if (wear_mask)
. += "[t_He] [t_is] wearing [wear_mask.get_examine_string(user)] on [t_his] face."
if (wear_neck)
- . += "[t_He] [t_is] wearing [wear_neck.get_examine_string(user)] around [t_his] neck."
+ . += "[t_He] [t_is] wearing [wear_neck.get_examine_string(user)] around [t_his] neck.\n"
+ if(can_be_held)
+ . += "[t_He] looks small enough to be picked up with Alt+Click!\n"
+
+
for(var/obj/item/I in held_items)
if(!(I.item_flags & ABSTRACT))
diff --git a/code/modules/mob/living/carbon/monkey/combat.dm b/code/modules/mob/living/carbon/monkey/combat.dm
index 25bc243f07..92ad87f6b5 100644
--- a/code/modules/mob/living/carbon/monkey/combat.dm
+++ b/code/modules/mob/living/carbon/monkey/combat.dm
@@ -132,6 +132,10 @@
pickupTarget = null
pickupTimer = 0
else if(ismob(pickupTarget.loc)) // in someones hand
+ if(istype(pickupTarget, /obj/item/clothing/head/mob_holder/))
+ var/obj/item/clothing/head/mob_holder/h = pickupTarget
+ if(h && h.held_mob==src)
+ return//dont let them pickpocket themselves
var/mob/M = pickupTarget.loc
if(!pickpocketing)
pickpocketing = TRUE
diff --git a/code/modules/mob/living/carbon/monkey/monkey.dm b/code/modules/mob/living/carbon/monkey/monkey.dm
index 025c1a2f75..fe041dd0a6 100644
--- a/code/modules/mob/living/carbon/monkey/monkey.dm
+++ b/code/modules/mob/living/carbon/monkey/monkey.dm
@@ -15,6 +15,7 @@
bodyparts = list(/obj/item/bodypart/chest/monkey, /obj/item/bodypart/head/monkey, /obj/item/bodypart/l_arm/monkey,
/obj/item/bodypart/r_arm/monkey, /obj/item/bodypart/r_leg/monkey, /obj/item/bodypart/l_leg/monkey)
hud_type = /datum/hud/monkey
+ can_be_held = "monkey"
/mob/living/carbon/monkey/Initialize(mapload, cubespawned=FALSE, mob/spawner)
verbs += /mob/living/proc/mob_sleep
@@ -45,6 +46,10 @@
SSmobs.cubemonkeys -= src
return ..()
+/mob/living/carbon/monkey/generate_mob_holder()
+ var/obj/item/clothing/head/mob_holder/holder = new(get_turf(src), src, "monkey", 'icons/mob/animals_held.dmi', 'icons/mob/animals_held_lh.dmi', 'icons/mob/animals_held_rh.dmi', TRUE)
+ return holder
+
/mob/living/carbon/monkey/create_internal_organs()
internal_organs += new /obj/item/organ/appendix
internal_organs += new /obj/item/organ/lungs
diff --git a/code/modules/mob/living/inhand_holder.dm b/code/modules/mob/living/inhand_holder.dm
index 420c59b22d..e57e5e2936 100644
--- a/code/modules/mob/living/inhand_holder.dm
+++ b/code/modules/mob/living/inhand_holder.dm
@@ -5,80 +5,126 @@
desc = "Yell at coderbrush."
icon = null
icon_state = ""
- item_flags = DROPDEL
var/mob/living/held_mob
- var/can_head = TRUE
- var/destroying = FALSE
+ var/can_head = FALSE
+ w_class = WEIGHT_CLASS_BULKY
-/obj/item/clothing/head/mob_holder/Initialize(mapload, mob/living/M, _worn_state, head_icon, lh_icon, rh_icon, _can_head = TRUE)
+/obj/item/clothing/head/mob_holder/Initialize(mapload, mob/living/M, _worn_state, alt_worn, lh_icon, rh_icon, _can_head_override = FALSE)
. = ..()
- can_head = _can_head
- if(head_icon)
- alternate_worn_icon = head_icon
+
+ if(M)
+ M.setDir(SOUTH)
+ held_mob = M
+ M.forceMove(src)
+ appearance = M.appearance
+ name = M.name
+ desc = M.desc
+
+ if(_can_head_override)
+ can_head = _can_head_override
+ if(alt_worn)
+ alternate_worn_icon = alt_worn
if(_worn_state)
item_state = _worn_state
+ icon_state = _worn_state
if(lh_icon)
lefthand_file = lh_icon
if(rh_icon)
righthand_file = rh_icon
if(!can_head)
slot_flags = NONE
- deposit(M)
/obj/item/clothing/head/mob_holder/Destroy()
- destroying = TRUE
if(held_mob)
- release(FALSE)
+ release()
return ..()
-/obj/item/clothing/head/mob_holder/proc/deposit(mob/living/L)
- if(!istype(L))
- return FALSE
- L.setDir(SOUTH)
- update_visuals(L)
- held_mob = L
- L.forceMove(src)
- name = L.name
- desc = L.desc
- return TRUE
+/obj/item/clothing/head/mob_holder/dropped()
+ ..()
+ if(isturf(loc))//don't release on soft-drops
+ release()
-/obj/item/clothing/head/mob_holder/proc/update_visuals(mob/living/L)
- appearance = L.appearance
-
-/obj/item/clothing/head/mob_holder/proc/release(del_on_release = TRUE)
- if(!held_mob)
- if(del_on_release && !destroying)
- qdel(src)
- return FALSE
+/obj/item/clothing/head/mob_holder/proc/release()
if(isliving(loc))
var/mob/living/L = loc
- to_chat(L, "[held_mob] wriggles free!")
L.dropItemToGround(src)
- held_mob.forceMove(get_turf(held_mob))
- held_mob.reset_perspective()
- held_mob.setDir(SOUTH)
- held_mob.visible_message("[held_mob] uncurls!")
- held_mob = null
- if(del_on_release && !destroying)
- qdel(src)
- return TRUE
+ if(held_mob)
+ var/mob/living/m = held_mob
+ m.forceMove(get_turf(m))
+ m.reset_perspective()
+ m.setDir(SOUTH)
+ held_mob = null
+ qdel(src)
/obj/item/clothing/head/mob_holder/relaymove(mob/user)
- release()
+ return
/obj/item/clothing/head/mob_holder/container_resist()
+ if(isliving(loc))
+ var/mob/living/L = loc
+ visible_message("[src] escapes [L]!")
release()
-/obj/item/clothing/head/mob_holder/drone/deposit(mob/living/L)
- . = ..()
- if(!isdrone(L))
- qdel(src)
- name = "drone (hiding)"
- desc = "This drone is scared and has curled up into a ball!"
+/mob/living/proc/mob_pickup(mob/living/L)
+ var/obj/item/clothing/head/mob_holder/holder = generate_mob_holder()
+ if(!holder)
+ return
+ drop_all_held_items()
+ L.put_in_hands(holder)
+ return
-/obj/item/clothing/head/mob_holder/drone/update_visuals(mob/living/L)
- var/mob/living/simple_animal/drone/D = L
- if(!D)
- return ..()
- icon = 'icons/mob/drone.dmi'
- icon_state = "[D.visualAppearence]_hat"
+/mob/living/proc/mob_try_pickup(mob/living/user)
+ if(!ishuman(user) || !src.Adjacent(user) || user.incapacitated() || !can_be_held)
+ return FALSE
+ if(user.get_active_held_item())
+ to_chat(user, "Your hands are full!")
+ return FALSE
+ if(buckled)
+ to_chat(user, "[src] is buckled to something!")
+ return FALSE
+ if(src == user)
+ to_chat(user, "You can't pick yourself up.")
+ return FALSE
+ visible_message("[user] starts picking up [src].", \
+ "[user] starts picking you up!")
+ if(!do_after(user, 20, target = src))
+ return FALSE
+
+ if(user.get_active_held_item()||buckled)
+ return FALSE
+
+ visible_message("[user] picks up [src]!", \
+ "[user] picks you up!")
+ to_chat(user, "You pick [src] up.")
+ mob_pickup(user)
+ return TRUE
+
+/mob/living/AltClick(mob/user)
+ . = ..()
+ if(mob_try_pickup(user))
+ return TRUE
+
+
+// I didn't define these for mobs, because you shouldn't be able to breathe out of mobs and using their loc isn't always the logical thing to do.
+
+/obj/item/clothing/head/mob_holder/assume_air(datum/gas_mixture/env)
+ var/atom/location = loc
+ if(!loc)
+ return //null
+ var/turf/T = get_turf(loc)
+ while(location != T)
+ location = location.loc
+ if(ismob(location))
+ return location.loc.assume_air(env)
+ return loc.assume_air(env)
+
+/obj/item/clothing/head/mob_holder/remove_air(amount)
+ var/atom/location = loc
+ if(!loc)
+ return //null
+ var/turf/T = get_turf(loc)
+ while(location != T)
+ location = location.loc
+ if(ismob(location))
+ return location.loc.remove_air(amount)
+ return loc.remove_air(amount)
diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm
index 5299618177..78542ab052 100644
--- a/code/modules/mob/living/living.dm
+++ b/code/modules/mob/living/living.dm
@@ -40,6 +40,11 @@
QDEL_LIST(diseases)
return ..()
+
+/mob/living/proc/generate_mob_holder()
+ var/obj/item/clothing/head/mob_holder/holder = new(get_turf(src), src, (istext(can_be_held) ? can_be_held : ""), 'icons/mob/animals_held.dmi', 'icons/mob/animals_held_lh.dmi', 'icons/mob/animals_held_rh.dmi')
+ return holder
+
/mob/living/onZImpact(turf/T, levels)
if(!isgroundlessturf(T))
ZImpactDamage(T, levels)
@@ -50,6 +55,7 @@
adjustBruteLoss((levels * 5) ** 1.5)
Knockdown(levels * 50)
+
/mob/living/proc/OpenCraftingMenu()
return
@@ -1153,24 +1159,6 @@
if(can_be_held)
mob_try_pickup(over)
-/mob/living/proc/mob_pickup(mob/living/L)
- return
-
-/mob/living/proc/mob_try_pickup(mob/living/user)
- if(!ishuman(user))
- return
- if(user.get_active_held_item())
- to_chat(user, "Your hands are full!")
- return FALSE
- if(buckled)
- to_chat(user, "[src] is buckled to something!")
- return FALSE
- user.visible_message("[user] starts trying to scoop up [src]!")
- if(!do_after(user, 20, target = src))
- return FALSE
- mob_pickup(user)
- return TRUE
-
/mob/living/proc/get_static_viruses() //used when creating blood and other infective objects
if(!LAZYLEN(diseases))
return
diff --git a/code/modules/mob/living/simple_animal/friendly/cat.dm b/code/modules/mob/living/simple_animal/friendly/cat.dm
index 73fd2b5569..3cbf036270 100644
--- a/code/modules/mob/living/simple_animal/friendly/cat.dm
+++ b/code/modules/mob/living/simple_animal/friendly/cat.dm
@@ -31,7 +31,7 @@
var/mob/living/simple_animal/mouse/movement_target
gold_core_spawnable = FRIENDLY_SPAWN
collar_type = "cat"
-
+ can_be_held = "cat2"
do_footstep = TRUE
/mob/living/simple_animal/pet/cat/Initialize()
@@ -80,6 +80,7 @@
pass_flags = PASSMOB
mob_size = MOB_SIZE_SMALL
collar_type = "kitten"
+ can_be_held = "cat"
//RUNTIME IS ALIVE! SQUEEEEEEEE~
/mob/living/simple_animal/pet/cat/Runtime
@@ -262,6 +263,7 @@
attacked_sound = 'sound/items/eatfood.ogg'
deathmessage = "loses its false life and collapses!"
death_sound = "bodyfall"
+ can_be_held = "cak"
/mob/living/simple_animal/pet/cat/cak/CheckParts(list/parts)
..()
diff --git a/code/modules/mob/living/simple_animal/friendly/dog.dm b/code/modules/mob/living/simple_animal/friendly/dog.dm
index eb7129181d..0d933d171b 100644
--- a/code/modules/mob/living/simple_animal/friendly/dog.dm
+++ b/code/modules/mob/living/simple_animal/friendly/dog.dm
@@ -15,7 +15,7 @@
turns_per_move = 10
do_footstep = TRUE
-
+ can_be_held = TRUE
//Corgis and pugs are now under one dog subtype
/mob/living/simple_animal/pet/dog/corgi
@@ -35,6 +35,7 @@
var/obj/item/inventory_back
var/shaved = FALSE
var/nofur = FALSE //Corgis that have risen past the material plane of existence.
+ can_be_held = "corgi"
/mob/living/simple_animal/pet/dog/corgi/Destroy()
QDEL_NULL(inventory_head)
@@ -63,6 +64,7 @@
butcher_results = list(/obj/item/reagent_containers/food/snacks/meat/slab/pug = 3)
gold_core_spawnable = FRIENDLY_SPAWN
collar_type = "pug"
+ can_be_held = "pug"
/mob/living/simple_animal/pet/dog/corgi/exoticcorgi
name = "Exotic Corgi"
@@ -360,6 +362,7 @@
icon_dead = "old_corgi_dead"
desc = "At a ripe old age of [record_age] Ian's not as spry as he used to be, but he'll always be the HoP's beloved corgi." //RIP
turns_per_move = 20
+ can_be_held = "old_corgi"
/mob/living/simple_animal/pet/dog/corgi/Ian/Life()
if(!stat && SSticker.current_state == GAME_STATE_FINISHED && !memory_saved)
@@ -582,6 +585,7 @@
unsuitable_atmos_damage = 0
minbodytemp = TCMB
maxbodytemp = T0C + 40
+ can_be_held = "void_puppy"
/mob/living/simple_animal/pet/dog/corgi/puppy/void/Process_Spacemove(movement_dir = 0)
return 1 //Void puppies can navigate space.
@@ -603,6 +607,7 @@
response_harm = "kicks"
var/turns_since_scan = 0
var/puppies = 0
+ can_be_held = "lisa"
//Lisa already has a cute bow!
/mob/living/simple_animal/pet/dog/corgi/Lisa/Topic(href, href_list)
diff --git a/code/modules/mob/living/simple_animal/friendly/drone/_drone.dm b/code/modules/mob/living/simple_animal/friendly/drone/_drone.dm
index 7e0e43055f..463b29772c 100644
--- a/code/modules/mob/living/simple_animal/friendly/drone/_drone.dm
+++ b/code/modules/mob/living/simple_animal/friendly/drone/_drone.dm
@@ -274,3 +274,7 @@
/mob/living/simple_animal/drone/electrocute_act(shock_damage, obj/source, siemens_coeff = 1, safety = 0, tesla_shock = 0, illusion = 0, stun = TRUE)
return 0 //So they don't die trying to fix wiring
+
+/mob/living/simple_animal/drone/generate_mob_holder()
+ var/obj/item/clothing/head/mob_holder/holder = new(get_turf(src), src, "[visualAppearence]_hat", null, null, null, TRUE)
+ return holder
diff --git a/code/modules/mob/living/simple_animal/friendly/drone/interaction.dm b/code/modules/mob/living/simple_animal/friendly/drone/interaction.dm
index 33031fd80c..e40eb585e8 100644
--- a/code/modules/mob/living/simple_animal/friendly/drone/interaction.dm
+++ b/code/modules/mob/living/simple_animal/friendly/drone/interaction.dm
@@ -29,28 +29,11 @@
if("Nothing")
return
-//ATTACK HAND IGNORING PARENT RETURN VALUE
+//picky up the drone c:
/mob/living/simple_animal/drone/attack_hand(mob/user)
- if(ishuman(user))
- if(stat == DEAD || status_flags & GODMODE || !can_be_held)
- ..()
- return
- if(user.get_active_held_item())
- to_chat(user, "Your hands are full!")
- return
- visible_message("[user] starts picking up [src].", \
- "[user] starts picking you up!")
- if(!do_after(user, 20, target = src))
- return
- visible_message("[user] picks up [src]!", \
- "[user] picks you up!")
- if(buckled)
- to_chat(user, "[src] is buckled to [buckled] and cannot be picked up!")
- return
- to_chat(user, "You pick [src] up.")
- drop_all_held_items()
- var/obj/item/clothing/head/mob_holder/drone/DH = new(get_turf(src), src)
- user.put_in_hands(DH)
+ ..()
+ if(user.a_intent == INTENT_HELP)
+ mob_try_pickup(user)
/mob/living/simple_animal/drone/proc/try_reactivate(mob/living/user)
var/mob/dead/observer/G = get_ghost()
diff --git a/code/modules/mob/living/simple_animal/friendly/fox.dm b/code/modules/mob/living/simple_animal/friendly/fox.dm
index 28b66c26ee..3f58ca593e 100644
--- a/code/modules/mob/living/simple_animal/friendly/fox.dm
+++ b/code/modules/mob/living/simple_animal/friendly/fox.dm
@@ -18,7 +18,7 @@
response_disarm = "gently pushes aside"
response_harm = "kicks"
gold_core_spawnable = FRIENDLY_SPAWN
-
+ can_be_held = "fox"
do_footstep = TRUE
//Captain fox
diff --git a/code/modules/mob/living/simple_animal/friendly/lizard.dm b/code/modules/mob/living/simple_animal/friendly/lizard.dm
index 6b179ba857..1a9a4ff4d7 100644
--- a/code/modules/mob/living/simple_animal/friendly/lizard.dm
+++ b/code/modules/mob/living/simple_animal/friendly/lizard.dm
@@ -23,6 +23,7 @@
obj_damage = 0
environment_smash = ENVIRONMENT_SMASH_NONE
var/static/list/edibles = typecacheof(list(/mob/living/simple_animal/butterfly, /mob/living/simple_animal/cockroach)) //list of atoms, however turfs won't affect AI, but will affect consumption.
+ can_be_held = "lizard" //you can hold lizards now.
/mob/living/simple_animal/hostile/lizard/CanAttack(atom/the_target)//Can we actually attack a possible target?
if(see_invisible < the_target.invisibility)//Target's invisible to us, forget it
@@ -39,3 +40,7 @@
return TRUE
else
return ..()
+
+/mob/living/simple_animal/hostile/lizard/generate_mob_holder()
+ var/obj/item/clothing/head/mob_holder/holder = new(get_turf(src), src, "lizard", 'icons/mob/animals_held.dmi', 'icons/mob/animals_held_lh.dmi', 'icons/mob/animals_held_rh.dmi', TRUE)
+ return holder
diff --git a/code/modules/mob/living/simple_animal/friendly/mouse.dm b/code/modules/mob/living/simple_animal/friendly/mouse.dm
index 5b962b2dd8..fec75e4a36 100644
--- a/code/modules/mob/living/simple_animal/friendly/mouse.dm
+++ b/code/modules/mob/living/simple_animal/friendly/mouse.dm
@@ -26,6 +26,7 @@
var/body_color //brown, gray and white, leave blank for random
gold_core_spawnable = FRIENDLY_SPAWN
var/chew_probability = 1
+ can_be_held = TRUE
/mob/living/simple_animal/mouse/Initialize()
. = ..()
@@ -35,7 +36,7 @@
icon_state = "mouse_[body_color]"
icon_living = "mouse_[body_color]"
icon_dead = "mouse_[body_color]_dead"
-
+ can_be_held = "mouse_[body_color]"
/mob/living/simple_animal/mouse/proc/splat()
src.health = 0
@@ -88,14 +89,17 @@
/mob/living/simple_animal/mouse/white
body_color = "white"
icon_state = "mouse_white"
+ can_be_held = "mouse_white"
/mob/living/simple_animal/mouse/gray
body_color = "gray"
icon_state = "mouse_gray"
+ can_be_held = "mouse_gray"
/mob/living/simple_animal/mouse/brown
body_color = "brown"
icon_state = "mouse_brown"
+ can_be_held = "mouse_brown"
//TOM IS ALIVE! SQUEEEEEEEE~K :)
/mob/living/simple_animal/mouse/brown/Tom
@@ -119,3 +123,8 @@
/obj/item/reagent_containers/food/snacks/deadmouse/on_grind()
reagents.clear_reagents()
+
+/mob/living/simple_animal/mouse/generate_mob_holder()
+ var/obj/item/clothing/head/mob_holder/holder = new(get_turf(src), src, (istext(can_be_held) ? can_be_held : ""), 'icons/mob/animals_held.dmi', 'icons/mob/animals_held_lh.dmi', 'icons/mob/animals_held_rh.dmi')
+ holder.w_class = WEIGHT_CLASS_TINY
+ return holder
diff --git a/code/modules/mob/living/simple_animal/friendly/sloth.dm b/code/modules/mob/living/simple_animal/friendly/sloth.dm
index 324fa107fa..175cb8e838 100644
--- a/code/modules/mob/living/simple_animal/friendly/sloth.dm
+++ b/code/modules/mob/living/simple_animal/friendly/sloth.dm
@@ -22,7 +22,7 @@
maxHealth = 50
speed = 10
glide_size = 2
-
+ can_be_held = "sloth" //finally oranges can be held
do_footstep = TRUE
diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/legion.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/legion.dm
index 5bfabe376e..49b56d0950 100644
--- a/code/modules/mob/living/simple_animal/hostile/megafauna/legion.dm
+++ b/code/modules/mob/living/simple_animal/hostile/megafauna/legion.dm
@@ -137,7 +137,7 @@ Difficulty: Medium
if(last_legion)
loot = list(/obj/item/staff/storm)
elimination = 0
- else if(prob(5))
+ else if(prob(20))
loot = list(/obj/structure/closet/crate/necropolis/tendril)
..()
diff --git a/code/modules/projectiles/ammunition/energy/special.dm b/code/modules/projectiles/ammunition/energy/special.dm
index 7b4e0bfa97..2f87872710 100644
--- a/code/modules/projectiles/ammunition/energy/special.dm
+++ b/code/modules/projectiles/ammunition/energy/special.dm
@@ -67,3 +67,8 @@
fire_sound = 'sound/weapons/emitter.ogg'
e_cost = 2000 //20,000 is in the cell making this 10 shots before reload
projectile_type = /obj/item/projectile/beam/emitter
+
+/obj/item/ammo_casing/energy/shrink
+ projectile_type = /obj/item/projectile/beam/shrink
+ select_name = "shrink ray"
+ e_cost = 200
\ No newline at end of file
diff --git a/code/modules/projectiles/projectile/beams.dm b/code/modules/projectiles/projectile/beams.dm
index ceb9b7a0fc..be73e22e6d 100644
--- a/code/modules/projectiles/projectile/beams.dm
+++ b/code/modules/projectiles/projectile/beams.dm
@@ -190,3 +190,21 @@
var/mob/living/carbon/M = target
M.visible_message("[M] explodes into a shower of gibs!")
M.gib()
+
+//a shrink ray that shrinks stuff, which grows back after a short while.
+/obj/item/projectile/beam/shrink
+ name = "shrink ray"
+ icon_state = "blue_laser"
+ hitsound = 'sound/weapons/shrink_hit.ogg'
+ damage = 0
+ damage_type = STAMINA
+ flag = "energy"
+ impact_effect_type = /obj/effect/temp_visual/impact_effect/shrink
+ light_color = LIGHT_COLOR_BLUE
+ var/shrink_time = 90
+
+/obj/item/projectile/beam/shrink/on_hit(atom/target, blocked = FALSE)
+ . = ..()
+ if(isopenturf(target) || istype(target, /turf/closed/indestructible))//shrunk floors wouldnt do anything except look weird, i-walls shouldnt be bypassable
+ return
+ target.AddComponent(/datum/component/shrink, shrink_time)
\ No newline at end of file
diff --git a/code/modules/projectiles/projectile/energy/stun.dm b/code/modules/projectiles/projectile/energy/stun.dm
index d7c8b8b082..a3c139bcff 100644
--- a/code/modules/projectiles/projectile/energy/stun.dm
+++ b/code/modules/projectiles/projectile/energy/stun.dm
@@ -23,6 +23,7 @@
var/mob/living/carbon/C = target
SEND_SIGNAL(C, COMSIG_ADD_MOOD_EVENT, "tased", /datum/mood_event/tased)
SEND_SIGNAL(C, COMSIG_LIVING_MINOR_SHOCK)
+ C.IgniteMob()
if(C.dna && C.dna.check_mutation(HULK))
C.say(pick(";RAAAAAAAARGH!", ";HNNNNNNNNNGGGGGGH!", ";GWAAAAAAAARRRHHH!", "NNNNNNNNGGGGGGGGHH!", ";AAAAAAARRRGH!" ), forced = "hulk")
else if((C.status_flags & CANKNOCKDOWN) && !HAS_TRAIT(C, TRAIT_STUNIMMUNE))
diff --git a/code/modules/reagents/chemistry/machinery/chem_dispenser.dm b/code/modules/reagents/chemistry/machinery/chem_dispenser.dm
index c6aa51deda..2c5d7aa3e2 100644
--- a/code/modules/reagents/chemistry/machinery/chem_dispenser.dm
+++ b/code/modules/reagents/chemistry/machinery/chem_dispenser.dm
@@ -656,3 +656,67 @@
component_parts += new /obj/item/stack/sheet/glass(null)
component_parts += new /obj/item/stock_parts/cell/bluespace(null)
RefreshParts()
+
+/obj/machinery/chem_dispenser/abductor
+ name = "reagent synthesizer"
+ desc = "Synthesizes a variety of reagents using proto-matter."
+ icon = 'icons/obj/abductor.dmi'
+ icon_state = "chem_dispenser"
+ has_panel_overlay = FALSE
+ circuit = /obj/item/circuitboard/machine/chem_dispenser/abductor
+ working_state = null
+ nopower_state = null
+ dispensable_reagents = list(
+ "hydrogen",
+ "lithium",
+ "carbon",
+ "nitrogen",
+ "oxygen",
+ "fluorine",
+ "sodium",
+ "aluminium",
+ "silicon",
+ "phosphorus",
+ "sulfur",
+ "chlorine",
+ "potassium",
+ "iron",
+ "copper",
+ "mercury",
+ "radium",
+ "water",
+ "ethanol",
+ "sugar",
+ "sacid",
+ "welding_fuel",
+ "silver",
+ "iodine",
+ "bromine",
+ "stable_plasma",
+ "oil",
+ "ammonia",
+ "ash",
+ "acetone",
+ "phenol",
+ "diethylamine",
+ "mine_salve",
+ "toxin",
+ "space_drugs",
+ "plasma",
+ "frostoil",
+ "uranium",
+ "histamine",
+ "morphine"
+ )
+
+/obj/machinery/chem_dispenser/abductor/Initialize()
+ . = ..()
+ component_parts = list()
+ component_parts += new /obj/item/circuitboard/machine/chem_dispenser(null)
+ component_parts += new /obj/item/stock_parts/matter_bin/bluespace(null)
+ component_parts += new /obj/item/stock_parts/matter_bin/bluespace(null)
+ component_parts += new /obj/item/stock_parts/capacitor/quadratic(null)
+ component_parts += new /obj/item/stock_parts/manipulator/femto(null)
+ component_parts += new /obj/item/stack/sheet/glass(null)
+ component_parts += new /obj/item/stock_parts/cell/bluespace(null)
+ RefreshParts()
\ No newline at end of file
diff --git a/code/modules/reagents/reagent_containers.dm b/code/modules/reagents/reagent_containers.dm
index d475b118b2..618b74fffb 100644
--- a/code/modules/reagents/reagent_containers.dm
+++ b/code/modules/reagents/reagent_containers.dm
@@ -156,6 +156,10 @@
if(beaker_weakness_bitflag & PH_WEAK)
if((reagents.pH < 1.5) || (reagents.pH > 12.5))
START_PROCESSING(SSobj, src)
+ else if((reagents.pH < -3) || (reagents.pH > 17))
+ visible_message("[icon2html(src, viewers(src))] \The [src] is damaged by the super pH and begins to deform!")
+ reagents.pH = CLAMP(reagents.pH, -3, 17)
+ container_HP -= 1
/obj/item/reagent_containers/process()
diff --git a/code/modules/recycling/disposal/bin.dm b/code/modules/recycling/disposal/bin.dm
index 88c03ee7d5..7025a649e6 100644
--- a/code/modules/recycling/disposal/bin.dm
+++ b/code/modules/recycling/disposal/bin.dm
@@ -102,9 +102,17 @@
return ..()
/obj/machinery/disposal/proc/place_item_in_disposal(obj/item/I, mob/user)
- I.forceMove(src)
- user.visible_message("[user.name] places \the [I] into \the [src].", "You place \the [I] into \the [src].")
-
+ if(istype(I, /obj/item/clothing/head/mob_holder))
+ var/obj/item/clothing/head/mob_holder/H = I
+ var/mob/living/m = H.held_mob
+ H.release()
+ if(m)
+ user.start_pulling(m, 1)
+ stuff_mob_in(m,user)
+ return//you don't want this going into disposals ever
+ if(user.temporarilyRemoveItemFromInventory(I)) //double-checks never hurt
+ I.forceMove(src)
+ user.visible_message("[user.name] places \the [I] into \the [src].", "You place \the [I] into \the [src].")
//mouse drop another mob or self
/obj/machinery/disposal/MouseDrop_T(mob/living/target, mob/living/user)
if(istype(target))
diff --git a/code/modules/research/designs/autolathe_desings/autolathe_designs_sec_and_hacked.dm b/code/modules/research/designs/autolathe_desings/autolathe_designs_sec_and_hacked.dm
index e5e3a6bb4f..94599e9c32 100644
--- a/code/modules/research/designs/autolathe_desings/autolathe_designs_sec_and_hacked.dm
+++ b/code/modules/research/designs/autolathe_desings/autolathe_designs_sec_and_hacked.dm
@@ -130,11 +130,19 @@
category = list("hacked", "Security")
/datum/design/a357
- name = "Speed Loader (.357)"
+ name = "Revolver Bullet (.357)"
id = "a357"
build_type = AUTOLATHE
- materials = list(MAT_METAL = 30000)
- build_path = /obj/item/ammo_box/a357
+ materials = list(MAT_METAL = 4000)
+ build_path = /obj/item/ammo_casing/a357
+ category = list("hacked", "Security")
+
+/datum/design/a762
+ name = "Rifle Bullet (7.62mm)"
+ id = "a762"
+ build_type = AUTOLATHE
+ materials = list(MAT_METAL = 5000) //need seclathe for clips
+ build_path = /obj/item/ammo_casing/a762
category = list("hacked", "Security")
/datum/design/c10mm
@@ -183,4 +191,4 @@
build_type = AUTOLATHE
materials = list(MAT_METAL = 5500)
build_path = /obj/item/clothing/head/foilhat
- category = list("hacked", "Misc")
\ No newline at end of file
+ category = list("hacked", "Misc")
diff --git a/code/modules/research/techweb/all_nodes.dm b/code/modules/research/techweb/all_nodes.dm
index 7ccdb76dd9..05eeb564e5 100644
--- a/code/modules/research/techweb/all_nodes.dm
+++ b/code/modules/research/techweb/all_nodes.dm
@@ -1056,7 +1056,7 @@
/obj/item/crowbar/abductor, /obj/item/multitool/abductor,
/obj/item/stock_parts/cell/infinite/abductor, /obj/item/weldingtool/abductor,
/obj/item/wirecutters/abductor, /obj/item/circuitboard/machine/abductor,
- /obj/item/abductor_baton, /obj/item/abductor, /obj/item/stack/sheet/mineral/abductor)
+ /obj/item/abductor, /obj/item/stack/sheet/mineral/abductor)
/datum/techweb_node/alien_bio
id = "alien_bio"
diff --git a/code/modules/surgery/organs/tongue.dm b/code/modules/surgery/organs/tongue.dm
index 0fbd8c8406..760cd97f08 100644
--- a/code/modules/surgery/organs/tongue.dm
+++ b/code/modules/surgery/organs/tongue.dm
@@ -125,6 +125,32 @@
taste_sensitivity = 101 // ayys cannot taste anything.
maxHealth = 120 //Ayys probe a lot
modifies_speech = TRUE
+ var/mothership
+
+/obj/item/organ/tongue/abductor/attack_self(mob/living/carbon/human/H)
+ if(!istype(H))
+ return
+
+ var/obj/item/organ/tongue/abductor/T = H.getorganslot(ORGAN_SLOT_TONGUE)
+ if(!istype(T))
+ return
+
+ if(T.mothership == mothership)
+ to_chat(H, "[src] is already attuned to the same channel as your own.")
+ return
+
+ H.visible_message("[H] holds [src] in their hands, and concentrates for a moment.", "You attempt to modify the attunation of [src].")
+ if(do_after(H, delay=15, target=src))
+ to_chat(H, "You attune [src] to your own channel.")
+ mothership = T.mothership
+
+/obj/item/organ/tongue/abductor/examine(mob/M)
+ . = ..()
+ if(HAS_TRAIT(M, TRAIT_ABDUCTOR_TRAINING) || HAS_TRAIT(M.mind, TRAIT_ABDUCTOR_TRAINING) || isobserver(M))
+ if(!mothership)
+ . += "It is not attuned to a specific mothership."
+ else
+ . += "It is attuned to [mothership]."
/obj/item/organ/tongue/abductor/handle_speech(datum/source, list/speech_args)
//Hacks
diff --git a/code/modules/uplink/uplink_items.dm b/code/modules/uplink/uplink_items.dm
index 8dde999921..2de04cf0df 100644
--- a/code/modules/uplink/uplink_items.dm
+++ b/code/modules/uplink/uplink_items.dm
@@ -811,12 +811,11 @@ datum/uplink_item/stealthy_weapons/taeclowndo_shoes
/datum/uplink_item/ammo/revolver
name = ".357 Speed Loader"
- desc = "A speed loader that contains seven additional .357 Magnum rounds; usable with the Syndicate revolver. \
+ desc = "A speed loader that contains seven additional .357 Magnum rounds, and can be further reloaded with individual bullets; usable with the Syndicate revolver. \
For when you really need a lot of things dead."
item = /obj/item/ammo_box/a357
cost = 3
exclude_modes = list(/datum/game_mode/nuclear/clown_ops)
- illegal_tech = FALSE
/datum/uplink_item/ammo/revolver/ap
name = ".357 Armor Piercing Speed Loader"
@@ -1590,6 +1589,13 @@ datum/uplink_item/stealthy_weapons/taeclowndo_shoes
item = /obj/item/storage/backpack/duffelbag/syndie/surgery_adv
cost = 10
+/datum/uplink_item/device_tools/brainwash_disk
+ name = "Brainwashing Surgery Program"
+ desc = "A disk containing the procedure to perform a brainwashing surgery, allowing you to implant an objective onto a target. \
+ Insert into an Operating Console to enable the procedure."
+ item = /obj/item/disk/surgery/brainwashing
+ cost = 3
+
/datum/uplink_item/device_tools/encryptionkey
name = "Syndicate Encryption Key"
desc = "A key that, when inserted into a radio headset, allows you to listen to all station department channels \
@@ -1757,14 +1763,6 @@ datum/uplink_item/stealthy_weapons/taeclowndo_shoes
cost = 12
restricted_roles = list("Research Director", "Scientist", "Roboticist")
-/datum/uplink_item/role_restricted/brainwash_disk
- name = "Brainwashing Surgery Program"
- desc = "A disk containing the procedure to perform a brainwashing surgery, allowing you to implant an objective onto a target. \
- Insert into an Operating Console to enable the procedure."
- item = /obj/item/disk/surgery/brainwashing
- restricted_roles = list("Medical Doctor", "Roboticist")
- cost = 3
-
/datum/uplink_item/role_restricted/clown_bomb
name = "Clown Bomb"
desc = "The Clown bomb is a hilarious device capable of massive pranks. It has an adjustable timer, \
diff --git a/html/changelogs/AutoChangeLog-pr-10051.yml b/html/changelogs/AutoChangeLog-pr-10051.yml
new file mode 100644
index 0000000000..0d7d274891
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-10051.yml
@@ -0,0 +1,4 @@
+author: "kappa-sama"
+delete-after: True
+changes:
+ - balance: "legion drops more crates now"
diff --git a/html/changelogs/AutoChangeLog-pr-10054.yml b/html/changelogs/AutoChangeLog-pr-10054.yml
new file mode 100644
index 0000000000..0dc3b36618
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-10054.yml
@@ -0,0 +1,5 @@
+author: "kappa-sama"
+delete-after: True
+changes:
+ - balance: ".357 speedloaders in autolathes are now individual bullets instead, speedloaders are now illegal tech, costs less total metal to make 7 bullets than a previous speedloader. 7.62mm bullets in autolathe when hacked and costs more metal to make 5 7.62mm bullets than getting a clip from the seclathe."
+ - tweak: "mentions that you can refill speedloaders on .357 uplink description"
diff --git a/html/changelogs/AutoChangeLog-pr-10058.yml b/html/changelogs/AutoChangeLog-pr-10058.yml
new file mode 100644
index 0000000000..6f9a985765
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-10058.yml
@@ -0,0 +1,4 @@
+author: "Ghommie"
+delete-after: True
+changes:
+ - balance: "Spinfusor nerf: Upped the casing and ammo box size by one step, removed the projectile's dismemberment value (explosions can still rip a limb or two off), halved the ammo box capacity, reduced the spinfusor ammo supply pack contents from 32 to 8, removed the casing's ability to explode when thrown."
diff --git a/html/changelogs/AutoChangeLog-pr-10059.yml b/html/changelogs/AutoChangeLog-pr-10059.yml
new file mode 100644
index 0000000000..e9f89c18a2
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-10059.yml
@@ -0,0 +1,4 @@
+author: "lolman360"
+delete-after: True
+changes:
+ - rscadd: "Added ability to pick up certain simplemobs."
diff --git a/html/changelogs/AutoChangeLog-pr-10126.yml b/html/changelogs/AutoChangeLog-pr-10126.yml
new file mode 100644
index 0000000000..6cf568ca70
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-10126.yml
@@ -0,0 +1,4 @@
+author: "Putnam3145"
+delete-after: True
+changes:
+ - balance: "Buffed HE pipes by making them realistically radiate away heat."
diff --git a/html/changelogs/AutoChangeLog-pr-10141.yml b/html/changelogs/AutoChangeLog-pr-10141.yml
new file mode 100644
index 0000000000..5f0bfe2267
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-10141.yml
@@ -0,0 +1,4 @@
+author: "Putnam3145"
+delete-after: True
+changes:
+ - admin: "A whole bunch of dynamic data is now available for statbus"
diff --git a/html/changelogs/AutoChangeLog-pr-10142.yml b/html/changelogs/AutoChangeLog-pr-10142.yml
new file mode 100644
index 0000000000..aba2675550
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-10142.yml
@@ -0,0 +1,4 @@
+author: "Trilbyspaceclone"
+delete-after: True
+changes:
+ - tweak: "CE hardsuit is now more rad-proof"
diff --git a/html/changelogs/AutoChangeLog-pr-10193.yml b/html/changelogs/AutoChangeLog-pr-10193.yml
new file mode 100644
index 0000000000..b432eb74a3
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-10193.yml
@@ -0,0 +1,4 @@
+author: "Commandersand"
+delete-after: True
+changes:
+ - tweak: "added two words to clown filter"
diff --git a/html/changelogs/AutoChangeLog-pr-10200.yml b/html/changelogs/AutoChangeLog-pr-10200.yml
new file mode 100644
index 0000000000..8d4782ee98
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-10200.yml
@@ -0,0 +1,4 @@
+author: "kevinz000"
+delete-after: True
+changes:
+ - rscadd: "added 8 character save slots"
diff --git a/html/changelogs/AutoChangeLog-pr-10231.yml b/html/changelogs/AutoChangeLog-pr-10231.yml
new file mode 100644
index 0000000000..7caf65b09f
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-10231.yml
@@ -0,0 +1,4 @@
+author: "Seris02"
+delete-after: True
+changes:
+ - bugfix: "the sprites"
diff --git a/html/changelogs/AutoChangeLog-pr-10232.yml b/html/changelogs/AutoChangeLog-pr-10232.yml
new file mode 100644
index 0000000000..479012a868
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-10232.yml
@@ -0,0 +1,4 @@
+author: "DeltaFire15"
+delete-after: True
+changes:
+ - tweak: "All heads of staff can now message CC"
diff --git a/html/changelogs/AutoChangeLog-pr-10235.yml b/html/changelogs/AutoChangeLog-pr-10235.yml
new file mode 100644
index 0000000000..ca8e4da164
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-10235.yml
@@ -0,0 +1,4 @@
+author: "kevinz000"
+delete-after: True
+changes:
+ - balance: "flashbangs process light/sound separately and uses viewers(), so xray users beware."
diff --git a/html/changelogs/AutoChangeLog-pr-9563.yml b/html/changelogs/AutoChangeLog-pr-9563.yml
new file mode 100644
index 0000000000..47fab1530d
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-9563.yml
@@ -0,0 +1,4 @@
+author: "Fermis"
+delete-after: True
+changes:
+ - tweak: "tweaked how super bases/acids work but limiting them"
diff --git a/html/changelogs/AutoChangeLog-pr-9596.yml b/html/changelogs/AutoChangeLog-pr-9596.yml
new file mode 100644
index 0000000000..3f924b85ab
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-9596.yml
@@ -0,0 +1,11 @@
+author: "Linzolle"
+delete-after: True
+changes:
+ - rscadd: "Abductor chem dispenser, and added it to the abductor console."
+ - rscadd: "\"Superlingual matrix\" to the abductor console. It's the abductor's tongue. Can be used to link it to your abductor communication channel and then implanted into a test subject."
+ - rscadd: "Shrink ray and added it to the abductor console."
+ - soundadd: "Shrink ray sound effect (its the fucking mega man death sound)"
+ - rscadd: "special jumpsuit for abductors"
+ - imageadd: "abductor jumpsuit, including digi version if a digitigrade person somehow manages to get their hands on it. sprites for the shrink ray and chem dispenser."
+ - rscadd: "new glands to play with, including the all-access gland, the quantum gland, and the blood type randomiser."
+ - code_imp: "split every gland into its own file instead of all being in one file"
diff --git a/html/changelogs/AutoChangeLog-pr-9925.yml b/html/changelogs/AutoChangeLog-pr-9925.yml
new file mode 100644
index 0000000000..6e2f44fc7b
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-9925.yml
@@ -0,0 +1,4 @@
+author: "Linzolle"
+delete-after: True
+changes:
+ - tweak: "cosmic coat crafting recipe changed to coat + cosmic bedsheet"
diff --git a/html/changelogs/AutoChangeLog-pr-9940.yml b/html/changelogs/AutoChangeLog-pr-9940.yml
new file mode 100644
index 0000000000..75563c69b4
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-9940.yml
@@ -0,0 +1,4 @@
+author: "Putnam"
+delete-after: True
+changes:
+ - tweak: "Dynamic rulesets have lower weight if a round recently featured them (except traitor)."
diff --git a/icons/mob/animals_held.dmi b/icons/mob/animals_held.dmi
new file mode 100644
index 0000000000..82a065d93a
Binary files /dev/null and b/icons/mob/animals_held.dmi differ
diff --git a/icons/mob/animals_held_lh.dmi b/icons/mob/animals_held_lh.dmi
new file mode 100644
index 0000000000..6c407c850b
Binary files /dev/null and b/icons/mob/animals_held_lh.dmi differ
diff --git a/icons/mob/animals_held_rh.dmi b/icons/mob/animals_held_rh.dmi
new file mode 100644
index 0000000000..322dad06d2
Binary files /dev/null and b/icons/mob/animals_held_rh.dmi differ
diff --git a/icons/mob/head.dmi b/icons/mob/head.dmi
index f5951e032c..365ed5afd9 100644
Binary files a/icons/mob/head.dmi and b/icons/mob/head.dmi differ
diff --git a/icons/mob/inhands/weapons/guns_lefthand.dmi b/icons/mob/inhands/weapons/guns_lefthand.dmi
index ea185c5ad7..a166610826 100644
Binary files a/icons/mob/inhands/weapons/guns_lefthand.dmi and b/icons/mob/inhands/weapons/guns_lefthand.dmi differ
diff --git a/icons/mob/inhands/weapons/guns_righthand.dmi b/icons/mob/inhands/weapons/guns_righthand.dmi
index 1d380e34bc..47ed1adfee 100644
Binary files a/icons/mob/inhands/weapons/guns_righthand.dmi and b/icons/mob/inhands/weapons/guns_righthand.dmi differ
diff --git a/icons/mob/screen_alert.dmi b/icons/mob/screen_alert.dmi
index 30b8eb8f9a..c1912d74cc 100644
Binary files a/icons/mob/screen_alert.dmi and b/icons/mob/screen_alert.dmi differ
diff --git a/icons/mob/uniform.dmi b/icons/mob/uniform.dmi
index e9e80935c6..0f962591a5 100644
Binary files a/icons/mob/uniform.dmi and b/icons/mob/uniform.dmi differ
diff --git a/icons/mob/uniform_digi.dmi b/icons/mob/uniform_digi.dmi
index 1e549eaa8a..94d9f07e10 100644
Binary files a/icons/mob/uniform_digi.dmi and b/icons/mob/uniform_digi.dmi differ
diff --git a/icons/obj/abductor.dmi b/icons/obj/abductor.dmi
index d8968a107b..fd0893b300 100644
Binary files a/icons/obj/abductor.dmi and b/icons/obj/abductor.dmi differ
diff --git a/icons/obj/clothing/uniforms.dmi b/icons/obj/clothing/uniforms.dmi
index db1ee89453..43162f2b7e 100644
Binary files a/icons/obj/clothing/uniforms.dmi and b/icons/obj/clothing/uniforms.dmi differ
diff --git a/icons/obj/guns/energy.dmi b/icons/obj/guns/energy.dmi
index d23af6c9b4..bba3efc951 100644
Binary files a/icons/obj/guns/energy.dmi and b/icons/obj/guns/energy.dmi differ
diff --git a/modular_citadel/code/modules/projectiles/guns/ballistic/spinfusor.dm b/modular_citadel/code/modules/projectiles/guns/ballistic/spinfusor.dm
index d16df6b285..b70858c9af 100644
--- a/modular_citadel/code/modules/projectiles/guns/ballistic/spinfusor.dm
+++ b/modular_citadel/code/modules/projectiles/guns/ballistic/spinfusor.dm
@@ -5,7 +5,6 @@
icon = 'modular_citadel/icons/obj/guns/cit_guns.dmi'
icon_state= "spinner"
damage = 30
- dismemberment = 25
/obj/item/projectile/bullet/spinfusor/on_hit(atom/target, blocked = FALSE) //explosion to emulate the spinfusor's AOE
..()
@@ -14,22 +13,16 @@
/obj/item/ammo_casing/caseless/spinfusor
name = "spinfusor disk"
- desc = "A magnetic disk designed specifically for the Stormhammer magnetic cannon. Warning: extremely volatile!"
+ desc = "A magnetic disk designed specifically for the Stormhammer magnetic cannon. Packs a punch."
projectile_type = /obj/item/projectile/bullet/spinfusor
caliber = "spinfusor"
icon = 'modular_citadel/icons/obj/guns/cit_guns.dmi'
icon_state = "disk"
+ w_class = WEIGHT_CLASS_SMALL
throwforce = 15 //still deadly when thrown
+ force = 5
throw_speed = 3
-/obj/item/ammo_casing/caseless/spinfusor/throw_impact(atom/target) //disks detonate when thrown
- if(!..()) // not caught in mid-air
- visible_message("[src] detonates!")
- playsound(src.loc, "sparks", 50, 1)
- explosion(target, -1, -1, 1, 1, -1)
- qdel(src)
- return 1
-
/obj/item/ammo_box/magazine/internal/spinfusor
name = "spinfusor internal magazine"
ammo_type = /obj/item/ammo_casing/caseless/spinfusor
@@ -71,7 +64,8 @@
icon = 'modular_citadel/icons/obj/guns/cit_guns.dmi'
icon_state = "spinfusorbox"
ammo_type = /obj/item/ammo_casing/caseless/spinfusor
- max_ammo = 8
+ w_class = WEIGHT_CLASS_NORMAL
+ max_ammo = 4
/datum/supply_pack/security/armory/spinfusor
name = "Stormhammer Spinfusor Crate"
@@ -84,7 +78,5 @@
name = "Spinfusor Disk Crate"
cost = 7000
contains = list(/obj/item/ammo_box/aspinfusor,
- /obj/item/ammo_box/aspinfusor,
- /obj/item/ammo_box/aspinfusor,
/obj/item/ammo_box/aspinfusor)
- crate_name = "spinfusor disk crate"
\ No newline at end of file
+ crate_name = "spinfusor disk crate"
diff --git a/sound/weapons/shrink_hit.ogg b/sound/weapons/shrink_hit.ogg
new file mode 100644
index 0000000000..c39c2d5269
Binary files /dev/null and b/sound/weapons/shrink_hit.ogg differ
diff --git a/tgstation.dme b/tgstation.dme
index 70b42185cd..964dd5478e 100755
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -384,6 +384,7 @@
#include "code\datums\components\riding.dm"
#include "code\datums\components\rotation.dm"
#include "code\datums\components\shrapnel.dm"
+#include "code\datums\components\shrink.dm"
#include "code\datums\components\slippery.dm"
#include "code\datums\components\spooky.dm"
#include "code\datums\components\squeak.dm"
@@ -1211,6 +1212,21 @@
#include "code\modules\antagonists\abductor\equipment\abduction_outfits.dm"
#include "code\modules\antagonists\abductor\equipment\abduction_surgery.dm"
#include "code\modules\antagonists\abductor\equipment\gland.dm"
+#include "code\modules\antagonists\abductor\equipment\glands\access.dm"
+#include "code\modules\antagonists\abductor\equipment\glands\blood.dm"
+#include "code\modules\antagonists\abductor\equipment\glands\chem.dm"
+#include "code\modules\antagonists\abductor\equipment\glands\egg.dm"
+#include "code\modules\antagonists\abductor\equipment\glands\electric.dm"
+#include "code\modules\antagonists\abductor\equipment\glands\heal.dm"
+#include "code\modules\antagonists\abductor\equipment\glands\mindshock.dm"
+#include "code\modules\antagonists\abductor\equipment\glands\plasma.dm"
+#include "code\modules\antagonists\abductor\equipment\glands\quantum.dm"
+#include "code\modules\antagonists\abductor\equipment\glands\slime.dm"
+#include "code\modules\antagonists\abductor\equipment\glands\spider.dm"
+#include "code\modules\antagonists\abductor\equipment\glands\transform.dm"
+#include "code\modules\antagonists\abductor\equipment\glands\trauma.dm"
+#include "code\modules\antagonists\abductor\equipment\glands\ventcrawl.dm"
+#include "code\modules\antagonists\abductor\equipment\glands\viral.dm"
#include "code\modules\antagonists\abductor\machinery\camera.dm"
#include "code\modules\antagonists\abductor\machinery\console.dm"
#include "code\modules\antagonists\abductor\machinery\dispenser.dm"