")
@@ -374,7 +385,7 @@ SUBSYSTEM_DEF(ticker)
if(player.mind.assigned_role != player.mind.special_role)
SSjob.EquipRank(N, player.mind.assigned_role, 0)
if(CONFIG_GET(flag/roundstart_traits) && ishuman(N.new_character))
- SSquirks.AssignQuirks(N.new_character, N.client, TRUE)
+ SSquirks.AssignQuirks(N.new_character, N.client, TRUE, TRUE, SSjob.GetJob(player.mind.assigned_role), FALSE, N)
CHECK_TICK
if(captainless)
for(var/mob/dead/new_player/N in GLOB.player_list)
@@ -414,7 +425,7 @@ SUBSYSTEM_DEF(ticker)
m = pick(memetips)
if(m)
- to_chat(world, "Tip of the round: [html_encode(m)]")
+ to_chat(world, "Tip of the round: [html_encode(m)]")
/datum/controller/subsystem/ticker/proc/check_queue()
var/hpc = CONFIG_GET(number/hard_popcap)
@@ -561,6 +572,15 @@ SUBSYSTEM_DEF(ticker)
news_message = "The burst of energy released near [station_name()] has been confirmed as merely a test of a new weapon. However, due to an unexpected mechanical error, their communications system has been knocked offline."
if(SHUTTLE_HIJACK)
news_message = "During routine evacuation procedures, the emergency shuttle of [station_name()] had its navigation protocols corrupted and went off course, but was recovered shortly after."
+ if(GANG_VICTORY)
+ news_message = "Company officials reaffirmed that sudden deployments of special forces are not in any way connected to rumors of [station_name()] being covered in graffiti."
+
+ if(SSblackbox.first_death)
+ var/list/ded = SSblackbox.first_death
+ if(ded.len)
+ news_message += " NT Sanctioned Psykers picked up faint traces of someone near the station, allegedly having had died. Their name was: [ded["name"]], [ded["role"]], at [ded["area"]].[ded["last_words"] ? " Their last words were: \"[ded["last_words"]]\"" : ""]"
+ else
+ news_message += " NT Sanctioned Psykers proudly confirm reports that nobody died this shift!"
if(news_message)
send2otherserver(news_source, news_message,"News_Report")
diff --git a/code/controllers/subsystem/vore.dm b/code/controllers/subsystem/vore.dm
index faaa297ca3..ca34f9ab61 100644
--- a/code/controllers/subsystem/vore.dm
+++ b/code/controllers/subsystem/vore.dm
@@ -7,7 +7,7 @@
SUBSYSTEM_DEF(bellies)
name = "Bellies"
- priority = 5
+ priority = FIRE_PRIORITY_VORE
wait = 1 SECONDS
flags = SS_KEEP_TIMING|SS_NO_INIT
runlevels = RUNLEVEL_GAME|RUNLEVEL_POSTGAME
diff --git a/code/controllers/subsystem/vote.dm b/code/controllers/subsystem/vote.dm
index 422b1eda26..2f56c69384 100644
--- a/code/controllers/subsystem/vote.dm
+++ b/code/controllers/subsystem/vote.dm
@@ -18,6 +18,8 @@ SUBSYSTEM_DEF(vote)
var/obfuscated = FALSE//CIT CHANGE - adds obfuscated/admin-only votes
+ var/list/stored_gamemode_votes = list() //Basically the last voted gamemode is stored here for end-of-round use.
+
/datum/controller/subsystem/vote/fire() //called by master_controller
if(mode)
time_remaining = round((started_time + CONFIG_GET(number/vote_period) - world.time)/10)
@@ -85,15 +87,20 @@ SUBSYSTEM_DEF(vote)
/datum/controller/subsystem/vote/proc/announce_result()
var/list/winners = get_result()
var/text
+ var/was_roundtype_vote = mode == "roundtype"
if(winners.len > 0)
if(question)
text += "[question]"
else
text += "[capitalize(mode)] Vote"
+ if(was_roundtype_vote)
+ stored_gamemode_votes = list()
for(var/i=1,i<=choices.len,i++)
var/votes = choices[choices[i]]
if(!votes)
votes = 0
+ if(was_roundtype_vote)
+ stored_gamemode_votes[choices[i]] = votes
text += "\n[choices[i]]: [obfuscated ? "???" : votes]" //CIT CHANGE - adds obfuscated votes
if(mode != "custom")
if(winners.len > 1 && !obfuscated) //CIT CHANGE - adds obfuscated votes
diff --git a/code/datums/action.dm b/code/datums/action.dm
index f64a549b29..fd33ef79b5 100644
--- a/code/datums/action.dm
+++ b/code/datums/action.dm
@@ -494,6 +494,7 @@
else
to_chat(owner, "Your hands are full!")
+//MGS Box
/datum/action/item_action/agent_box
name = "Deploy Box"
desc = "Find inner peace, here, in the box."
@@ -502,21 +503,27 @@
icon_icon = 'icons/mob/actions/actions_items.dmi'
button_icon_state = "deploy_box"
var/cooldown = 0
- var/obj/structure/closet/cardboard/agent/box
+ var/boxtype = /obj/structure/closet/cardboard/agent
+//Handles open and closing the box
/datum/action/item_action/agent_box/Trigger()
- if(!..())
+ . = ..()
+ if(!.)
return FALSE
- if(QDELETED(box))
- if(cooldown < world.time - 100)
- box = new(owner.drop_location())
- owner.forceMove(box)
- cooldown = world.time
- owner.playsound_local(box, 'sound/misc/box_deploy.ogg', 50, TRUE)
- else
- owner.forceMove(box.drop_location())
+ if(istype(owner.loc, /obj/structure/closet/cardboard/agent))
+ var/obj/structure/closet/cardboard/agent/box = owner.loc
+ owner.playsound_local(box, 'sound/misc/box_deploy.ogg', 50, TRUE)
+ box.open()
+ return
+ //Box closing from here on out.
+ if(!isturf(owner.loc)) //Don't let the player use this to escape mechs/welded closets.
+ to_chat(owner, "You need more space to activate this implant.")
+ return
+ if(cooldown < world.time - 100)
+ var/box = new boxtype(owner.drop_location())
+ owner.forceMove(box)
+ cooldown = world.time
owner.playsound_local(box, 'sound/misc/box_deploy.ogg', 50, TRUE)
- QDEL_NULL(box)
//Preset for spells
/datum/action/spell_action
diff --git a/code/datums/brain_damage/brain_trauma.dm b/code/datums/brain_damage/brain_trauma.dm
index 3731397170..56a3f3969b 100644
--- a/code/datums/brain_damage/brain_trauma.dm
+++ b/code/datums/brain_damage/brain_trauma.dm
@@ -32,16 +32,18 @@
//Called when given to a mob
/datum/brain_trauma/proc/on_gain()
to_chat(owner, gain_text)
+ RegisterSignal(owner, COMSIG_MOB_SAY, .proc/handle_speech)
//Called when removed from a mob
/datum/brain_trauma/proc/on_lose(silent)
if(!silent)
to_chat(owner, lose_text)
+ UnregisterSignal(owner, COMSIG_MOB_SAY)
//Called when hearing a spoken message
/datum/brain_trauma/proc/on_hear(message, speaker, message_language, raw_message, radio_freq)
return message
//Called when speaking
-/datum/brain_trauma/proc/on_say(message)
- return message
+/datum/brain_trauma/proc/handle_speech(datum/source, list/speech_args)
+ UnregisterSignal(owner, COMSIG_MOB_SAY)
diff --git a/code/datums/brain_damage/hypnosis.dm b/code/datums/brain_damage/hypnosis.dm
new file mode 100644
index 0000000000..8909d1b85f
--- /dev/null
+++ b/code/datums/brain_damage/hypnosis.dm
@@ -0,0 +1,72 @@
+/datum/brain_trauma/hypnosis
+ name = "Hypnosis"
+ desc = "Patient's unconscious is completely enthralled by a word or sentence, focusing their thoughts and actions on it."
+ scan_desc = "looping thought pattern"
+ gain_text = ""
+ lose_text = ""
+ resilience = TRAUMA_RESILIENCE_SURGERY
+ var/hypnotic_phrase = ""
+ var/regex/target_phrase
+
+/datum/brain_trauma/hypnosis/New(phrase, quirk = FALSE)
+ if(!phrase)
+ qdel(src)
+ if(quirk == TRUE)
+ hypnotic_phrase = phrase
+ else
+ friendliify(phrase)
+ if(IsAdminAdvancedProcCall())
+ to_chat(usr, "Hypnosis New() skipped due to try/catch incompatibility with admin proccalling.")
+ qdel(src)
+ try
+ target_phrase = new("(\\b[hypnotic_phrase]\\b)","ig")
+ catch(var/exception/e)
+ stack_trace("[e] on [e.file]:[e.line]")
+ qdel(src)
+ ..()
+
+/datum/brain_trauma/hypnosis/proc/friendliify(phrase)
+ phrase = replacetext(lowertext(phrase), "kill", "hug")
+ phrase = replacetext(lowertext(phrase), "murder", "cuddle")
+ phrase = replacetext(lowertext(phrase), "harm", "snuggle")
+ phrase = replacetext(lowertext(phrase), "decapitate", "headpat")
+ phrase = replacetext(lowertext(phrase), "strangle", "meow at")
+ phrase = replacetext(lowertext(phrase), "suicide", "self-love")
+ phrase = replacetext(lowertext(phrase), "lynch", "kiss")
+ hypnotic_phrase = phrase
+
+/datum/brain_trauma/hypnosis/on_gain()
+ message_admins("[ADMIN_LOOKUPFLW(owner)] was hypnotized with the phrase '[hypnotic_phrase]'.")
+ log_game("[key_name(owner)] was hypnotized with the phrase '[hypnotic_phrase]'.")
+ to_chat(owner, "[hypnotic_phrase]")
+ to_chat(owner, "[pick("You feel your thoughts focusing on this phrase... you can't seem to get it out of your head.",\
+ "Your head hurts, but this is all you can think of. It must be vitally important.",\
+ "You feel a part of your mind repeating this over and over. You need to follow these words.",\
+ "Something about this sounds... right, for some reason. You feel like you should follow these words.",\
+ "These words keep echoing in your mind. You find yourself completely fascinated by them.")]")
+ if(!HAS_TRAIT(owner, "hypnotherapy"))
+ to_chat(owner, "You've been hypnotized by this sentence. You must follow these words. If it isn't a clear order, you can freely interpret how to do so,\
+ as long as you act like the words are your highest priority.")
+ else
+ to_chat(owner, "You've been hypnotized by this sentence. You feel an incredible desire to follow these words, but are able to resist it somewhat. If it isn't a clear order, you can freely interpret how to do so,\
+ however this does not take precedence over your other objectives.")
+ ..()
+
+/datum/brain_trauma/hypnosis/on_lose()
+ message_admins("[ADMIN_LOOKUPFLW(owner)] is no longer hypnotized with the phrase '[hypnotic_phrase]'.")
+ log_game("[key_name(owner)] is no longer hypnotized with the phrase '[hypnotic_phrase]'.")
+ to_chat(owner, "You suddenly snap out of your fixation. The phrase '[hypnotic_phrase]' no longer feels important to you.")
+ ..()
+
+/datum/brain_trauma/hypnosis/on_life()
+ ..()
+ if(prob(2))
+ switch(rand(1,2))
+ if(1)
+ to_chat(owner, "...[lowertext(hypnotic_phrase)]...")
+ if(2)
+ new /datum/hallucination/chat(owner, TRUE, FALSE, "[hypnotic_phrase]")
+
+/datum/brain_trauma/hypnosis/on_hear(message, speaker, message_language, raw_message, radio_freq)
+ message = target_phrase.Replace(message, "$1")
+ return message
diff --git a/code/datums/brain_damage/imaginary_friend.dm b/code/datums/brain_damage/imaginary_friend.dm
index 1bc7d8e3be..5bbc5de4a5 100644
--- a/code/datums/brain_damage/imaginary_friend.dm
+++ b/code/datums/brain_damage/imaginary_friend.dm
@@ -46,7 +46,7 @@
var/list/mob/dead/observer/candidates = pollCandidatesForMob("Do you want to play as [owner]'s imaginary friend?", ROLE_PAI, null, null, 75, friend)
if(LAZYLEN(candidates))
var/mob/dead/observer/C = pick(candidates)
- friend.key = C.key
+ C.transfer_ckey(friend, FALSE)
friend_initialized = TRUE
else
qdel(src)
diff --git a/code/datums/brain_damage/mild.dm b/code/datums/brain_damage/mild.dm
index f2fec3bb63..c049a7db33 100644
--- a/code/datums/brain_damage/mild.dm
+++ b/code/datums/brain_damage/mild.dm
@@ -68,18 +68,12 @@
lose_text = ""
/datum/brain_trauma/mild/speech_impediment/on_gain()
- owner.dna.add_mutation(UNINTELLIGIBLE)
- ..()
-
-//no fiddling with genetics to get out of this one
-/datum/brain_trauma/mild/speech_impediment/on_life()
- if(!(GLOB.mutations_list[UNINTELLIGIBLE] in owner.dna.mutations))
- on_gain()
- ..()
+ ADD_TRAIT(owner, TRAIT_UNINTELLIGIBLE_SPEECH, TRAUMA_TRAIT)
+ . = ..()
/datum/brain_trauma/mild/speech_impediment/on_lose()
- owner.dna.remove_mutation(UNINTELLIGIBLE)
- ..()
+ REMOVE_TRAIT(owner, TRAIT_UNINTELLIGIBLE_SPEECH, TRAUMA_TRAIT)
+ . = ..()
/datum/brain_trauma/mild/concussion
name = "Concussion"
diff --git a/code/datums/brain_damage/phobia.dm b/code/datums/brain_damage/phobia.dm
index 71a5f21454..f802555c7e 100644
--- a/code/datums/brain_damage/phobia.dm
+++ b/code/datums/brain_damage/phobia.dm
@@ -31,6 +31,8 @@
/datum/brain_trauma/mild/phobia/on_life()
..()
+ if(HAS_TRAIT(owner, TRAIT_FEARLESS))
+ return
if(is_blind(owner))
return
if(world.time > next_check && world.time > next_scare)
@@ -70,6 +72,8 @@
/datum/brain_trauma/mild/phobia/on_hear(message, speaker, message_language, raw_message, radio_freq)
if(!owner.can_hear() || world.time < next_scare) //words can't trigger you if you can't hear them *taps head*
return message
+ if(HAS_TRAIT(owner, TRAIT_FEARLESS))
+ return message
for(var/word in trigger_words)
var/reg = regex("(\\b|\\A)[REGEX_QUOTE(word)]'?s*(\\b|\\Z)", "i")
@@ -78,14 +82,15 @@
break
return message
-/datum/brain_trauma/mild/phobia/on_say(message)
+/datum/brain_trauma/mild/phobia/handle_speech(datum/source, list/speech_args)
+ if(HAS_TRAIT(owner, TRAIT_FEARLESS))
+ return
for(var/word in trigger_words)
var/reg = regex("(\\b|\\A)[REGEX_QUOTE(word)]'?s*(\\b|\\Z)", "i")
- if(findtext(message, reg))
+ if(findtext(speech_args[SPEECH_MESSAGE], reg))
to_chat(owner, "You can't bring yourself to say the word \"[word]\"!")
- return ""
- return message
+ speech_args[SPEECH_MESSAGE] = ""
/datum/brain_trauma/mild/phobia/proc/freak_out(atom/reason, trigger_word)
next_scare = world.time + 120
diff --git a/code/datums/brain_damage/severe.dm b/code/datums/brain_damage/severe.dm
index d094c162d7..890e9cf903 100644
--- a/code/datums/brain_damage/severe.dm
+++ b/code/datums/brain_damage/severe.dm
@@ -254,3 +254,20 @@
/datum/brain_trauma/severe/pacifism/on_lose()
REMOVE_TRAIT(owner, TRAIT_PACIFISM, TRAUMA_TRAIT)
..()
+
+//ported from TG
+/datum/brain_trauma/severe/hypnotic_stupor
+ name = "Hypnotic Stupor"
+ desc = "Patient is prone to episodes of extreme stupor that leaves them extremely suggestible."
+ scan_desc = "oneiric feedback loop"
+ gain_text = "You feel somewhat dazed."
+ lose_text = "You feel like a fog was lifted from your mind."
+
+/datum/brain_trauma/severe/hypnotic_stupor/on_lose() //hypnosis must be cleared separately, but brain surgery should get rid of both anyway
+ ..()
+ owner.remove_status_effect(/datum/status_effect/trance)
+
+/datum/brain_trauma/severe/hypnotic_stupor/on_life()
+ ..()
+ if(prob(1) && !owner.has_status_effect(/datum/status_effect/trance))
+ owner.apply_status_effect(/datum/status_effect/trance, rand(100,300), FALSE)
diff --git a/code/datums/brain_damage/split_personality.dm b/code/datums/brain_damage/split_personality.dm
index 612af13392..1a26ea7a14 100644
--- a/code/datums/brain_damage/split_personality.dm
+++ b/code/datums/brain_damage/split_personality.dm
@@ -26,7 +26,7 @@
var/list/mob/dead/observer/candidates = pollCandidatesForMob("Do you want to play as [owner]'s split personality?", ROLE_PAI, null, null, 75, stranger_backseat)
if(LAZYLEN(candidates))
var/mob/dead/observer/C = pick(candidates)
- stranger_backseat.key = C.key
+ C.transfer_ckey(stranger_backseat, FALSE)
log_game("[key_name(stranger_backseat)] became [key_name(owner)]'s split personality.")
message_admins("[ADMIN_LOOKUPFLW(stranger_backseat)] became [ADMIN_LOOKUPFLW(owner)]'s split personality.")
else
@@ -184,7 +184,7 @@
var/list/mob/dead/observer/candidates = pollCandidatesForMob("Do you want to play as [owner]'s brainwashed mind?", null, null, null, 75, stranger_backseat)
if(LAZYLEN(candidates))
var/mob/dead/observer/C = pick(candidates)
- stranger_backseat.key = C.key
+ C.transfer_ckey(stranger_backseat, FALSE)
else
qdel(src)
@@ -199,10 +199,9 @@
addtimer(CALLBACK(src, /datum/brain_trauma/severe/split_personality.proc/switch_personalities), 10)
return message
-/datum/brain_trauma/severe/split_personality/brainwashing/on_say(message)
- if(findtext(message, codeword))
- return "" //oh hey did you want to tell people about the secret word to bring you back?
- return message
+/datum/brain_trauma/severe/split_personality/brainwashing/handle_speech(datum/source, list/speech_args)
+ if(findtext(speech_args[SPEECH_MESSAGE], codeword))
+ speech_args[SPEECH_MESSAGE] = "" //oh hey did you want to tell people about the secret word to bring you back?
/mob/living/split_personality/traitor
name = "split personality"
diff --git a/code/datums/components/bouncy.dm b/code/datums/components/bouncy.dm
new file mode 100644
index 0000000000..f6a2a89195
--- /dev/null
+++ b/code/datums/components/bouncy.dm
@@ -0,0 +1,40 @@
+/datum/component/bouncy
+ dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS
+ var/bouncy_mod = 1
+ var/list/bounce_signals = list(COMSIG_MOVABLE_IMPACT, COMSIG_ITEM_HIT_REACT, COMSIG_ITEM_ATTACK)
+
+/datum/component/bouncy/Initialize(_bouncy_mod, list/_bounce_signals)
+ if(!ismovableatom(parent))
+ return COMPONENT_INCOMPATIBLE
+ if(_bouncy_mod)
+ bouncy_mod = _bouncy_mod
+ if(_bounce_signals)
+ bounce_signals = _bounce_signals
+
+/datum/component/bouncy/InheritComponent(datum/component/bouncy/B, original, _bouncy_mod, list/_bounce_signals)
+ if(_bouncy_mod)
+ bouncy_mod = max(bouncy_mod, _bouncy_mod)
+ if(_bounce_signals)
+ var/list/diff_bounces = difflist(bounce_signals, _bounce_signals, TRUE)
+ for(var/bounce in diff_bounces)
+ bounce_signals += bounce
+ RegisterSignal(parent, bounce, .proc/bounce_up)
+
+/datum/component/bouncy/RegisterWithParent()
+ RegisterSignal(parent, bounce_signals, .proc/bounce_up)
+
+/datum/component/bouncy/UnregisterFromParent()
+ UnregisterSignal(parent, bounce_signals)
+
+/datum/component/bouncy/proc/bounce_up(datum/source)
+ var/atom/movable/A = parent
+ switch(rand(1, 3))
+ if(1)
+ A.do_jiggle(45 + rand(-10, 10) * bouncy_mod, 14)
+ if(2)
+ var/min_b = 0.6/bouncy_mod
+ var/max_b = 1.2 * bouncy_mod
+ A.do_squish(rand(min_b, max_b), rand(min_b, max_b), 14)
+ if(3)
+ var/pixelshift = 8 * bouncy_mod
+ A.Shake(pixelshift, pixelshift, duration = 15)
diff --git a/code/datums/components/caltrop.dm b/code/datums/components/caltrop.dm
index 838a1b576a..f0333072a8 100644
--- a/code/datums/components/caltrop.dm
+++ b/code/datums/components/caltrop.dm
@@ -37,9 +37,9 @@
if(O.status == BODYPART_ROBOTIC)
return
- var/feetCover = (H.wear_suit && (H.wear_suit.body_parts_covered & FEET)) || (H.w_uniform && (H.w_uniform.body_parts_covered & FEET))
+ var/feetCover = (H.wear_suit && (H.wear_suit.body_parts_covered & FEET)) || (H.w_uniform && (H.w_uniform.body_parts_covered & FEET) || (H.shoes && (H.shoes.body_parts_covered & FEET)))
- if(!(flags & CALTROP_BYPASS_SHOES) && (H.shoes || feetCover))
+ if(!(flags & CALTROP_BYPASS_SHOES) && feetCover)
return
if((H.movement_type & FLYING) || H.buckled)
diff --git a/code/datums/components/earhealing.dm b/code/datums/components/earhealing.dm
index 6eb71285e0..bd3d57480d 100644
--- a/code/datums/components/earhealing.dm
+++ b/code/datums/components/earhealing.dm
@@ -26,5 +26,5 @@
if(!HAS_TRAIT(wearer, TRAIT_DEAF))
var/obj/item/organ/ears/ears = wearer.getorganslot(ORGAN_SLOT_EARS)
if (ears)
- ears.deaf = max(ears.deaf - 1, (ears.ear_damage < UNHEALING_EAR_DAMAGE ? 0 : 1)) // Do not clear deafness while above the unhealing ear damage threshold
- ears.ear_damage = max(ears.ear_damage - 0.1, 0)
+ ears.deaf = max(ears.deaf - 1, (ears.damage < ears.maxHealth ? 0 : 1)) // Do not clear deafness if our ears are too damaged
+ ears.damage = max(ears.damage - 0.1, 0)
diff --git a/code/datums/components/footstep.dm b/code/datums/components/footstep.dm
index bfcc49f453..c4e65ea120 100644
--- a/code/datums/components/footstep.dm
+++ b/code/datums/components/footstep.dm
@@ -1,105 +1,108 @@
-/datum/component/footstep
- var/steps = 0
- var/volume
- var/e_range
-
-/datum/component/footstep/Initialize(volume_ = 0.5, e_range_ = -1)
- if(!isliving(parent))
- return COMPONENT_INCOMPATIBLE
- volume = volume_
- e_range = e_range_
- RegisterSignal(parent, list(COMSIG_MOVABLE_MOVED), .proc/play_footstep)
-
-/datum/component/footstep/proc/play_footstep()
- var/turf/open/T = get_turf(parent)
- if(!istype(T))
- return
-
- var/mob/living/LM = parent
- var/v = volume
- var/e = e_range
- if(!T.footstep || LM.buckled || LM.lying || !LM.canmove || LM.resting || LM.buckled || LM.throwing || LM.movement_type & (VENTCRAWLING | FLYING))
- if (LM.lying && !(!T.footstep || LM.movement_type & (VENTCRAWLING | FLYING))) //play crawling sound if we're lying
- playsound(T, 'sound/effects/footstep/crawl1.ogg', 15 * v)
- return
-
- if(iscarbon(LM))
- var/mob/living/carbon/C = LM
- if(!C.get_bodypart(BODY_ZONE_L_LEG) && !C.get_bodypart(BODY_ZONE_R_LEG))
- return
- if(ishuman(C) && C.m_intent == MOVE_INTENT_WALK)
- v /= 2
- e -= 5
- steps++
-
- if(steps >= 3)
- steps = 0
-
- else
- return
-
- if(prob(80) && !LM.has_gravity(T)) // don't need to step as often when you hop around
- return
-
- //begin playsound shenanigans//
-
- //for barefooted non-clawed mobs like monkeys
- if(isbarefoot(LM))
- playsound(T, pick(GLOB.barefootstep[T.barefootstep][1]),
- GLOB.barefootstep[T.barefootstep][2] * v,
- TRUE,
- GLOB.barefootstep[T.barefootstep][3] + e)
- return
-
- //for xenomorphs, dogs, and other clawed mobs
- if(isclawfoot(LM))
- if(isalienadult(LM)) //xenos are stealthy and get quieter footsteps
- v /= 3
- e -= 5
-
- playsound(T, pick(GLOB.clawfootstep[T.clawfootstep][1]),
- GLOB.clawfootstep[T.clawfootstep][2] * v,
- TRUE,
- GLOB.clawfootstep[T.clawfootstep][3] + e)
- return
-
- //for megafauna and other large and imtimidating mobs such as the bloodminer
- if(isheavyfoot(LM))
- playsound(T, pick(GLOB.heavyfootstep[T.heavyfootstep][1]),
- GLOB.heavyfootstep[T.heavyfootstep][2] * v,
- TRUE,
- GLOB.heavyfootstep[T.heavyfootstep][3] + e)
- return
-
- //for slimes
- if(isslime(LM))
- playsound(T, 'sound/effects/footstep/slime1.ogg', 15 * v)
- return
-
- //for (simple) humanoid mobs (clowns, russians, pirates, etc.)
- if(isshoefoot(LM))
- if(!ishuman(LM))
- playsound(T, pick(GLOB.footstep[T.footstep][1]),
- GLOB.footstep[T.footstep][2] * v,
- TRUE,
- GLOB.footstep[T.footstep][3] + e)
- return
- if(ishuman(LM)) //for proper humans, they're special
- var/mob/living/carbon/human/H = LM
- var/feetCover = (H.wear_suit && (H.wear_suit.body_parts_covered & FEET)) || (H.w_uniform && (H.w_uniform.body_parts_covered & FEET))
-
- if (H.dna.features["taur"] == "Naga" || H.dna.features["taur"] == "Tentacle") //are we a naga or tentacle taur creature
- playsound(T, 'sound/effects/footstep/crawl1.ogg', 15 * v)
- return
-
- if(H.shoes || feetCover) //are we wearing shoes
- playsound(T, pick(GLOB.footstep[T.footstep][1]),
- GLOB.footstep[T.footstep][2] * v,
- TRUE,
- GLOB.footstep[T.footstep][3] + e)
-
- if((!H.shoes && !feetCover)) //are we NOT wearing shoes
- playsound(T, pick(GLOB.barefootstep[T.barefootstep][1]),
- GLOB.barefootstep[T.barefootstep][2] * v,
- TRUE,
+/datum/component/footstep
+ var/steps = 0
+ var/volume
+ var/e_range
+
+/datum/component/footstep/Initialize(volume_ = 0.5, e_range_ = -1)
+ if(!isliving(parent))
+ return COMPONENT_INCOMPATIBLE
+ volume = volume_
+ e_range = e_range_
+ RegisterSignal(parent, list(COMSIG_MOVABLE_MOVED), .proc/play_footstep)
+
+/datum/component/footstep/proc/play_footstep()
+ var/turf/open/T = get_turf(parent)
+ if(!istype(T))
+ return
+
+ var/mob/living/LM = parent
+ var/v = volume
+ var/e = e_range
+ if(!T.footstep || LM.buckled || LM.lying || !LM.canmove || LM.resting || LM.buckled || LM.throwing || LM.movement_type & (VENTCRAWLING | FLYING))
+ if (LM.lying && !LM.buckled && !(!T.footstep || LM.movement_type & (VENTCRAWLING | FLYING))) //play crawling sound if we're lying
+ playsound(T, 'sound/effects/footstep/crawl1.ogg', 15 * v)
+ return
+
+ if(HAS_TRAIT(LM, TRAIT_SILENT_STEP))
+ return
+
+ if(iscarbon(LM))
+ var/mob/living/carbon/C = LM
+ if(!C.get_bodypart(BODY_ZONE_L_LEG) && !C.get_bodypart(BODY_ZONE_R_LEG))
+ return
+ if(ishuman(C) && C.m_intent == MOVE_INTENT_WALK)
+ v /= 2
+ e -= 5
+ steps++
+
+ if(steps >= 3)
+ steps = 0
+
+ else
+ return
+
+ if(prob(80) && !LM.has_gravity(T)) // don't need to step as often when you hop around
+ return
+
+ //begin playsound shenanigans//
+
+ //for barefooted non-clawed mobs like monkeys
+ if(isbarefoot(LM))
+ playsound(T, pick(GLOB.barefootstep[T.barefootstep][1]),
+ GLOB.barefootstep[T.barefootstep][2] * v,
+ TRUE,
+ GLOB.barefootstep[T.barefootstep][3] + e)
+ return
+
+ //for xenomorphs, dogs, and other clawed mobs
+ if(isclawfoot(LM))
+ if(isalienadult(LM)) //xenos are stealthy and get quieter footsteps
+ v /= 3
+ e -= 5
+
+ playsound(T, pick(GLOB.clawfootstep[T.clawfootstep][1]),
+ GLOB.clawfootstep[T.clawfootstep][2] * v,
+ TRUE,
+ GLOB.clawfootstep[T.clawfootstep][3] + e)
+ return
+
+ //for megafauna and other large and imtimidating mobs such as the bloodminer
+ if(isheavyfoot(LM))
+ playsound(T, pick(GLOB.heavyfootstep[T.heavyfootstep][1]),
+ GLOB.heavyfootstep[T.heavyfootstep][2] * v,
+ TRUE,
+ GLOB.heavyfootstep[T.heavyfootstep][3] + e)
+ return
+
+ //for slimes
+ if(isslime(LM))
+ playsound(T, 'sound/effects/footstep/slime1.ogg', 15 * v)
+ return
+
+ //for (simple) humanoid mobs (clowns, russians, pirates, etc.)
+ if(isshoefoot(LM))
+ if(!ishuman(LM))
+ playsound(T, pick(GLOB.footstep[T.footstep][1]),
+ GLOB.footstep[T.footstep][2] * v,
+ TRUE,
+ GLOB.footstep[T.footstep][3] + e)
+ return
+ if(ishuman(LM)) //for proper humans, they're special
+ var/mob/living/carbon/human/H = LM
+ var/feetCover = (H.wear_suit && (H.wear_suit.body_parts_covered & FEET)) || (H.w_uniform && (H.w_uniform.body_parts_covered & FEET) || (H.shoes && (H.shoes.body_parts_covered & FEET)))
+
+ if (H.dna.features["taur"] == "Naga" || H.dna.features["taur"] == "Tentacle") //are we a naga or tentacle taur creature
+ playsound(T, 'sound/effects/footstep/crawl1.ogg', 15 * v)
+ return
+
+ if(feetCover) //are we wearing shoes
+ playsound(T, pick(GLOB.footstep[T.footstep][1]),
+ GLOB.footstep[T.footstep][2] * v,
+ TRUE,
+ GLOB.footstep[T.footstep][3] + e)
+
+ if(!feetCover) //are we NOT wearing shoes
+ playsound(T, pick(GLOB.barefootstep[T.barefootstep][1]),
+ GLOB.barefootstep[T.barefootstep][2] * v,
+ TRUE,
GLOB.barefootstep[T.barefootstep][3] + e)
\ No newline at end of file
diff --git a/code/datums/components/mood.dm b/code/datums/components/mood.dm
index b32921a4ce..eb381af577 100644
--- a/code/datums/components/mood.dm
+++ b/code/datums/components/mood.dm
@@ -6,6 +6,7 @@
var/sanity = 100 //Current sanity
var/shown_mood //Shown happiness, this is what others can see when they try to examine you, prevents antag checking by noticing traitors are always very happy.
var/mood_level = 5 //To track what stage of moodies they're on
+ var/sanity_level = 5 //To track what stage of sanity they're on
var/mood_modifier = 1 //Modifier to allow certain mobs to be less affected by moodlets
var/datum/mood_event/list/mood_events = list()
var/insanity_effect = 0 //is the owner being punished for low mood? If so, how much?
@@ -20,6 +21,8 @@
RegisterSignal(parent, COMSIG_ADD_MOOD_EVENT, .proc/add_event)
RegisterSignal(parent, COMSIG_CLEAR_MOOD_EVENT, .proc/clear_event)
+ RegisterSignal(parent, COMSIG_INCREASE_SANITY, .proc/IncreaseSanity)
+ RegisterSignal(parent, COMSIG_DECREASE_SANITY, .proc/DecreaseSanity)
RegisterSignal(parent, COMSIG_MOB_HUD_CREATED, .proc/modify_hud)
var/mob/living/owner = parent
@@ -118,6 +121,8 @@
if(owner.client && owner.hud_used)
if(sanity < 25)
screen_obj.icon_state = "mood_insane"
+ else if (owner.has_status_effect(/datum/status_effect/chem/enthrall))//Fermichem enthral chem, maybe change?
+ screen_obj.icon_state = "mood_entrance"
else
screen_obj.icon_state = "mood[mood_level]"
@@ -126,23 +131,23 @@
switch(mood_level)
if(1)
- DecreaseSanity(0.2)
+ DecreaseSanity(src, 0.2)
if(2)
- DecreaseSanity(0.125, SANITY_CRAZY)
+ DecreaseSanity(src, 0.125, SANITY_CRAZY)
if(3)
- DecreaseSanity(0.075, SANITY_UNSTABLE)
+ DecreaseSanity(src, 0.075, SANITY_UNSTABLE)
if(4)
- DecreaseSanity(0.025, SANITY_DISTURBED)
+ DecreaseSanity(src, 0.025, SANITY_DISTURBED)
if(5)
- IncreaseSanity(0.1)
+ IncreaseSanity(src, 0.1)
if(6)
- IncreaseSanity(0.15)
+ IncreaseSanity(src, 0.15)
if(7)
- IncreaseSanity(0.20)
+ IncreaseSanity(src, 0.20)
if(8)
- IncreaseSanity(0.25, SANITY_GREAT)
+ IncreaseSanity(src, 0.25, SANITY_GREAT)
if(9)
- IncreaseSanity(0.4, SANITY_GREAT)
+ IncreaseSanity(src, 0.4, SANITY_GREAT)
if(insanity_effect != holdmyinsanityeffect)
if(insanity_effect > holdmyinsanityeffect)
@@ -163,9 +168,61 @@
HandleNutrition(owner)
-/datum/component/mood/proc/DecreaseSanity(amount, minimum = SANITY_INSANE)
+/datum/component/mood/proc/setSanity(amount, minimum=SANITY_INSANE, maximum=SANITY_NEUTRAL)//I'm sure bunging this in here will have no negative repercussions.
+ var/mob/living/master = parent
+
+ if(amount == sanity)
+ return
+ // If we're out of the acceptable minimum-maximum range move back towards it in steps of 0.5
+ // If the new amount would move towards the acceptable range faster then use it instead
+ if(sanity < minimum && amount < sanity + 0.5)
+ amount = sanity + 0.5
+ else if(sanity > maximum && amount > sanity - 0.5)
+ amount = sanity - 0.5
+
+ // Disturbed stops you from getting any more sane
+ if(HAS_TRAIT(master, TRAIT_UNSTABLE))
+ sanity = min(amount,sanity)
+ else
+ sanity = amount
+
+ switch(sanity)
+ if(SANITY_INSANE to SANITY_CRAZY)
+ setInsanityEffect(MAJOR_INSANITY_PEN)
+ master.add_movespeed_modifier(MOVESPEED_ID_SANITY, TRUE, 100, override=TRUE, multiplicative_slowdown=1.5) //Did we change something ? movetypes is runtiming, movetypes=(~FLYING))
+ sanity_level = 6
+ if(SANITY_CRAZY to SANITY_UNSTABLE)
+ setInsanityEffect(MINOR_INSANITY_PEN)
+ master.add_movespeed_modifier(MOVESPEED_ID_SANITY, TRUE, 100, override=TRUE, multiplicative_slowdown=1)//, movetypes=(~FLYING))
+ sanity_level = 5
+ if(SANITY_UNSTABLE to SANITY_DISTURBED)
+ setInsanityEffect(0)
+ master.add_movespeed_modifier(MOVESPEED_ID_SANITY, TRUE, 100, override=TRUE, multiplicative_slowdown=0.5)//, movetypes=(~FLYING))
+ sanity_level = 4
+ if(SANITY_DISTURBED to SANITY_NEUTRAL)
+ setInsanityEffect(0)
+ master.remove_movespeed_modifier(MOVESPEED_ID_SANITY, TRUE)
+ sanity_level = 3
+ if(SANITY_NEUTRAL+1 to SANITY_GREAT+1) //shitty hack but +1 to prevent it from responding to super small differences
+ setInsanityEffect(0)
+ master.remove_movespeed_modifier(MOVESPEED_ID_SANITY, TRUE)
+ sanity_level = 2
+ if(SANITY_GREAT+1 to INFINITY)
+ setInsanityEffect(0)
+ master.remove_movespeed_modifier(MOVESPEED_ID_SANITY, TRUE)
+ sanity_level = 1
+ //update_mood_icon()
+
+/datum/component/mood/proc/setInsanityEffect(newval)//More code so that the previous proc works
+ if(newval == insanity_effect)
+ return
+ var/mob/living/master = parent
+ master.crit_threshold = (master.crit_threshold - insanity_effect) + newval
+ insanity_effect = newval
+
+/datum/component/mood/proc/DecreaseSanity(datum/source, amount, minimum = SANITY_INSANE)
if(sanity < minimum) //This might make KevinZ stop fucking pinging me.
- IncreaseSanity(0.5)
+ IncreaseSanity(src, 0.5)
else
sanity = max(minimum, sanity - amount)
if(sanity < SANITY_UNSTABLE)
@@ -174,9 +231,13 @@
else
insanity_effect = (MINOR_INSANITY_PEN)
-/datum/component/mood/proc/IncreaseSanity(amount, maximum = SANITY_NEUTRAL)
+/datum/component/mood/proc/IncreaseSanity(datum/source, amount, maximum = SANITY_NEUTRAL)
+ // Disturbed stops you from getting any more sane - I'm just gonna bung this in here
+ var/mob/living/owner = parent
+ if(HAS_TRAIT(owner, TRAIT_UNSTABLE))
+ return
if(sanity > maximum)
- DecreaseSanity(0.5) //Removes some sanity to go back to our current limit.
+ DecreaseSanity(src, 0.5) //Removes some sanity to go back to our current limit.
else
sanity = min(maximum, sanity + amount)
if(sanity > SANITY_CRAZY)
@@ -195,7 +256,7 @@
if(the_event.timeout)
addtimer(CALLBACK(src, .proc/clear_event, null, category), the_event.timeout, TIMER_UNIQUE|TIMER_OVERRIDE)
return 0 //Don't have to update the event.
- the_event = new type(src, param)
+ the_event = new type(src, param)//This causes a runtime for some reason, was this me? No - there's an event floating around missing a definition.
mood_events[category] = the_event
update_mood()
@@ -203,6 +264,8 @@
if(the_event.timeout)
addtimer(CALLBACK(src, .proc/clear_event, null, category), the_event.timeout, TIMER_UNIQUE|TIMER_OVERRIDE)
+ return the_event
+
/datum/component/mood/proc/clear_event(datum/source, category)
var/datum/mood_event/event = mood_events[category]
if(!event)
diff --git a/code/datums/components/nanites.dm b/code/datums/components/nanites.dm
index 426855f887..3f7f794435 100644
--- a/code/datums/components/nanites.dm
+++ b/code/datums/components/nanites.dm
@@ -2,10 +2,10 @@
dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS
var/mob/living/host_mob
- var/nanite_volume = 100 //amount of nanites in the system, used as fuel for nanite programs
- var/max_nanites = 500 //maximum amount of nanites in the system
+ var/nanite_volume = 50 //amount of nanites in the system, used as fuel for nanite programs
+ var/max_nanites = 250 //maximum amount of nanites in the system
var/regen_rate = 0.5 //nanites generated per second
- var/safety_threshold = 50 //how low nanites will get before they stop processing/triggering
+ var/safety_threshold = 25 //how low nanites will get before they stop processing/triggering
var/cloud_id = 0 //0 if not connected to the cloud, 1-100 to set a determined cloud backup to draw from
var/next_sync = 0
var/list/datum/nanite_program/programs = list()
@@ -46,9 +46,9 @@
RegisterSignal(parent, COMSIG_NANITE_ADD_PROGRAM, .proc/add_program)
RegisterSignal(parent, COMSIG_NANITE_SCAN, .proc/nanite_scan)
RegisterSignal(parent, COMSIG_NANITE_SYNC, .proc/sync)
+ RegisterSignal(parent, COMSIG_ATOM_EMP_ACT, .proc/on_emp)
if(isliving(parent))
- RegisterSignal(parent, COMSIG_ATOM_EMP_ACT, .proc/on_emp)
RegisterSignal(parent, COMSIG_MOB_DEATH, .proc/on_death)
RegisterSignal(parent, COMSIG_MOB_ALLOWED, .proc/check_access)
RegisterSignal(parent, COMSIG_LIVING_ELECTROCUTE_ACT, .proc/on_shock)
@@ -169,23 +169,19 @@
holder.icon_state = "nanites[nanite_percent]"
/datum/component/nanites/proc/on_emp(datum/source, severity)
- nanite_volume *= (rand(0.60, 0.90)) //Lose 10-40% of nanites
- adjust_nanites(null, -(rand(5, 50))) //Lose 5-50 flat nanite volume
- if(prob(40/severity))
- cloud_id = 0
+ adjust_nanites(null, -(nanite_volume * 0.3 + 50)) //Lose 30% variable and 50 flat nanite volume.
for(var/X in programs)
var/datum/nanite_program/NP = X
NP.on_emp(severity)
/datum/component/nanites/proc/on_shock(datum/source, shock_damage)
- nanite_volume *= (rand(0.45, 0.80)) //Lose 20-55% of nanites
- adjust_nanites(null, -(rand(5, 50))) //Lose 5-50 flat nanite volume
+ adjust_nanites(null, -(nanite_volume * (shock_damage * 0.005) + shock_damage)) //0.5% of shock damage (@ 50 damage it'd drain 25%) + shock damage flat volume
for(var/X in programs)
var/datum/nanite_program/NP = X
NP.on_shock(shock_damage)
/datum/component/nanites/proc/on_minor_shock(datum/source)
- adjust_nanites(null, -(rand(5, 15))) //Lose 5-15 flat nanite volume
+ adjust_nanites(null, -25)
for(var/X in programs)
var/datum/nanite_program/NP = X
NP.on_minor_shock()
@@ -311,4 +307,4 @@
mob_program["trigger_code"] = P.trigger_code
id++
mob_programs += list(mob_program)
- data["mob_programs"] = mob_programs
\ No newline at end of file
+ data["mob_programs"] = mob_programs
diff --git a/code/datums/components/riding.dm b/code/datums/components/riding.dm
index 7b80f87657..0af90f694c 100644
--- a/code/datums/components/riding.dm
+++ b/code/datums/components/riding.dm
@@ -162,12 +162,12 @@
if(!Process_Spacemove(direction) || !isturf(AM.loc))
return
step(AM, direction)
-
+
if((direction & (direction - 1)) && (AM.loc == next)) //moved diagonally
last_move_diagonal = TRUE
else
last_move_diagonal = FALSE
-
+
handle_vehicle_layer()
handle_vehicle_offsets()
else
@@ -195,21 +195,47 @@
. = ..()
RegisterSignal(parent, COMSIG_HUMAN_MELEE_UNARMED_ATTACK, .proc/on_host_unarmed_melee)
+/datum/component/riding/human/vehicle_mob_unbuckle(datum/source, mob/living/M, force = FALSE)
+ var/mob/living/carbon/human/H = parent
+ H.remove_movespeed_modifier(MOVESPEED_ID_HUMAN_CARRYING)
+ . = ..()
+
+/datum/component/riding/human/vehicle_mob_buckle(datum/source, mob/living/M, force = FALSE)
+ . = ..()
+ var/mob/living/carbon/human/H = parent
+ H.add_movespeed_modifier(MOVESPEED_ID_HUMAN_CARRYING, multiplicative_slowdown = HUMAN_CARRY_SLOWDOWN)
+
/datum/component/riding/human/proc/on_host_unarmed_melee(atom/target)
- var/mob/living/carbon/human/AM = parent
- if(AM.a_intent == INTENT_DISARM && (target in AM.buckled_mobs))
+ var/mob/living/carbon/human/H = parent
+ if(H.a_intent == INTENT_DISARM && (target in H.buckled_mobs))
force_dismount(target)
/datum/component/riding/human/handle_vehicle_layer()
var/atom/movable/AM = parent
if(AM.buckled_mobs && AM.buckled_mobs.len)
- if(AM.dir == SOUTH)
- AM.layer = ABOVE_MOB_LAYER
+ for(var/mob/M in AM.buckled_mobs) //ensure proper layering of piggyback and carry, sometimes weird offsets get applied
+ M.layer = MOB_LAYER
+ if(!AM.buckle_lying)
+ if(AM.dir == SOUTH)
+ AM.layer = ABOVE_MOB_LAYER
+ else
+ AM.layer = OBJ_LAYER
else
- AM.layer = OBJ_LAYER
+ if(AM.dir == NORTH)
+ AM.layer = OBJ_LAYER
+ else
+ AM.layer = ABOVE_MOB_LAYER
else
AM.layer = MOB_LAYER
+/datum/component/riding/human/get_offsets(pass_index)
+ var/mob/living/carbon/human/H = parent
+ if(H.buckle_lying)
+ return list(TEXT_NORTH = list(0, 6), TEXT_SOUTH = list(0, 6), TEXT_EAST = list(0, 6), TEXT_WEST = list(0, 6))
+ else
+ return list(TEXT_NORTH = list(0, 6), TEXT_SOUTH = list(0, 6), TEXT_EAST = list(-6, 4), TEXT_WEST = list( 6, 4))
+
+
/datum/component/riding/human/force_dismount(mob/living/user)
var/atom/movable/AM = parent
AM.unbuckle_mob(user)
@@ -273,12 +299,15 @@
M.throw_at(target, 14, 5, AM)
M.Knockdown(60)
-/datum/component/riding/proc/equip_buckle_inhands(mob/living/carbon/human/user, amount_required = 1)
+/datum/component/riding/proc/equip_buckle_inhands(mob/living/carbon/human/user, amount_required = 1, riding_target_override = null)
var/atom/movable/AM = parent
var/amount_equipped = 0
for(var/amount_needed = amount_required, amount_needed > 0, amount_needed--)
var/obj/item/riding_offhand/inhand = new /obj/item/riding_offhand(user)
- inhand.rider = user
+ if(!riding_target_override)
+ inhand.rider = user
+ else
+ inhand.rider = riding_target_override
inhand.parent = AM
if(user.put_in_hands(inhand, TRUE))
amount_equipped++
@@ -318,7 +347,7 @@
. = ..()
/obj/item/riding_offhand/equipped()
- if(loc != rider)
+ if(loc != rider && loc != parent)
selfdeleting = TRUE
qdel(src)
. = ..()
diff --git a/code/datums/components/spooky.dm b/code/datums/components/spooky.dm
index 8989cb499d..1d5549d0fe 100644
--- a/code/datums/components/spooky.dm
+++ b/code/datums/components/spooky.dm
@@ -27,7 +27,7 @@
C.stuttering = 20
if((!istype(H.dna.species, /datum/species/skeleton)) && (!istype(H.dna.species, /datum/species/golem)) && (!istype(H.dna.species, /datum/species/android)) && (!istype(H.dna.species, /datum/species/jelly)))
C.adjustStaminaLoss(25) //boneless humanoids don't lose the will to live
- to_chat(C, "DOOT")
+ to_chat(C, "DOOT")
spectral_change(H)
else //the sound will spook monkeys.
diff --git a/code/datums/components/storage/concrete/emergency.dm b/code/datums/components/storage/concrete/emergency.dm
index 48880ff605..348821a913 100644
--- a/code/datums/components/storage/concrete/emergency.dm
+++ b/code/datums/components/storage/concrete/emergency.dm
@@ -36,3 +36,4 @@
/datum/component/storage/concrete/emergency/proc/unlock_me(datum/source)
if(locked)
set_locked(source, FALSE)
+ return TRUE
diff --git a/code/datums/components/storage/concrete/pockets.dm b/code/datums/components/storage/concrete/pockets.dm
index e0a23b0209..84be4fdca4 100644
--- a/code/datums/components/storage/concrete/pockets.dm
+++ b/code/datums/components/storage/concrete/pockets.dm
@@ -52,7 +52,7 @@
. = ..()
cant_hold = typecacheof(list(/obj/item/screwdriver/power))
can_hold = typecacheof(list(
- /obj/item/kitchen/knife, /obj/item/switchblade, /obj/item/pen,
+ /obj/item/kitchen/knife, /obj/item/switchblade, /obj/item/pen, /obj/item/melee/cultblade/dagger,
/obj/item/scalpel, /obj/item/reagent_containers/syringe, /obj/item/dnainjector,
/obj/item/reagent_containers/hypospray/medipen, /obj/item/reagent_containers/dropper,
/obj/item/implanter, /obj/item/screwdriver, /obj/item/weldingtool/mini,
@@ -63,7 +63,7 @@
. = ..()
cant_hold = typecacheof(list(/obj/item/screwdriver/power))
can_hold = typecacheof(list(
- /obj/item/kitchen/knife, /obj/item/switchblade, /obj/item/pen,
+ /obj/item/kitchen/knife, /obj/item/switchblade, /obj/item/pen, /obj/item/melee/cultblade/dagger,
/obj/item/scalpel, /obj/item/reagent_containers/syringe, /obj/item/dnainjector,
/obj/item/reagent_containers/hypospray/medipen, /obj/item/reagent_containers/dropper,
/obj/item/implanter, /obj/item/screwdriver, /obj/item/weldingtool/mini,
diff --git a/code/datums/components/storage/storage.dm b/code/datums/components/storage/storage.dm
index 52d0036154..adec6f67ce 100644
--- a/code/datums/components/storage/storage.dm
+++ b/code/datums/components/storage/storage.dm
@@ -234,7 +234,7 @@
/datum/component/storage/proc/quick_empty(mob/M)
var/atom/A = parent
- if((!ishuman(M) && (A.loc != M)) || (M.stat != CONSCIOUS) || M.restrained() || !M.canmove)
+ if(!M.canUseStorage() || !A.Adjacent(M) || M.incapacitated())
return
if(check_locked(null, M, TRUE))
return FALSE
@@ -592,7 +592,7 @@
if(!stop_messages)
to_chat(M, "[IP] cannot hold [I] as it's a storage item of the same size!")
return FALSE //To prevent the stacking of same sized storage items.
- if(I.item_flags & NODROP) //SHOULD be handled in unEquip, but better safe than sorry.
+ if(HAS_TRAIT(I, TRAIT_NODROP)) //SHOULD be handled in unEquip, but better safe than sorry.
to_chat(M, "\the [I] is stuck to your hand, you can't put it in \the [host]!")
return FALSE
var/datum/component/storage/concrete/master = master()
diff --git a/code/datums/components/uplink.dm b/code/datums/components/uplink.dm
index 8982530ab6..2c3b7518ba 100644
--- a/code/datums/components/uplink.dm
+++ b/code/datums/components/uplink.dm
@@ -24,8 +24,9 @@ GLOBAL_LIST_EMPTY(uplinks)
var/unlock_note
var/unlock_code
var/failsafe_code
+ var/datum/ui_state/checkstate
-/datum/component/uplink/Initialize(_owner, _lockable = TRUE, _enabled = FALSE, datum/game_mode/_gamemode, starting_tc = 20)
+/datum/component/uplink/Initialize(_owner, _lockable = TRUE, _enabled = FALSE, datum/game_mode/_gamemode, starting_tc = 20, datum/ui_state/_checkstate)
if(!isitem(parent))
return COMPONENT_INCOMPATIBLE
@@ -57,6 +58,7 @@ GLOBAL_LIST_EMPTY(uplinks)
active = _enabled
gamemode = _gamemode
telecrystals = starting_tc
+ checkstate = _checkstate
if(!lockable)
active = TRUE
locked = FALSE
@@ -115,6 +117,7 @@ GLOBAL_LIST_EMPTY(uplinks)
/datum/component/uplink/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \
datum/tgui/master_ui = null, datum/ui_state/state = GLOB.inventory_state)
+ state = checkstate ? checkstate : state
active = TRUE
ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
if(!ui)
@@ -123,6 +126,12 @@ GLOBAL_LIST_EMPTY(uplinks)
ui.set_style("syndicate")
ui.open()
+/datum/component/uplink/ui_host(mob/user)
+ if(istype(parent, /obj/item/implant)) //implants are like organs, not really located inside mobs codewise.
+ var/obj/item/implant/I = parent
+ return I.imp_in
+ return ..()
+
/datum/component/uplink/ui_data(mob/user)
if(!user.mind)
return
diff --git a/code/datums/components/virtual_reality.dm b/code/datums/components/virtual_reality.dm
new file mode 100644
index 0000000000..750cc045ce
--- /dev/null
+++ b/code/datums/components/virtual_reality.dm
@@ -0,0 +1,128 @@
+/datum/component/virtual_reality
+ dupe_mode = COMPONENT_DUPE_ALLOWED //mindswap memes, shouldn't stack up otherwise.
+ var/datum/mind/mastermind // where is my mind t. pixies
+ var/datum/mind/current_mind
+ var/obj/machinery/vr_sleeper/vr_sleeper
+ var/datum/action/quit_vr/quit_action
+ var/you_die_in_the_game_you_die_for_real = FALSE
+ var/datum/component/virtual_reality/inception //The component works on a very fragile link betwixt mind, ckey and death.
+
+/datum/component/virtual_reality/Initialize(mob/M, obj/machinery/vr_sleeper/gaming_pod, yolo = FALSE, new_char = TRUE)
+ if(!ismob(parent) || !istype(M))
+ return COMPONENT_INCOMPATIBLE
+ var/mob/vr_M = parent
+ mastermind = M.mind
+ RegisterSignal(M, list(COMSIG_MOB_DEATH, COMSIG_PARENT_QDELETED), .proc/game_over)
+ RegisterSignal(M, COMSIG_MOB_KEY_CHANGE, .proc/switch_player)
+ RegisterSignal(mastermind, COMSIG_MIND_TRANSFER, .proc/switch_player)
+ you_die_in_the_game_you_die_for_real = yolo
+ quit_action = new()
+ if(gaming_pod)
+ vr_sleeper = gaming_pod
+ RegisterSignal(vr_sleeper, COMSIG_ATOM_EMAG_ACT, .proc/you_only_live_once)
+ RegisterSignal(vr_sleeper, COMSIG_MACHINE_EJECT_OCCUPANT, .proc/revert_to_reality)
+ vr_M.ckey = M.ckey
+ var/datum/component/virtual_reality/clusterfk = M.GetComponent(/datum/component/virtual_reality)
+ if(clusterfk && !clusterfk.inception)
+ clusterfk.inception = src
+ SStgui.close_user_uis(M, src)
+
+/datum/component/virtual_reality/RegisterWithParent()
+ var/mob/M = parent
+ current_mind = M.mind
+ quit_action.Grant(M)
+ RegisterSignal(quit_action, COMSIG_ACTION_TRIGGER, .proc/revert_to_reality)
+ RegisterSignal(M, list(COMSIG_MOB_DEATH, COMSIG_PARENT_QDELETED), .proc/game_over)
+ RegisterSignal(M, COMSIG_MOB_GHOSTIZE, .proc/be_a_quitter)
+ RegisterSignal(M, COMSIG_MOB_KEY_CHANGE, .proc/pass_me_the_remote)
+ RegisterSignal(current_mind, COMSIG_MIND_TRANSFER, .proc/pass_me_the_remote)
+ mastermind.current.audiovisual_redirect = M
+ if(vr_sleeper)
+ vr_sleeper.vr_mob = M
+
+/datum/component/virtual_reality/UnregisterFromParent()
+ quit_action.Remove(parent)
+ UnregisterSignal(parent, list(COMSIG_MOB_DEATH, COMSIG_PARENT_QDELETED, COMSIG_MOB_KEY_CHANGE, COMSIG_MOB_GHOSTIZE))
+ UnregisterSignal(current_mind, COMSIG_MIND_TRANSFER)
+ UnregisterSignal(quit_action, COMSIG_ACTION_TRIGGER)
+ current_mind = null
+ mastermind.current.audiovisual_redirect = null
+
+/datum/component/virtual_reality/proc/switch_player(datum/source, mob/new_mob, mob/old_mob)
+ if(vr_sleeper || !new_mob.mind)
+ // Machineries currently don't deal up with the occupant being polymorphed et similar... Or did something fuck up?
+ revert_to_reality()
+ return
+ old_mob.audiovisual_redirect = null
+ new_mob.audiovisual_redirect = parent
+
+/datum/component/virtual_reality/proc/action_trigger(datum/signal_source, datum/action/source)
+ if(source != quit_action)
+ return COMPONENT_ACTION_BLOCK_TRIGGER
+ revert_to_reality(signal_source)
+
+/datum/component/virtual_reality/proc/you_only_live_once()
+ if(you_die_in_the_game_you_die_for_real || vr_sleeper?.only_current_user_can_interact)
+ return FALSE
+ you_die_in_the_game_you_die_for_real = TRUE
+ return TRUE
+
+/datum/component/virtual_reality/proc/pass_me_the_remote(datum/source, mob/new_mob)
+ if(new_mob == mastermind.current)
+ revert_to_reality(source)
+ return TRUE
+ new_mob.TakeComponent(src)
+ return TRUE
+
+/datum/component/virtual_reality/PostTransfer()
+ if(!ismob(parent))
+ return COMPONENT_INCOMPATIBLE
+
+/datum/component/virtual_reality/proc/revert_to_reality(datum/source)
+ quit_it()
+
+/datum/component/virtual_reality/proc/game_over(datum/source)
+ quit_it(TRUE, TRUE)
+
+/datum/component/virtual_reality/proc/be_a_quitter(datum/source, can_reenter_corpse)
+ quit_it()
+ return COMPONENT_BLOCK_GHOSTING
+
+/datum/component/virtual_reality/proc/virtual_reality_in_a_virtual_reality(mob/player, killme = FALSE, datum/component/virtual_reality/yo_dawg)
+ var/mob/M = parent
+ quit_it(FALSE, killme, player, yo_dawg)
+ yo_dawg.inception = null
+ if(killme)
+ M.death(FALSE)
+
+/datum/component/virtual_reality/proc/quit_it(deathcheck = FALSE, cleanup = FALSE, mob/override)
+ var/mob/M = parent
+ var/mob/dreamer = override ? override : mastermind.current
+ if(!mastermind)
+ to_chat(M, "You feel a dreadful sensation, something terrible happened. You try to wake up, but you find yourself unable to...")
+ else
+ var/key_transfer = FALSE
+ if(inception?.parent)
+ inception.virtual_reality_in_a_virtual_reality(dreamer, cleanup, src)
+ else
+ key_transfer = TRUE
+ if(key_transfer)
+ M.transfer_ckey(dreamer, FALSE)
+ dreamer.stop_sound_channel(CHANNEL_HEARTBEAT)
+ dreamer.audiovisual_redirect = null
+ if(deathcheck && you_die_in_the_game_you_die_for_real)
+ to_chat(mastermind, "You feel everything fading away...")
+ dreamer.death(FALSE)
+ if(cleanup)
+ var/obj/effect/vr_clean_master/cleanbot = locate() in get_area(M)
+ if(cleanbot)
+ LAZYADD(cleanbot.corpse_party, M)
+ if(vr_sleeper)
+ vr_sleeper.vr_mob = null
+ vr_sleeper = null
+ qdel(src)
+
+/datum/component/virtual_reality/Destroy()
+ var/datum/action/quit_vr/delet_me = quit_action
+ . = ..()
+ qdel(delet_me)
\ No newline at end of file
diff --git a/code/datums/datumvars.dm b/code/datums/datumvars.dm
index d5c983bc39..cfbd8be32c 100644
--- a/code/datums/datumvars.dm
+++ b/code/datums/datumvars.dm
@@ -94,10 +94,23 @@
TOXIN:[M.getToxLoss()]
OXY:[M.getOxyLoss()]
CLONE:[M.getCloneLoss()]
- BRAIN:[M.getBrainLoss()]
+ BRAIN:[M.getOrganLoss(ORGAN_SLOT_BRAIN)]
STAMINA:[M.getStaminaLoss()]
AROUSAL:[M.getArousalLoss()]
-
+ "}
+ if(GLOB.Debug2)
+ atomsnowflake += {"
+ HEART:[M.getOrganLoss(ORGAN_SLOT_HEART)]
+ LIVER:[M.getOrganLoss(ORGAN_SLOT_LIVER)]
+ LUNGS:[M.getOrganLoss(ORGAN_SLOT_LUNGS)]
+ EYES:[M.getOrganLoss(ORGAN_SLOT_EYES)]
+ EARS:[M.getOrganLoss(ORGAN_SLOT_EARS)]
+ STOMACH:[M.getOrganLoss(ORGAN_SLOT_STOMACH)]
+ TONGUE:[M.getOrganLoss(ORGAN_SLOT_TONGUE)]
+ APPENDIX:[M.getOrganLoss(ORGAN_SLOT_APPENDIX)]
+ "}
+ atomsnowflake += {"
+
"}
else
atomsnowflake += "[D]"
@@ -1334,8 +1347,8 @@
L.adjustOxyLoss(amount)
newamt = L.getOxyLoss()
if("brain")
- L.adjustBrainLoss(amount)
- newamt = L.getBrainLoss()
+ L.adjustOrganLoss(ORGAN_SLOT_BRAIN, amount)
+ newamt = L.getOrganLoss(ORGAN_SLOT_BRAIN)
if("clone")
L.adjustCloneLoss(amount)
newamt = L.getCloneLoss()
@@ -1345,14 +1358,38 @@
if("arousal")
L.adjustArousalLoss(amount)
newamt = L.getArousalLoss()
+ if("heart")
+ L.adjustOrganLoss(ORGAN_SLOT_HEART, amount)
+ newamt = L.getOrganLoss(ORGAN_SLOT_HEART)
+ if("liver")
+ L.adjustOrganLoss(ORGAN_SLOT_LIVER, amount)
+ newamt = L.getOrganLoss(ORGAN_SLOT_LIVER)
+ if("lungs")
+ L.adjustOrganLoss(ORGAN_SLOT_LUNGS, amount)
+ newamt = L.getOrganLoss(ORGAN_SLOT_LUNGS)
+ if("eye_sight")
+ L.adjustOrganLoss(ORGAN_SLOT_EYES, amount)
+ newamt = L.getOrganLoss(ORGAN_SLOT_EYES)
+ if("ears")
+ L.adjustOrganLoss(ORGAN_SLOT_EARS, amount)
+ newamt = L.getOrganLoss(ORGAN_SLOT_EARS)
+ if("stomach")
+ L.adjustOrganLoss(ORGAN_SLOT_STOMACH, amount)
+ newamt = L.getOrganLoss(ORGAN_SLOT_STOMACH)
+ if("tongue")
+ L.adjustOrganLoss(ORGAN_SLOT_TONGUE, amount)
+ newamt = L.getOrganLoss(ORGAN_SLOT_TONGUE)
+ if("appendix")
+ L.adjustOrganLoss(ORGAN_SLOT_APPENDIX, amount)
+ newamt = L.getOrganLoss(ORGAN_SLOT_APPENDIX)
else
to_chat(usr, "You caused an error. DEBUG: Text:[Text] Mob:[L]")
return
if(amount != 0)
log_admin("[key_name(usr)] dealt [amount] amount of [Text] damage to [L] ")
- var/msg = "[key_name(usr)] dealt [amount] amount of [Text] damage to [L] "
- message_admins(msg)
+ var/msg = "[key_name(usr)] dealt [amount] amount of [Text] damage to [L] "
+ message_admins("[msg]")
admin_ticket_log(L, msg)
vv_update_display(L, Text, "[newamt]")
else if(href_list["copyoutfit"])
@@ -1360,4 +1397,4 @@
return
var/mob/living/carbon/human/H = locate(href_list["copyoutfit"]) in GLOB.carbon_list
if(istype(H))
- H.copy_outfit()
\ No newline at end of file
+ H.copy_outfit()
diff --git a/code/datums/diseases/_MobProcs.dm b/code/datums/diseases/_MobProcs.dm
index e1432bf9a6..c125a9b7c7 100644
--- a/code/datums/diseases/_MobProcs.dm
+++ b/code/datums/diseases/_MobProcs.dm
@@ -144,3 +144,9 @@
if(!((locate(thing) in bodyparts) || (locate(thing) in internal_organs)))
return FALSE
return ..()
+
+/mob/living/proc/CanSpreadAirborneDisease()
+ return !is_mouth_covered()
+
+/mob/living/carbon/CanSpreadAirborneDisease()
+ return !((head && (head.flags_cover & HEADCOVERSMOUTH) && (head.armor.getRating("bio") >= 25)) || (wear_mask && (wear_mask.flags_cover & MASKCOVERSMOUTH) && (wear_mask.armor.getRating("bio") >= 25)))
\ No newline at end of file
diff --git a/code/datums/diseases/_disease.dm b/code/datums/diseases/_disease.dm
index 0af4eea8ac..8a6966666e 100644
--- a/code/datums/diseases/_disease.dm
+++ b/code/datums/diseases/_disease.dm
@@ -55,6 +55,13 @@
D.after_add()
infectee.med_hud_set_status()
+ var/turf/source_turf = get_turf(infectee)
+ log_virus("[key_name(infectee)] was infected by virus: [src.admin_details()] at [loc_name(source_turf)]")
+
+//Return a string for admin logging uses, should describe the disease in detail
+/datum/disease/proc/admin_details()
+ return "[src.name] : [src.type]"
+
/datum/disease/proc/stage_act()
var/cure = has_cure()
@@ -65,15 +72,17 @@
if(!cure)
if(prob(stage_prob))
- stage = min(stage + 1,max_stages)
+ update_stage(min(stage + 1,max_stages))
else
if(prob(cure_chance))
- stage = max(stage - 1, 1)
+ update_stage(max(stage - 1, 1))
if(disease_flags & CURABLE)
if(cure && prob(cure_chance))
cure()
+/datum/disease/proc/update_stage(new_stage)
+ stage = new_stage
/datum/disease/proc/has_cure()
if(!(disease_flags & CURABLE))
diff --git a/code/datums/diseases/advance/advance.dm b/code/datums/diseases/advance/advance.dm
index f506f44ad5..8d5e915b31 100644
--- a/code/datums/diseases/advance/advance.dm
+++ b/code/datums/diseases/advance/advance.dm
@@ -31,13 +31,43 @@
var/id = ""
var/processing = FALSE
var/mutable = TRUE //set to FALSE to prevent most in-game methods of altering the disease via virology
+ var/oldres //To prevent setting new cures unless resistance changes.
- // The order goes from easy to cure to hard to cure.
+ // The order goes from easy to cure to hard to cure. Keep in mind that sentient diseases pick two cures from tier 6 and up, ensure they wont react away in bodies.
var/static/list/advance_cures = list(
- "sodiumchloride", "sugar", "orangejuice",
- "spaceacillin", "salglu_solution", "ethanol",
- "leporazine", "synaptizine", "lipolicide",
- "silver", "gold"
+ list( // level 1
+ "copper", "silver", "iodine", "iron", "carbon"
+ ),
+ list( // level 2
+ "potassium", "ethanol", "lithium", "silicon", "bromine"
+ ),
+ list( // level 3
+ "sodiumchloride", "sugar", "orangejuice", "tomatojuice", "milk"
+ ),
+ list( //level 4
+ "spaceacillin", "salglu_solution", "epinephrine", "charcoal"
+ ),
+ list( //level 5
+ "oil", "synaptizine", "mannitol", "space_drugs", "cryptobiolin"
+ ),
+ list( // level 6
+ "phenol", "inacusiate", "oculine", "antihol"
+ ),
+ list( // level 7
+ "leporazine", "mindbreaker", "corazone"
+ ),
+ list( // level 8
+ "pax", "happiness", "ephedrine"
+ ),
+ list( // level 9
+ "lipolicide", "sal_acid"
+ ),
+ list( // level 10
+ "haloperidol", "aranesp", "diphenhydramine"
+ ),
+ list( //level 11
+ "modafinil", "anacea"
+ )
)
/*
@@ -80,15 +110,22 @@
return
if(symptoms && symptoms.len)
-
if(!processing)
processing = TRUE
for(var/datum/symptom/S in symptoms)
- S.Start(src)
+ if(S.Start(src)) //this will return FALSE if the symptom is neutered
+ S.next_activation = world.time + rand(S.symptom_delay_min * 10, S.symptom_delay_max * 10)
+ S.on_stage_change(src)
for(var/datum/symptom/S in symptoms)
S.Activate(src)
+// Tell symptoms stage changed
+/datum/disease/advance/update_stage(new_stage)
+ ..()
+ for(var/datum/symptom/S in symptoms)
+ S.on_stage_change(src)
+
// Compares type then ID.
/datum/disease/advance/IsSame(datum/disease/advance/D)
@@ -108,9 +145,18 @@
A.properties = properties.Copy()
A.id = id
A.mutable = mutable
+ A.oldres = oldres
//this is a new disease starting over at stage 1, so processing is not copied
return A
+//Describe this disease to an admin in detail (for logging)
+/datum/disease/advance/admin_details()
+ var/list/name_symptoms = list()
+ for(var/datum/symptom/S in symptoms)
+ name_symptoms += S.name
+ return "[name] sym:[english_list(name_symptoms)] r:[totalResistance()] s:[totalStealth()] ss:[totalStageSpeed()] t:[totalTransmittable()]"
+
+
/*
NEW PROCS
@@ -161,6 +207,10 @@
/datum/disease/advance/proc/Refresh(new_name = FALSE)
GenerateProperties()
AssignProperties()
+ if(processing && symptoms && symptoms.len)
+ for(var/datum/symptom/S in symptoms)
+ S.Start(src)
+ S.on_stage_change(src)
id = null
var/the_id = GetDiseaseID()
@@ -250,7 +300,10 @@
/datum/disease/advance/proc/GenerateCure()
if(properties && properties.len)
var/res = CLAMP(properties["resistance"] - (symptoms.len / 2), 1, advance_cures.len)
- cures = list(advance_cures[res])
+ if(res == oldres)
+ return
+ cures = list(pick(advance_cures[res]))
+ oldres = res
// Get the cure name from the cure_id
var/datum/reagent/D = GLOB.chemical_reagents_list[cures[1]]
@@ -309,28 +362,28 @@
return id
-// Add a symptom, if it is over the limit (with a small chance to be able to go over)
-// we take a random symptom away and add the new one.
+// Add a symptom, if it is over the limit we take a random symptom away and add the new one.
/datum/disease/advance/proc/AddSymptom(datum/symptom/S)
if(HasSymptom(S))
return
- if(symptoms.len < (VIRUS_SYMPTOM_LIMIT - 1) + rand(-1, 1))
- symptoms += S
- else
+ if(!(symptoms.len < (VIRUS_SYMPTOM_LIMIT - 1) + rand(-1, 1)))
RemoveSymptom(pick(symptoms))
- symptoms += S
+ symptoms += S
+ S.OnAdd(src)
// Simply removes the symptom.
/datum/disease/advance/proc/RemoveSymptom(datum/symptom/S)
symptoms -= S
+ S.OnRemove(src)
// Neuter a symptom, so it will only affect stats
/datum/disease/advance/proc/NeuterSymptom(datum/symptom/S)
if(!S.neutered)
S.neutered = TRUE
S.name += " (neutered)"
+ S.OnRemove(src)
/*
@@ -384,7 +437,7 @@
var/i = VIRUS_SYMPTOM_LIMIT
- var/datum/disease/advance/D = new(0, null)
+ var/datum/disease/advance/D = new()
D.symptoms = list()
var/list/symptoms = list()
@@ -412,9 +465,6 @@
D.AssignName(new_name)
D.Refresh()
- for(var/datum/disease/advance/AD in SSdisease.active_diseases)
- AD.Refresh()
-
for(var/mob/living/carbon/human/H in shuffle(GLOB.alive_mob_list))
if(!is_station_level(H.z))
continue
@@ -425,8 +475,8 @@
var/list/name_symptoms = list()
for(var/datum/symptom/S in D.symptoms)
name_symptoms += S.name
- message_admins("[key_name_admin(user)] has triggered a custom virus outbreak of [D.name]! It has these symptoms: [english_list(name_symptoms)]")
-
+ message_admins("[key_name_admin(user)] has triggered a custom virus outbreak of [D.admin_details()]")
+ log_virus("[key_name(user)] has triggered a custom virus outbreak of [D.admin_details()]!")
/datum/disease/advance/proc/totalStageSpeed()
return properties["stage_rate"]
diff --git a/code/datums/diseases/advance/symptoms/choking.dm b/code/datums/diseases/advance/symptoms/choking.dm
index 81cdad8042..a54b132de1 100644
--- a/code/datums/diseases/advance/symptoms/choking.dm
+++ b/code/datums/diseases/advance/symptoms/choking.dm
@@ -144,5 +144,5 @@ Bonus
/datum/symptom/asphyxiation/proc/Asphyxiate_death(mob/living/M, datum/disease/advance/A)
var/get_damage = rand(25,35) * power
M.adjustOxyLoss(get_damage)
- M.adjustBrainLoss(get_damage/2)
+ M.adjustOrganLoss(ORGAN_SLOT_BRAIN, get_damage/2)
return 1
diff --git a/code/datums/diseases/advance/symptoms/confusion.dm b/code/datums/diseases/advance/symptoms/confusion.dm
index e7315c6bb1..260c6888d7 100644
--- a/code/datums/diseases/advance/symptoms/confusion.dm
+++ b/code/datums/diseases/advance/symptoms/confusion.dm
@@ -55,7 +55,7 @@ Bonus
to_chat(M, "You can't think straight!")
M.confused = min(100 * power, M.confused + 8)
if(brain_damage)
- M.adjustBrainLoss(3 * power, 80)
+ M.adjustOrganLoss(ORGAN_SLOT_BRAIN, 3 * power, 80)
M.updatehealth()
return
diff --git a/code/datums/diseases/advance/symptoms/cough.dm b/code/datums/diseases/advance/symptoms/cough.dm
index 83d93c55e0..e4283101f5 100644
--- a/code/datums/diseases/advance/symptoms/cough.dm
+++ b/code/datums/diseases/advance/symptoms/cough.dm
@@ -70,6 +70,6 @@ BONUS
addtimer(CALLBACK(M, /mob/.proc/emote, "cough"), 6)
addtimer(CALLBACK(M, /mob/.proc/emote, "cough"), 12)
addtimer(CALLBACK(M, /mob/.proc/emote, "cough"), 18)
- if(infective)
+ if(infective && M.CanSpreadAirborneDisease())
A.spread(1)
diff --git a/code/datums/diseases/advance/symptoms/deafness.dm b/code/datums/diseases/advance/symptoms/deafness.dm
index cc388f0b59..3718104b48 100644
--- a/code/datums/diseases/advance/symptoms/deafness.dm
+++ b/code/datums/diseases/advance/symptoms/deafness.dm
@@ -50,9 +50,9 @@ Bonus
if(5)
if(power > 2)
var/obj/item/organ/ears/ears = M.getorganslot(ORGAN_SLOT_EARS)
- if(istype(ears) && ears.ear_damage < UNHEALING_EAR_DAMAGE)
+ if(istype(ears) && ears.damage < ears.maxHealth)
to_chat(M, "Your ears pop painfully and start bleeding!")
- ears.ear_damage = max(ears.ear_damage, UNHEALING_EAR_DAMAGE)
+ ears.damage = max(ears.damage, ears.maxHealth)
M.emote("scream")
else
to_chat(M, "Your ears pop and begin ringing loudly!")
diff --git a/code/datums/diseases/advance/symptoms/heal.dm b/code/datums/diseases/advance/symptoms/heal.dm
index e666c7acd6..1ab3456c22 100644
--- a/code/datums/diseases/advance/symptoms/heal.dm
+++ b/code/datums/diseases/advance/symptoms/heal.dm
@@ -219,8 +219,10 @@
level = 8
passive_message = "The pain from your wounds makes you feel oddly sleepy..."
var/deathgasp = FALSE
+ var/stabilize = FALSE
var/active_coma = FALSE //to prevent multiple coma procs
threshold_desc = "Stealth 2: Host appears to die when falling into a coma. \
+ Resistance 4: The virus also stabilizes the host while they are in critical condition. \
Stage Speed 7: Increases healing speed."
/datum/symptom/heal/coma/Start(datum/disease/advance/A)
@@ -228,9 +230,25 @@
return
if(A.properties["stage_rate"] >= 7)
power = 1.5
+ if(A.properties["resistance"] >= 4)
+ stabilize = TRUE
if(A.properties["stealth"] >= 2)
deathgasp = TRUE
+/datum/symptom/heal/coma/on_stage_change(datum/disease/advance/A) //mostly copy+pasted from the code for self-respiration's TRAIT_NOBREATH stuff
+ if(!..())
+ return FALSE
+ if(A.stage >= 4 && stabilize)
+ ADD_TRAIT(A.affected_mob, TRAIT_NOCRITDAMAGE, DISEASE_TRAIT)
+ else
+ REMOVE_TRAIT(A.affected_mob, TRAIT_NOCRITDAMAGE, DISEASE_TRAIT)
+ return TRUE
+
+/datum/symptom/heal/coma/End(datum/disease/advance/A)
+ if(!..())
+ return
+ REMOVE_TRAIT(A.affected_mob, TRAIT_NOCRITDAMAGE, DISEASE_TRAIT)
+
/datum/symptom/heal/coma/CanHeal(datum/disease/advance/A)
var/mob/living/M = A.affected_mob
if(HAS_TRAIT(M, TRAIT_DEATHCOMA))
diff --git a/code/datums/diseases/advance/symptoms/nanites.dm b/code/datums/diseases/advance/symptoms/nanites.dm
index 99665aafc0..b18e089a41 100644
--- a/code/datums/diseases/advance/symptoms/nanites.dm
+++ b/code/datums/diseases/advance/symptoms/nanites.dm
@@ -25,7 +25,7 @@
if(!..())
return
var/mob/living/carbon/M = A.affected_mob
- SEND_SIGNAL(M, COMSIG_NANITE_ADJUST_VOLUME, 0.5 * power)
+ SEND_SIGNAL(M, COMSIG_NANITE_ADJUST_VOLUME, power)
if(reverse_boost && SEND_SIGNAL(M, COMSIG_HAS_NANITES))
if(prob(A.stage_prob))
A.stage = min(A.stage + 1,A.max_stages)
@@ -60,4 +60,4 @@
SEND_SIGNAL(M, COMSIG_NANITE_ADJUST_VOLUME, -0.4 * power)
if(reverse_boost && SEND_SIGNAL(M, COMSIG_HAS_NANITES))
if(prob(A.stage_prob))
- A.stage = min(A.stage + 1,A.max_stages)
\ No newline at end of file
+ A.stage = min(A.stage + 1,A.max_stages)
diff --git a/code/datums/diseases/advance/symptoms/oxygen.dm b/code/datums/diseases/advance/symptoms/oxygen.dm
index 7bb6934707..65f8307101 100644
--- a/code/datums/diseases/advance/symptoms/oxygen.dm
+++ b/code/datums/diseases/advance/symptoms/oxygen.dm
@@ -44,9 +44,25 @@ Bonus
if(4, 5)
M.adjustOxyLoss(-7, 0)
M.losebreath = max(0, M.losebreath - 4)
- if(regenerate_blood && M.blood_volume < BLOOD_VOLUME_NORMAL)
+ if(regenerate_blood && M.blood_volume < (BLOOD_VOLUME_NORMAL * M.blood_ratio))
M.blood_volume += 1
else
if(prob(base_message_chance))
to_chat(M, "[pick("Your lungs feel great.", "You realize you haven't been breathing.", "You don't feel the need to breathe.")]")
return
+
+/datum/symptom/oxygen/on_stage_change(datum/disease/advance/A)
+ if(!..())
+ return FALSE
+ var/mob/living/carbon/M = A.affected_mob
+ if(A.stage >= 4)
+ ADD_TRAIT(M, TRAIT_NOBREATH, DISEASE_TRAIT)
+ else
+ REMOVE_TRAIT(M, TRAIT_NOBREATH, DISEASE_TRAIT)
+ return TRUE
+
+/datum/symptom/oxygen/End(datum/disease/advance/A)
+ if(!..())
+ return
+ if(A.stage >= 4)
+ REMOVE_TRAIT(A.affected_mob, TRAIT_NOBREATH, DISEASE_TRAIT)
\ No newline at end of file
diff --git a/code/datums/diseases/advance/symptoms/sensory.dm b/code/datums/diseases/advance/symptoms/sensory.dm
index 8d7cc5ed70..2705e0b168 100644
--- a/code/datums/diseases/advance/symptoms/sensory.dm
+++ b/code/datums/diseases/advance/symptoms/sensory.dm
@@ -51,7 +51,7 @@
M.hallucination = max(0, M.hallucination - 10)
if(A.stage >= 5)
- M.adjustBrainLoss(-3)
+ M.adjustOrganLoss(ORGAN_SLOT_BRAIN, -3)
if(trauma_heal_mild && iscarbon(M))
var/mob/living/carbon/C = M
if(prob(10))
@@ -100,8 +100,8 @@
else if(M.eye_blind || M.eye_blurry)
M.set_blindness(0)
M.set_blurriness(0)
- else if(eyes.eye_damage > 0)
- M.adjust_eye_damage(-1)
+ else if(eyes.damage > 0)
+ eyes.applyOrganDamage(-1)
else
if(prob(base_message_chance))
- to_chat(M, "[pick("Your eyes feel great.","You feel like your eyes can focus more clearly.", "You don't feel the need to blink.","Your ears feel great.","Your healing feels more acute.")]")
\ No newline at end of file
+ to_chat(M, "[pick("Your eyes feel great.","You feel like your eyes can focus more clearly.", "You don't feel the need to blink.","Your ears feel great.","Your healing feels more acute.")]")
diff --git a/code/datums/diseases/advance/symptoms/sneeze.dm b/code/datums/diseases/advance/symptoms/sneeze.dm
index 8fe70c542f..5e21fb3dd9 100644
--- a/code/datums/diseases/advance/symptoms/sneeze.dm
+++ b/code/datums/diseases/advance/symptoms/sneeze.dm
@@ -48,4 +48,5 @@ Bonus
M.emote("sniff")
else
M.emote("sneeze")
- A.spread(4 + power)
\ No newline at end of file
+ if(M.CanSpreadAirborneDisease()) //don't spread germs if they covered their mouth
+ A.spread(4 + power)
\ No newline at end of file
diff --git a/code/datums/diseases/advance/symptoms/species.dm b/code/datums/diseases/advance/symptoms/species.dm
index d0c32f3244..a8b18ae735 100644
--- a/code/datums/diseases/advance/symptoms/species.dm
+++ b/code/datums/diseases/advance/symptoms/species.dm
@@ -8,12 +8,14 @@
level = 5
severity = 0
-/datum/symptom/undead_adaptation/Start(datum/disease/advance/A)
- if(!..())
- return
+/datum/symptom/undead_adaptation/OnAdd(datum/disease/advance/A)
A.process_dead = TRUE
A.infectable_biotypes |= MOB_UNDEAD
+/datum/symptom/undead_adaptation/OnRemove(datum/disease/advance/A)
+ A.process_dead = FALSE
+ A.infectable_biotypes -= MOB_UNDEAD
+
/datum/symptom/inorganic_adaptation
name = "Inorganic Biology"
desc = "The virus can survive and replicate even in an inorganic environment, increasing its resistance and infection rate."
@@ -24,7 +26,8 @@
level = 5
severity = 0
-/datum/symptom/inorganic_adaptation/Start(datum/disease/advance/A)
- if(!..())
- return
- A.infectable_biotypes |= MOB_INORGANIC
\ No newline at end of file
+/datum/symptom/inorganic_adaptation/OnAdd(datum/disease/advance/A)
+ A.infectable_biotypes |= MOB_INORGANIC
+
+/datum/symptom/inorganic_adaptation/OnRemove(datum/disease/advance/A)
+ A.infectable_biotypes -= MOB_INORGANIC
\ No newline at end of file
diff --git a/code/datums/diseases/advance/symptoms/symptoms.dm b/code/datums/diseases/advance/symptoms/symptoms.dm
index e42b68cc05..99dbd397a2 100644
--- a/code/datums/diseases/advance/symptoms/symptoms.dm
+++ b/code/datums/diseases/advance/symptoms/symptoms.dm
@@ -38,11 +38,10 @@
return
CRASH("We couldn't assign an ID!")
-// Called when processing of the advance disease, which holds this symptom, starts.
+// Called when processing of the advance disease that holds this symptom infects a host and upon each Refresh() of that advance disease.
/datum/symptom/proc/Start(datum/disease/advance/A)
if(neutered)
return FALSE
- next_activation = world.time + rand(symptom_delay_min * 10, symptom_delay_max * 10) //so it doesn't instantly activate on infection
return TRUE
// Called when the advance disease is going to be deleted or when the advance disease stops processing.
@@ -60,6 +59,11 @@
next_activation = world.time + rand(symptom_delay_min * 10, symptom_delay_max * 10)
return TRUE
+/datum/symptom/proc/on_stage_change(datum/disease/advance/A)
+ if(neutered)
+ return FALSE
+ return TRUE
+
/datum/symptom/proc/Copy()
var/datum/symptom/new_symp = new type
new_symp.name = name
@@ -69,3 +73,9 @@
/datum/symptom/proc/generate_threshold_desc()
return
+
+/datum/symptom/proc/OnAdd(datum/disease/advance/A) //Overload when a symptom needs to be active before processing, like changing biotypes.
+ return
+
+/datum/symptom/proc/OnRemove(datum/disease/advance/A) //But dont forget to remove them too.
+ return
\ No newline at end of file
diff --git a/code/datums/diseases/advance/symptoms/vision.dm b/code/datums/diseases/advance/symptoms/vision.dm
index d1cc6905a6..b4a33cb837 100644
--- a/code/datums/diseases/advance/symptoms/vision.dm
+++ b/code/datums/diseases/advance/symptoms/vision.dm
@@ -45,7 +45,7 @@ Bonus
return
var/mob/living/carbon/M = A.affected_mob
var/obj/item/organ/eyes/eyes = M.getorganslot(ORGAN_SLOT_EYES)
- if(istype(eyes))
+ if(eyes)
switch(A.stage)
if(1, 2)
if(prob(base_message_chance) && !suppress_warning)
@@ -53,20 +53,20 @@ Bonus
if(3, 4)
to_chat(M, "Your eyes burn!")
M.blur_eyes(10)
- M.adjust_eye_damage(1)
+ eyes.applyOrganDamage(1)
else
M.blur_eyes(20)
- M.adjust_eye_damage(5)
- if(eyes.eye_damage >= 10)
+ eyes.applyOrganDamage(5)
+ if(eyes.damage >= 10)
M.become_nearsighted(EYE_DAMAGE)
- if(prob(eyes.eye_damage - 10 + 1))
+ if(prob(eyes.damage - 10 + 1))
if(!remove_eyes)
if(!HAS_TRAIT(M, TRAIT_BLIND))
to_chat(M, "You go blind!")
- M.become_blind(EYE_DAMAGE)
+ eyes.applyOrganDamage(eyes.maxHealth)
else
M.visible_message("[M]'s eyes fall off their sockets!", "Your eyes fall off their sockets!")
eyes.Remove(M)
eyes.forceMove(get_turf(M))
else
- to_chat(M, "Your eyes burn horrifically!")
\ No newline at end of file
+ to_chat(M, "Your eyes burn horrifically!")
diff --git a/code/datums/diseases/appendicitis.dm b/code/datums/diseases/appendicitis.dm
index 5708447542..be7e6ceecd 100644
--- a/code/datums/diseases/appendicitis.dm
+++ b/code/datums/diseases/appendicitis.dm
@@ -27,8 +27,10 @@
A.update_icon()
if(prob(3))
to_chat(affected_mob, "You feel a stabbing pain in your abdomen!")
+ affected_mob.adjustOrganLoss(ORGAN_SLOT_APPENDIX, 5)
affected_mob.Stun(rand(40,60))
affected_mob.adjustToxLoss(1)
if(3)
if(prob(1))
affected_mob.vomit(95)
+ affected_mob.adjustOrganLoss(ORGAN_SLOT_APPENDIX, 15)
diff --git a/code/datums/diseases/brainrot.dm b/code/datums/diseases/brainrot.dm
index 0a34501763..98ae74270b 100644
--- a/code/datums/diseases/brainrot.dm
+++ b/code/datums/diseases/brainrot.dm
@@ -24,7 +24,7 @@
if(prob(2))
to_chat(affected_mob, "You don't feel like yourself.")
if(prob(5))
- affected_mob.adjustBrainLoss(1, 170)
+ affected_mob.adjustOrganLoss(ORGAN_SLOT_BRAIN, 1, 170)
affected_mob.updatehealth()
if(3)
if(prob(2))
@@ -32,7 +32,7 @@
if(prob(2))
affected_mob.emote("drool")
if(prob(10))
- affected_mob.adjustBrainLoss(2, 170)
+ affected_mob.adjustOrganLoss(ORGAN_SLOT_BRAIN, 2, 170)
affected_mob.updatehealth()
if(prob(2))
to_chat(affected_mob, "Your try to remember something important...but can't.")
@@ -43,7 +43,7 @@
if(prob(2))
affected_mob.emote("drool")
if(prob(15))
- affected_mob.adjustBrainLoss(3, 170)
+ affected_mob.adjustOrganLoss(ORGAN_SLOT_BRAIN, 3, 170)
affected_mob.updatehealth()
if(prob(2))
to_chat(affected_mob, "Strange buzzing fills your head, removing all thoughts.")
diff --git a/code/datums/diseases/pierrot_throat.dm b/code/datums/diseases/pierrot_throat.dm
index 64f453cd48..b2241d59ba 100644
--- a/code/datums/diseases/pierrot_throat.dm
+++ b/code/datums/diseases/pierrot_throat.dm
@@ -26,3 +26,30 @@
if(4)
if(prob(5))
affected_mob.say( pick( list("HONK!", "Honk!", "Honk.", "Honk?", "Honk!!", "Honk?!", "Honk...") ) , forced = "pierrot's throat")
+
+/datum/disease/pierrot_throat/after_add()
+ RegisterSignal(affected_mob, COMSIG_MOB_SAY, .proc/handle_speech)
+
+/datum/disease/pierrot_throat/proc/handle_speech(datum/source, list/speech_args)
+ var/message = speech_args[SPEECH_MESSAGE]
+ var/list/split_message = splittext(message, " ") //List each word in the message
+ var/applied = 0
+ for (var/i in 1 to length(split_message))
+ if(prob(3 * stage)) //Stage 1: 3% Stage 2: 6% Stage 3: 9% Stage 4: 12%
+ if(findtext(split_message[i], "*") || findtext(split_message[i], ";") || findtext(split_message[i], ":"))
+ continue
+ split_message[i] = "HONK"
+ if (applied++ > stage)
+ break
+ if (applied)
+ speech_args[SPEECH_SPANS] |= SPAN_CLOWN // a little bonus
+ message = jointext(split_message, " ")
+ speech_args[SPEECH_MESSAGE] = message
+
+/datum/disease/pierrot_throat/Destroy()
+ UnregisterSignal(affected_mob, COMSIG_MOB_SAY)
+ return ..()
+
+/datum/disease/pierrot_throat/remove_disease()
+ UnregisterSignal(affected_mob, COMSIG_MOB_SAY)
+ return ..()
\ No newline at end of file
diff --git a/code/datums/diseases/transformation.dm b/code/datums/diseases/transformation.dm
index b295f5f10b..083a1f9c6c 100644
--- a/code/datums/diseases/transformation.dm
+++ b/code/datums/diseases/transformation.dm
@@ -67,7 +67,7 @@
if(affected_mob.mind)
affected_mob.mind.transfer_to(new_mob)
else
- new_mob.key = affected_mob.key
+ affected_mob.transfer_ckey(new_mob)
new_mob.name = affected_mob.real_name
new_mob.real_name = new_mob.name
@@ -82,7 +82,7 @@
to_chat(affected_mob, "Your mob has been taken over by a ghost! Appeal your job ban if you want to avoid this in the future!")
message_admins("[key_name_admin(C)] has taken control of ([key_name_admin(affected_mob)]) to replace a jobbaned player.")
affected_mob.ghostize(0)
- affected_mob.key = C.key
+ C.transfer_ckey(affected_mob)
else
to_chat(new_mob, "Your mob has been claimed by death! Appeal your job ban if you want to avoid this in the future!")
new_mob.death()
diff --git a/code/datums/dna.dm b/code/datums/dna.dm
index 938515625d..33e92e4de5 100644
--- a/code/datums/dna.dm
+++ b/code/datums/dna.dm
@@ -185,25 +185,6 @@
if(DNA_TAUR_BLOCK)
construct_block(GLOB.taur_list.Find(features["taur"]), GLOB.taur_list.len)
-/datum/dna/proc/mutations_say_mods(message)
- if(message)
- for(var/datum/mutation/human/M in mutations)
- message = M.say_mod(message)
- return message
-
-/datum/dna/proc/mutations_get_spans()
- var/list/spans = list()
- for(var/datum/mutation/human/M in mutations)
- spans |= M.get_spans()
- return spans
-
-/datum/dna/proc/species_get_spans()
- var/list/spans = list()
- if(species)
- spans |= species.get_spans()
- return spans
-
-
/datum/dna/proc/is_same_as(datum/dna/D)
if(uni_identity == D.uni_identity && struc_enzymes == D.struc_enzymes && real_name == D.real_name && nameless == D.nameless && custom_species == D.custom_species)
if(species.type == D.species.type && features == D.features && blood_type == D.blood_type)
diff --git a/code/datums/ert.dm b/code/datums/ert.dm
index d61c95c8f2..4b4cce3794 100644
--- a/code/datums/ert.dm
+++ b/code/datums/ert.dm
@@ -21,8 +21,8 @@
/datum/ert/amber
code = "Amber"
- leader_role = /datum/antagonist/ert/commander/red
- roles = list(/datum/antagonist/ert/security/red, /datum/antagonist/ert/medic/red, /datum/antagonist/ert/engineer/red)
+ leader_role = /datum/antagonist/ert/commander/amber
+ roles = list(/datum/antagonist/ert/security/amber, /datum/antagonist/ert/medic/amber, /datum/antagonist/ert/engineer/amber)
/datum/ert/red
leader_role = /datum/antagonist/ert/commander/red
@@ -55,3 +55,13 @@
rename_team = "Inquisition"
mission = "Destroy any traces of paranormal activity aboard the station."
polldesc = "a Nanotrasen paranormal response team"
+
+/datum/ert/greybois
+ code = "Green"
+ teamsize = 1
+ opendoors = FALSE
+ enforce_human = FALSE
+ roles = list(/datum/antagonist/greybois)
+ leader_role = /datum/antagonist/greybois/greygod
+ rename_team = "Emergency Assistants"
+ polldesc = "an Emergency Assistant"
diff --git a/code/datums/helper_datums/teleport.dm b/code/datums/helper_datums/teleport.dm
index 0623e2f5f9..4d1986cccf 100644
--- a/code/datums/helper_datums/teleport.dm
+++ b/code/datums/helper_datums/teleport.dm
@@ -5,11 +5,19 @@
// effectout: effect to show right after teleportation
// asoundin: soundfile to play before teleportation
// asoundout: soundfile to play after teleportation
-// force_teleport: if false, teleport will use Move() proc (dense objects will prevent teleportation)
// no_effects: disable the default effectin/effectout of sparks
-/proc/do_teleport(atom/movable/teleatom, atom/destination, precision=null, force_teleport=TRUE, datum/effect_system/effectin=null, datum/effect_system/effectout=null, asoundin=null, asoundout=null, no_effects=FALSE)
+// forceMove: if false, teleport will use Move() proc (dense objects will prevent teleportation)
+// forced: whether or not to ignore no_teleport
+/proc/do_teleport(atom/movable/teleatom, atom/destination, precision=null, forceMove = TRUE, datum/effect_system/effectin=null, datum/effect_system/effectout=null, asoundin=null, asoundout=null, no_effects=FALSE, channel=TELEPORT_CHANNEL_BLUESPACE, forced = FALSE)
// teleporting most effects just deletes them
- if(iseffect(teleatom) && !istype(teleatom, /obj/effect/dummy/chameleon))
+ var/static/list/delete_atoms = typecacheof(list(
+ /obj/effect,
+ )) - typecacheof(list(
+ /obj/effect/dummy/chameleon,
+ /obj/effect/wisp,
+ /obj/effect/mob_spawn
+ ))
+ if(delete_atoms[teleatom.type])
qdel(teleatom)
return FALSE
@@ -17,25 +25,37 @@
// if the precision is not specified, default to 0, but apply BoH penalties
if (isnull(precision))
precision = 0
- if(istype(teleatom, /obj/item/storage/backpack/holding))
- precision = rand(1,100)
- var/static/list/bag_cache = typecacheof(/obj/item/storage/backpack/holding)
- var/list/bagholding = typecache_filter_list(teleatom.GetAllContents(), bag_cache)
- if(bagholding.len)
- precision = max(rand(1,100)*bagholding.len,100)
- if(isliving(teleatom))
- var/mob/living/MM = teleatom
- to_chat(MM, "The bluespace interface on your bag of holding interferes with the teleport!")
+ switch(channel)
+ if(TELEPORT_CHANNEL_BLUESPACE)
+ if(istype(teleatom, /obj/item/storage/backpack/holding))
+ precision = rand(1,100)
- // if effects are not specified and not explicitly disabled, sparks
- if ((!effectin || !effectout) && !no_effects)
- var/datum/effect_system/spark_spread/sparks = new
- sparks.set_up(5, 1, teleatom)
- if (!effectin)
- effectin = sparks
- if (!effectout)
- effectout = sparks
+ var/static/list/bag_cache = typecacheof(/obj/item/storage/backpack/holding)
+ var/list/bagholding = typecache_filter_list(teleatom.GetAllContents(), bag_cache)
+ if(bagholding.len)
+ precision = max(rand(1,100)*bagholding.len,100)
+ if(isliving(teleatom))
+ var/mob/living/MM = teleatom
+ to_chat(MM, "The bluespace interface on your bag of holding interferes with the teleport!")
+
+ // if effects are not specified and not explicitly disabled, sparks
+ if ((!effectin || !effectout) && !no_effects)
+ var/datum/effect_system/spark_spread/sparks = new
+ sparks.set_up(5, 1, teleatom)
+ if (!effectin)
+ effectin = sparks
+ if (!effectout)
+ effectout = sparks
+ if(TELEPORT_CHANNEL_QUANTUM)
+ // if effects are not specified and not explicitly disabled, rainbow sparks
+ if ((!effectin || !effectout) && !no_effects)
+ var/datum/effect_system/spark_spread/quantum/sparks = new
+ sparks.set_up(5, 1, teleatom)
+ if (!effectin)
+ effectin = sparks
+ if (!effectout)
+ effectout = sparks
// perform the teleport
var/turf/curturf = get_turf(teleatom)
@@ -45,11 +65,15 @@
return FALSE
var/area/A = get_area(curturf)
- if(A.noteleport)
+ var/area/B = get_area(destturf)
+ if(!forced && (A.noteleport || B.noteleport))
+ return FALSE
+
+ if(SEND_SIGNAL(destturf, COMSIG_ATOM_INTERCEPT_TELEPORT, channel, curturf, destturf))
return FALSE
tele_play_specials(teleatom, curturf, effectin, asoundin)
- var/success = force_teleport ? teleatom.forceMove(destturf) : teleatom.Move(destturf)
+ var/success = forceMove ? teleatom.forceMove(destturf) : teleatom.Move(destturf)
if (success)
log_game("[key_name(teleatom)] has teleported from [loc_name(curturf)] to [loc_name(destturf)]")
tele_play_specials(teleatom, destturf, effectout, asoundout)
diff --git a/code/datums/looping_sounds/_looping_sound.dm b/code/datums/looping_sounds/_looping_sound.dm
index 1b7a304494..49942976ce 100644
--- a/code/datums/looping_sounds/_looping_sound.dm
+++ b/code/datums/looping_sounds/_looping_sound.dm
@@ -96,4 +96,4 @@
/datum/looping_sound/proc/on_stop()
if(end_sound)
- play(end_sound)
\ No newline at end of file
+ play(end_sound)
diff --git a/code/datums/map_config.dm b/code/datums/map_config.dm
index 99b84a1b35..dcc16f1ba5 100644
--- a/code/datums/map_config.dm
+++ b/code/datums/map_config.dm
@@ -18,7 +18,7 @@
var/map_file = "BoxStation.dmm"
var/traits = null
- var/space_ruin_levels = 1 //Citadel edit - reduces the default space ruin z-level count to 1
+ var/space_ruin_levels = 2
var/space_empty_levels = 1
var/minetype = "lavaland"
diff --git a/code/datums/martial.dm b/code/datums/martial.dm
index ae8f92a342..d119759efc 100644
--- a/code/datums/martial.dm
+++ b/code/datums/martial.dm
@@ -2,6 +2,7 @@
var/name = "Martial Art"
var/streak = ""
var/max_streak_length = 6
+ var/id = "" //ID, used by mind/has_martialartcode\game\objects\items\granters.dm:345:error: user.mind.has_martialart: undefined proccode\game\objects\items\granters.dm:345:error: user.mind.has_martialart: undefined proccode\game\objects\items\granters.dm:345:error: user.mind.has_martialart: undefined proccode\game\objects\items\granters.dm:345:error: user.mind.has_martialart: undefined proccode\game\objects\items\granters.dm:345:error: user.mind.has_martialart: undefined proc
var/current_target
var/datum/martial_art/base // The permanent style. This will be null unless the martial art is temporary
var/deflection_chance = 0 //Chance to deflect projectiles
diff --git a/code/datums/martial/boxing.dm b/code/datums/martial/boxing.dm
index c93fc47f2b..7399528e1c 100644
--- a/code/datums/martial/boxing.dm
+++ b/code/datums/martial/boxing.dm
@@ -1,5 +1,6 @@
/datum/martial_art/boxing
name = "Boxing"
+ id = MARTIALART_BOXING
/datum/martial_art/boxing/disarm_act(mob/living/carbon/human/A, mob/living/carbon/human/D)
to_chat(A, "Can't disarm while boxing!")
diff --git a/code/datums/martial/cqc.dm b/code/datums/martial/cqc.dm
index 55cd4a3f7a..c7644997ee 100644
--- a/code/datums/martial/cqc.dm
+++ b/code/datums/martial/cqc.dm
@@ -6,6 +6,7 @@
/datum/martial_art/cqc
name = "CQC"
+ id = MARTIALART_CQC
help_verb = /mob/living/carbon/human/proc/CQC_help
block_chance = 75
var/just_a_cook = FALSE
@@ -78,7 +79,7 @@
"[A] kicks your head, knocking you out!")
playsound(get_turf(A), 'sound/weapons/genhit1.ogg', 50, 1, -1)
D.SetSleeping(300)
- D.adjustBrainLoss(15, 150)
+ D.adjustOrganLoss(ORGAN_SLOT_BRAIN, 15, 150)
return TRUE
/datum/martial_art/cqc/proc/Pressure(mob/living/carbon/human/A, mob/living/carbon/human/D)
diff --git a/code/datums/martial/krav_maga.dm b/code/datums/martial/krav_maga.dm
index 4283d7f78f..6379d481ca 100644
--- a/code/datums/martial/krav_maga.dm
+++ b/code/datums/martial/krav_maga.dm
@@ -1,5 +1,6 @@
/datum/martial_art/krav_maga
name = "Krav Maga"
+ id = MARTIALART_KRAVMAGA
var/datum/action/neck_chop/neckchop = new/datum/action/neck_chop()
var/datum/action/leg_sweep/legsweep = new/datum/action/leg_sweep()
var/datum/action/lung_punch/lungpunch = new/datum/action/lung_punch()
@@ -85,14 +86,14 @@
return 1
return 0
-/datum/martial_art/krav_maga/proc/leg_sweep(var/mob/living/carbon/human/A, var/mob/living/carbon/human/D)
+/datum/martial_art/krav_maga/proc/leg_sweep(mob/living/carbon/human/A, mob/living/carbon/human/D)
if(D.lying || D.IsKnockdown())
return 0
D.visible_message("[A] leg sweeps [D]!", \
"[A] leg sweeps you!")
playsound(get_turf(A), 'sound/effects/hit_kick.ogg', 50, 1, -1)
D.apply_damage(5, BRUTE)
- D.Knockdown(40, override_hardstun = 0.01, 25)
+ D.Knockdown(40, override_hardstun = 0.01, override_stamdmg = 25)
log_combat(A, D, "leg sweeped")
return 1
@@ -196,7 +197,7 @@
name = "combat gloves plus"
desc = "These tactical gloves are fireproof and shock resistant, and using nanochip technology it teaches you the powers of krav maga."
icon_state = "combat"
- item_state = "blackglovesplus"
+ item_state = "blackgloves"
siemens_coefficient = 0
permeability_coefficient = 0.05
strip_delay = 80
diff --git a/code/datums/martial/mushpunch.dm b/code/datums/martial/mushpunch.dm
index 33d5bb51bf..1ef7734771 100644
--- a/code/datums/martial/mushpunch.dm
+++ b/code/datums/martial/mushpunch.dm
@@ -1,5 +1,6 @@
/datum/martial_art/mushpunch
name = "Mushroom Punch"
+ id = MARTIALART_MUSHPUNCH
/datum/martial_art/mushpunch/harm_act(mob/living/carbon/human/A, mob/living/carbon/human/D)
var/atk_verb
diff --git a/code/datums/martial/plasma_fist.dm b/code/datums/martial/plasma_fist.dm
index f540dc0783..e38a011db0 100644
--- a/code/datums/martial/plasma_fist.dm
+++ b/code/datums/martial/plasma_fist.dm
@@ -4,6 +4,7 @@
/datum/martial_art/plasma_fist
name = "Plasma Fist"
+ id = MARTIALART_PLASMAFIST
help_verb = /mob/living/carbon/human/proc/plasma_fist_help
diff --git a/code/datums/martial/psychotic_brawl.dm b/code/datums/martial/psychotic_brawl.dm
index 1cb18f903f..34301516dc 100644
--- a/code/datums/martial/psychotic_brawl.dm
+++ b/code/datums/martial/psychotic_brawl.dm
@@ -1,5 +1,6 @@
/datum/martial_art/psychotic_brawling
name = "Psychotic Brawling"
+ id = MARTIALART_PSYCHOBRAWL
/datum/martial_art/psychotic_brawling/disarm_act(mob/living/carbon/human/A, mob/living/carbon/human/D)
return psycho_attack(A,D)
@@ -45,7 +46,7 @@
D.apply_damage(rand(5,10), BRUTE, BODY_ZONE_HEAD)
A.apply_damage(rand(5,10), BRUTE, BODY_ZONE_HEAD)
if(!istype(D.head,/obj/item/clothing/head/helmet/) && !istype(D.head,/obj/item/clothing/head/hardhat))
- D.adjustBrainLoss(5)
+ D.adjustOrganLoss(ORGAN_SLOT_BRAIN, 5)
A.Stun(rand(10,45))
D.Knockdown(rand(5,30))//CIT CHANGE - makes stuns from martial arts always use Knockdown instead of Stun for the sake of consistency
if(5,6)
diff --git a/code/datums/martial/sleeping_carp.dm b/code/datums/martial/sleeping_carp.dm
index 5f19c37b99..801e8c8c7a 100644
--- a/code/datums/martial/sleeping_carp.dm
+++ b/code/datums/martial/sleeping_carp.dm
@@ -6,6 +6,7 @@
/datum/martial_art/the_sleeping_carp
name = "The Sleeping Carp"
+ id = MARTIALART_SLEEPINGCARP
deflection_chance = 100
reroute_deflection = TRUE
no_guns = TRUE
@@ -223,7 +224,7 @@
H.visible_message("[user] delivers a heavy hit to [H]'s head, knocking [H.p_them()] out cold!", \
"[user] knocks you unconscious!")
H.SetSleeping(600)
- H.adjustBrainLoss(15, 150)
+ H.adjustOrganLoss(ORGAN_SLOT_BRAIN, 15, 150)
else
return ..()
diff --git a/code/datums/martial/wrestling.dm b/code/datums/martial/wrestling.dm
index e57edf9fb2..e07fa27ef5 100644
--- a/code/datums/martial/wrestling.dm
+++ b/code/datums/martial/wrestling.dm
@@ -10,6 +10,7 @@
/datum/martial_art/wrestling
name = "Wrestling"
+ id = MARTIALART_WRESTLING
var/datum/action/slam/slam = new/datum/action/slam()
var/datum/action/throw_wrassle/throw_wrassle = new/datum/action/throw_wrassle()
var/datum/action/kick/kick = new/datum/action/kick()
diff --git a/code/datums/mind.dm b/code/datums/mind.dm
index 7d150d890d..68efe93254 100644
--- a/code/datums/mind.dm
+++ b/code/datums/mind.dm
@@ -19,9 +19,9 @@
- IMPORTANT NOTE 2, if you want a player to become a ghost, use mob.ghostize() It does all the hard work for you.
- When creating a new mob which will be a new IC character (e.g. putting a shade in a construct or randomly selecting
- a ghost to become a xeno during an event). Simply assign the key or ckey like you've always done.
+ a ghost to become a xeno during an event), use this mob proc.
- new_mob.key = key
+ mob.transfer_ckey(new_mob)
The Login proc will handle making a new mind for that mobtype (including setting up stuff like mind.name). Simple!
However if you want that mind to have any special properties like being a traitor etc you will have to do that
@@ -64,6 +64,8 @@
var/force_escaped = FALSE // Set by Into The Sunset command of the shuttle manipulator
+ var/list/learned_recipes //List of learned recipe TYPES.
+
/datum/mind/New(var/key)
src.key = key
soulOwner = src
@@ -87,6 +89,7 @@
return language_holder
/datum/mind/proc/transfer_to(mob/new_character, var/force_key_move = 0)
+ var/old_character = current
if(current) // remove ourself from our old body's mind variable
current.mind = null
SStgui.on_transfer(current, new_character)
@@ -97,7 +100,7 @@
if(key)
if(new_character.key != key) //if we're transferring into a body with a key associated which is not ours
- new_character.ghostize(1) //we'll need to ghostize so that key isn't mobless.
+ new_character.ghostize(TRUE, TRUE) //we'll need to ghostize so that key isn't mobless.
else
key = new_character.key
@@ -121,6 +124,7 @@
transfer_martial_arts(new_character)
if(active || force_key_move)
new_character.key = key //now transfer the key to link the client to our new body
+ SEND_SIGNAL(src, COMSIG_MIND_TRANSFER, new_character, old_character)
//CIT CHANGE - makes arousal update when transfering bodies
if(isliving(new_character)) //New humans and such are by default enabled arousal. Let's always use the new mind's prefs.
@@ -130,7 +134,8 @@
L.update_arousal_hud() //Removes the old icon
/datum/mind/proc/store_memory(new_text)
- memory += "[new_text] "
+ if((length(memory) + length(new_text)) <= MAX_MESSAGE_LEN)
+ memory += "[new_text] "
/datum/mind/proc/wipe_memory()
memory = null
@@ -778,6 +783,11 @@
mind_initialize() //updates the mind (or creates and initializes one if one doesn't exist)
mind.active = 1 //indicates that the mind is currently synced with a client
+/datum/mind/proc/has_martialart(var/string)
+ if(martial_art && martial_art.id == string)
+ return martial_art
+ return FALSE
+
/mob/dead/new_player/sync_mind()
return
diff --git a/code/datums/mood_events/drink_events.dm b/code/datums/mood_events/drink_events.dm
index 5d0eb0e0cb..70acffdb8f 100644
--- a/code/datums/mood_events/drink_events.dm
+++ b/code/datums/mood_events/drink_events.dm
@@ -6,24 +6,24 @@
/datum/mood_event/quality_nice
description = "That drink wasn't bad at all.\n"
mood_change = 1
- timeout = 1200
+ timeout = 2 MINUTES
/datum/mood_event/quality_good
description = "That drink was pretty good.\n"
mood_change = 2
- timeout = 1200
+ timeout = 2 MINUTES
/datum/mood_event/quality_verygood
description = "That drink was great!\n"
mood_change = 3
- timeout = 1200
+ timeout = 2 MINUTES
/datum/mood_event/quality_fantastic
description = "That drink was amazing!\n"
mood_change = 4
- timeout = 1200
+ timeout = 2 MINUTES
/datum/mood_event/amazingtaste
description = "Amazing taste!\n"
- mood_change = 50
+ mood_change = 50 //Is this not really high..?
timeout = 10 MINUTES
diff --git a/code/datums/mood_events/drug_events.dm b/code/datums/mood_events/drug_events.dm
index 40c239180e..469bf80979 100644
--- a/code/datums/mood_events/drug_events.dm
+++ b/code/datums/mood_events/drug_events.dm
@@ -37,3 +37,19 @@
/datum/mood_event/withdrawal_critical/add_effects(drug_name)
description = "[drug_name]! [drug_name]! [drug_name]!\n"
+
+/datum/mood_event/happiness_drug
+ description = "I can't feel anything and I never want this to end.\n"
+ mood_change = 10
+
+/datum/mood_event/happiness_drug_good_od
+ description = "YES! YES!! YES!!!\n"
+ mood_change = 20
+ timeout = 300
+ //special_screen_obj = "mood_happiness_good" Originally in tg, but I personally think they look dumb
+
+/datum/mood_event/happiness_drug_bad_od
+ description = "NO! NO!! NO!!!\n"
+ mood_change = -20
+ timeout = 300
+ //special_screen_obj = "mood_happiness_bad" Originally in tg
diff --git a/code/datums/mood_events/generic_negative_events.dm b/code/datums/mood_events/generic_negative_events.dm
index b2b03fb56b..f747c563ad 100644
--- a/code/datums/mood_events/generic_negative_events.dm
+++ b/code/datums/mood_events/generic_negative_events.dm
@@ -1,3 +1,5 @@
+
+
/datum/mood_event/handcuffed
description = "I guess my antics have finally caught up with me.\n"
mood_change = -1
@@ -17,7 +19,7 @@
/datum/mood_event/burnt_thumb
description = "I shouldn't play with lighters...\n"
mood_change = -1
- timeout = 1200
+ timeout = 2 MINUTES
/datum/mood_event/cold
description = "It's way too cold in here.\n"
@@ -30,17 +32,17 @@
/datum/mood_event/creampie
description = "I've been creamed. Tastes like pie flavor.\n"
mood_change = -2
- timeout = 1800
+ timeout = 3 MINUTES
/datum/mood_event/slipped
description = "I slipped. I should be more careful next time...\n"
mood_change = -2
- timeout = 1800
+ timeout = 3 MINUTES
/datum/mood_event/eye_stab
description = "I used to be an adventurer like you, until I took a screwdriver to the eye.\n"
mood_change = -4
- timeout = 1800
+ timeout = 3 MINUTES
/datum/mood_event/delam //SM delamination
description = "Those God damn engineers can't do anything right...\n"
@@ -50,12 +52,12 @@
/datum/mood_event/depression
description = "I feel sad for no particular reason.\n"
mood_change = -9
- timeout = 1200
+ timeout = 2 MINUTES
/datum/mood_event/shameful_suicide //suicide_acts that return SHAME, like sord
description = "I can't even end it all!\n"
mood_change = -10
- timeout = 600
+ timeout = 1 MINUTES
/datum/mood_event/dismembered
description = "AHH! I WAS USING THAT LIMB!\n"
@@ -69,7 +71,7 @@
/datum/mood_event/tased
description = "There's no \"z\" in \"taser\". It's in the zap.\n"
mood_change = -3
- timeout = 1200
+ timeout = 2 MINUTES
/datum/mood_event/embedded
description = "Pull it out!\n"
@@ -78,7 +80,7 @@
/datum/mood_event/table
description = "Someone threw me on a table!\n"
mood_change = -2
- timeout = 1200
+ timeout = 2 MINUTES
/datum/mood_event/table/add_effects()
if(ishuman(owner))
@@ -117,6 +119,30 @@
description = "I'm missing my family heirloom...\n"
mood_change = -4
+/datum/mood_event/healsbadman
+ description = "I feel a lot better, but wow that was disgusting.\n"
+ mood_change = -4
+ timeout = 2 MINUTES
+
+/datum/mood_event/jittery
+ description = "I'm nervous and on edge and I can't stand still!!\n"
+ mood_change = -2
+
+/datum/mood_event/vomit
+ description = "I just threw up. Gross.\n"
+ mood_change = -2
+ timeout = 2 MINUTES
+
+/datum/mood_event/vomitself
+ description = "I just threw up all over myself. This is disgusting.\n"
+ mood_change = -4
+ timeout = 3 MINUTES
+
+/datum/mood_event/painful_medicine
+ description = "Medicine may be good for me but right now it stings like hell.\n"
+ mood_change = -5
+ timeout = 1 MINUTES
+
/datum/mood_event/loud_gong
description = "That loud gong noise really hurt my ears!\n"
mood_change = -3
@@ -143,3 +169,10 @@
/datum/mood_event/sad_empath/add_effects(mob/sadtarget)
description = "[sadtarget.name] seems upset...\n"
+
+/datum/mood_event/revenant_blight
+ description = "Just give up, honk...\n"
+ mood_change = -5
+
+/datum/mood_event/revenant_blight/add_effects()
+ description = "Just give up, [pick("no one will miss you", "there is nothing you can do to help", "even a clown would be more useful than you", "does it even matter in the end?")]...\n"
diff --git a/code/datums/mood_events/generic_positive_events.dm b/code/datums/mood_events/generic_positive_events.dm
index 051a548d1d..422ec4476c 100644
--- a/code/datums/mood_events/generic_positive_events.dm
+++ b/code/datums/mood_events/generic_positive_events.dm
@@ -1,7 +1,7 @@
/datum/mood_event/hug
description = "Hugs are nice.\n"
mood_change = 1
- timeout = 1200
+ timeout = 2 MINUTES
/datum/mood_event/arcade
description = "I beat the arcade game!\n"
@@ -50,7 +50,7 @@
/datum/mood_event/jolly
description = "I feel happy for no particular reason.\n"
mood_change = 6
- timeout = 1200
+ timeout = 2 MINUTES
/datum/mood_event/focused
description = "I have a goal, and I will reach it, whatever it takes!\n" //Used for syndies, nukeops etc so they can focus on their goals
@@ -76,6 +76,20 @@
mood_change = 3
timeout = 600
+/datum/mood_event/chemical_euphoria
+ description = "Heh...hehehe...hehe...\n"
+ mood_change = 4
+
+ /datum/mood_event/chemical_laughter
+ description = "Laughter really is the best medicine! Or is it?\n"
+ mood_change = 4
+ timeout = 3 MINUTES
+
+ /datum/mood_event/chemical_superlaughter
+ description = "*WHEEZE*\n"
+ mood_change = 12
+ timeout = 3 MINUTES
+
/datum/mood_event/betterhug
description = "Someone was very nice to me.\n"
mood_change = 3
@@ -94,8 +108,8 @@
/datum/mood_event/happy_empath
description = "Someone seems happy!\n"
- mood_change = 2
+ mood_change = 3
timeout = 600
/datum/mood_event/happy_empath/add_effects(var/mob/happytarget)
- description = "[happytarget.name]'s happiness is infectious!\n"
+ description = "[happytarget.name]'s happiness is infectious!\n"
diff --git a/code/datums/mood_events/needs_events.dm b/code/datums/mood_events/needs_events.dm
index 75b7e6931f..eb58f2aa92 100644
--- a/code/datums/mood_events/needs_events.dm
+++ b/code/datums/mood_events/needs_events.dm
@@ -59,4 +59,4 @@
/datum/mood_event/nice_shower
description = "I have recently had a nice shower.\n"
mood_change = 2
- timeout = 1800
+ timeout = 3 MINUTES
diff --git a/code/datums/mutations.dm b/code/datums/mutations.dm
index 7de9ca8b4a..efa248b4f0 100644
--- a/code/datums/mutations.dm
+++ b/code/datums/mutations.dm
@@ -105,13 +105,6 @@ GLOBAL_LIST_EMPTY(mutations_list)
return 0
return 1
-/datum/mutation/human/proc/say_mod(message)
- if(message)
- return message
-
-/datum/mutation/human/proc/get_spans()
- return list()
-
/mob/living/carbon/proc/update_mutations_overlay()
return
diff --git a/code/datums/mutations/body.dm b/code/datums/mutations/body.dm
index 461c221ff8..a32220aa43 100644
--- a/code/datums/mutations/body.dm
+++ b/code/datums/mutations/body.dm
@@ -63,16 +63,14 @@
/datum/mutation/human/dwarfism/on_acquiring(mob/living/carbon/human/owner)
if(..())
return
- owner.resize = 0.8
- owner.update_transform()
+ owner.transform = owner.transform.Scale(1, 0.8)
owner.pass_flags |= PASSTABLE
owner.visible_message("[owner] suddenly shrinks!", "Everything around you seems to grow..")
/datum/mutation/human/dwarfism/on_losing(mob/living/carbon/human/owner)
if(..())
return
- owner.resize = 1.25
- owner.update_transform()
+ owner.transform = owner.transform.Scale(1, 1.25)
owner.pass_flags &= ~PASSTABLE
owner.visible_message("[owner] suddenly grows!", "Everything around you seems to shrink..")
diff --git a/code/datums/mutations/hulk.dm b/code/datums/mutations/hulk.dm
index 0c760f4620..85cecca489 100644
--- a/code/datums/mutations/hulk.dm
+++ b/code/datums/mutations/hulk.dm
@@ -15,6 +15,7 @@
ADD_TRAIT(owner, TRAIT_PUSHIMMUNE, TRAIT_HULK)
owner.update_body_parts()
SEND_SIGNAL(owner, COMSIG_ADD_MOOD_EVENT, "hulk", /datum/mood_event/hulk)
+ RegisterSignal(owner, COMSIG_MOB_SAY, .proc/handle_speech)
/datum/mutation/human/hulk/on_attack_hand(mob/living/carbon/human/owner, atom/target, proximity)
if(proximity) //no telekinetic hulk attack
@@ -32,8 +33,11 @@
REMOVE_TRAIT(owner, TRAIT_PUSHIMMUNE, TRAIT_HULK)
owner.update_body_parts()
SEND_SIGNAL(owner, COMSIG_CLEAR_MOOD_EVENT, "hulk")
+ UnregisterSignal(owner, COMSIG_MOB_SAY)
-/datum/mutation/human/hulk/say_mod(message)
+/datum/mutation/human/hulk/proc/handle_speech(original_message, wrapped_message)
+ var/message = wrapped_message[1]
if(message)
- message = "[uppertext(replacetext(message, ".", "!"))]!!"
- return message
+ message = "[replacetext(message, ".", "!")]!!"
+ wrapped_message[1] = message
+ return COMPONENT_UPPERCASE_SPEECH
diff --git a/code/datums/mutations/speech.dm b/code/datums/mutations/speech.dm
index d986672924..f02b7f185f 100644
--- a/code/datums/mutations/speech.dm
+++ b/code/datums/mutations/speech.dm
@@ -17,9 +17,20 @@
text_gain_indication = "You feel an off sensation in your voicebox."
text_lose_indication = "The off sensation passes."
-/datum/mutation/human/wacky/get_spans()
- return list(SPAN_SANS)
+/datum/mutation/human/wacky/on_acquiring(mob/living/carbon/human/owner)
+ . = ..()
+ if(.)
+ return
+ RegisterSignal(owner, COMSIG_MOB_SAY, .proc/handle_speech)
+/datum/mutation/human/wacky/on_losing(mob/living/carbon/human/owner)
+ . = ..()
+ if(.)
+ return
+ UnregisterSignal(owner, COMSIG_MOB_SAY)
+
+/datum/mutation/human/wacky/proc/handle_speech(datum/source, list/speech_args)
+ speech_args[SPEECH_SPANS] |= SPAN_SANS
/datum/mutation/human/mute
name = "Mute"
@@ -28,12 +39,14 @@
text_lose_indication = "You feel able to speak freely again."
/datum/mutation/human/mute/on_acquiring(mob/living/carbon/human/owner)
- if(..())
+ . = ..()
+ if(.)
return
ADD_TRAIT(owner, TRAIT_MUTE, GENETIC_MUTATION)
/datum/mutation/human/mute/on_losing(mob/living/carbon/human/owner)
- if(..())
+ . = ..()
+ if(.)
return
REMOVE_TRAIT(owner, TRAIT_MUTE, GENETIC_MUTATION)
@@ -45,7 +58,20 @@
text_gain_indication = "You feel so happy. Nothing can be wrong with anything. :)"
text_lose_indication = "Everything is terrible again. :("
-/datum/mutation/human/smile/say_mod(message)
+/datum/mutation/human/smile/on_acquiring(mob/living/carbon/human/owner)
+ . = ..()
+ if(.)
+ return
+ RegisterSignal(owner, COMSIG_MOB_SAY, .proc/handle_speech)
+
+/datum/mutation/human/smile/on_losing(mob/living/carbon/human/owner)
+ . = ..()
+ if(.)
+ return
+ UnregisterSignal(owner, COMSIG_MOB_SAY)
+
+/datum/mutation/human/smile/proc/handle_speech(datum/source, list/speech_args)
+ var/message = speech_args[SPEECH_MESSAGE]
if(message)
message = " [message] "
//Time for a friendly game of SS13
@@ -65,34 +91,31 @@
message = replacetext(message," oh god "," cheese and crackers ")
message = replacetext(message," jesus "," gee wiz ")
message = replacetext(message," weak "," strong ")
- message = replacetext(message," kill "," hug ")
- message = replacetext(message," murder "," tease ")
+ message = replacetext(message," kill yourself "," hug ")
message = replacetext(message," ugly "," beautiful ")
message = replacetext(message," douchbag "," nice guy ")
message = replacetext(message," whore "," lady ")
- message = replacetext(message," nerd "," smart guy ")
+ message = replacetext(message," nerd "," smarty pants ")
message = replacetext(message," moron "," fun person ")
message = replacetext(message," IT'S LOOSE "," EVERYTHING IS FINE ")
message = replacetext(message," sex "," hug fight ")
message = replacetext(message," idiot "," genius ")
message = replacetext(message," fat "," thin ")
- message = replacetext(message," beer "," water with ice ")
- message = replacetext(message," drink "," water ")
+ message = replacetext(message," beer "," liquid bread ")
+ message = replacetext(message," drink "," liquid ")
message = replacetext(message," feminist "," empowered woman ")
- message = replacetext(message," i hate you "," you're mean ")
- message = replacetext(message," nigger "," african american ")
+ message = replacetext(message," i hate you "," you're a mean ")
message = replacetext(message," jew "," jewish ")
message = replacetext(message," shit "," shiz ")
message = replacetext(message," crap "," poo ")
message = replacetext(message," slut "," tease ")
message = replacetext(message," ass "," butt ")
message = replacetext(message," damn "," dang ")
- message = replacetext(message," fuck "," ")
message = replacetext(message," penis "," privates ")
message = replacetext(message," cunt "," privates ")
- message = replacetext(message," dick "," jerk ")
+ message = replacetext(message," dick "," privates ")
message = replacetext(message," vagina "," privates ")
- return trim(message)
+ speech_args[SPEECH_MESSAGE] = trim(message)
/datum/mutation/human/unintelligible
@@ -102,30 +125,17 @@
text_gain_indication = "You can't seem to form any coherent thoughts!"
text_lose_indication = "Your mind feels more clear."
-/datum/mutation/human/unintelligible/say_mod(message)
- if(message)
- var/prefix=copytext(message,1,2)
- if(prefix == ";")
- message = copytext(message,2)
- else if(prefix in list(":","#"))
- prefix += copytext(message,2,3)
- message = copytext(message,3)
- else
- prefix=""
+/datum/mutation/human/unintelligible/on_acquiring(mob/living/carbon/human/owner)
+ . = ..()
+ if(.)
+ return
+ ADD_TRAIT(owner, TRAIT_UNINTELLIGIBLE_SPEECH, GENETIC_MUTATION)
- var/list/words = splittext(message," ")
- var/list/rearranged = list()
- for(var/i=1;i<=words.len;i++)
- var/cword = pick(words)
- words.Remove(cword)
- var/suffix = copytext(cword,length(cword)-1,length(cword))
- while(length(cword)>0 && suffix in list(".",",",";","!",":","?"))
- cword = copytext(cword,1 ,length(cword)-1)
- suffix = copytext(cword,length(cword)-1,length(cword) )
- if(length(cword))
- rearranged += cword
- message ="[prefix][jointext(rearranged," ")]"
- return message
+/datum/mutation/human/unintelligible/on_losing(mob/living/carbon/human/owner)
+ . = ..()
+ if(.)
+ return
+ REMOVE_TRAIT(owner, TRAIT_UNINTELLIGIBLE_SPEECH, GENETIC_MUTATION)
/datum/mutation/human/swedish
@@ -135,7 +145,20 @@
text_gain_indication = "You feel Swedish, however that works."
text_lose_indication = "The feeling of Swedishness passes."
-/datum/mutation/human/swedish/say_mod(message)
+/datum/mutation/human/swedish/on_acquiring(mob/living/carbon/human/owner)
+ . = ..()
+ if(.)
+ return
+ RegisterSignal(owner, COMSIG_MOB_SAY, .proc/handle_speech)
+
+/datum/mutation/human/swedish/on_losing(mob/living/carbon/human/owner)
+ . = ..()
+ if(.)
+ return
+ UnregisterSignal(owner, COMSIG_MOB_SAY)
+
+/datum/mutation/human/swedish/proc/handle_speech(datum/source, list/speech_args)
+ var/message = speech_args[SPEECH_MESSAGE]
if(message)
message = replacetext(message,"w","v")
message = replacetext(message,"j","y")
@@ -144,7 +167,7 @@
message = replacetext(message,"o",pick("�","�","o"))
if(prob(30))
message += " Bork[pick("",", bork",", bork, bork")]!"
- return message
+ speech_args[SPEECH_MESSAGE] = trim(message)
/datum/mutation/human/chav
@@ -154,7 +177,20 @@
text_gain_indication = "Ye feel like a reet prat like, innit?"
text_lose_indication = "You no longer feel like being rude and sassy."
-/datum/mutation/human/chav/say_mod(message)
+/datum/mutation/human/chav/on_acquiring(mob/living/carbon/human/owner)
+ . = ..()
+ if(.)
+ return
+ RegisterSignal(owner, COMSIG_MOB_SAY, .proc/handle_speech)
+
+/datum/mutation/human/chav/on_losing(mob/living/carbon/human/owner)
+ . = ..()
+ if(.)
+ return
+ UnregisterSignal(owner, COMSIG_MOB_SAY)
+
+/datum/mutation/human/chav/proc/handle_speech(datum/source, list/speech_args)
+ var/message = speech_args[SPEECH_MESSAGE]
if(message)
message = " [message] "
message = replacetext(message," looking at "," gawpin' at ")
@@ -178,7 +214,7 @@
message = replacetext(message," break "," do ")
message = replacetext(message," your "," yer ")
message = replacetext(message," security "," coppers ")
- return trim(message)
+ speech_args[SPEECH_MESSAGE] = trim(message)
/datum/mutation/human/elvis
@@ -199,7 +235,20 @@
if(prob(15))
owner.visible_message("[owner] [pick("jiggles their hips", "rotates their hips", "gyrates their hips", "taps their foot", "dances to an imaginary song", "jiggles their legs", "snaps their fingers")]!")
-/datum/mutation/human/elvis/say_mod(message)
+/datum/mutation/human/elvis/on_acquiring(mob/living/carbon/human/owner)
+ . = ..()
+ if(.)
+ return
+ RegisterSignal(owner, COMSIG_MOB_SAY, .proc/handle_speech)
+
+/datum/mutation/human/elvis/on_losing(mob/living/carbon/human/owner)
+ . = ..()
+ if(.)
+ return
+ UnregisterSignal(owner, COMSIG_MOB_SAY)
+
+/datum/mutation/human/elvis/proc/handle_speech(datum/source, list/speech_args)
+ var/message = speech_args[SPEECH_MESSAGE]
if(message)
message = " [message] "
message = replacetext(message," i'm not "," I aint ")
@@ -211,7 +260,7 @@
message = replacetext(message," yes ",pick(" sure", "yea "))
message = replacetext(message," faggot "," square ")
message = replacetext(message," muh valids "," getting my kicks ")
- return trim(message)
+ speech_args[SPEECH_MESSAGE] = trim(message)
/datum/mutation/human/stoner
@@ -229,4 +278,4 @@
/datum/mutation/human/stoner/on_losing(mob/living/carbon/human/owner)
..()
owner.grant_language(/datum/language/common)
- owner.remove_language(/datum/language/beachbum)
\ No newline at end of file
+ owner.remove_language(/datum/language/beachbum)
diff --git a/code/datums/outfit.dm b/code/datums/outfit.dm
index 1f5c28d3c2..8386b59d97 100755
--- a/code/datums/outfit.dm
+++ b/code/datums/outfit.dm
@@ -21,6 +21,7 @@
var/l_hand = null
var/internals_slot = null //ID of slot containing a gas tank
var/list/backpack_contents = null // In the list(path=count,otherpath=count) format
+ var/box // Internals box. Will be inserted at the start of backpack_contents
var/list/implants = null
var/accessory = null
@@ -83,6 +84,13 @@
H.equip_to_slot_or_del(new l_pocket(H),SLOT_L_STORE)
if(r_pocket)
H.equip_to_slot_or_del(new r_pocket(H),SLOT_R_STORE)
+
+ if(box)
+ if(!backpack_contents)
+ backpack_contents = list()
+ backpack_contents.Insert(1, box)
+ backpack_contents[box] = 1
+
if(backpack_contents)
for(var/path in backpack_contents)
var/number = backpack_contents[path]
@@ -104,7 +112,7 @@
H.update_action_buttons_icon()
if(implants)
for(var/implant_type in implants)
- var/obj/item/implant/I = new implant_type(H)
+ var/obj/item/implant/I = new implant_type
I.implant(H, null, TRUE)
H.update_body()
diff --git a/code/datums/radiation_wave.dm b/code/datums/radiation_wave.dm
index 5fb777f9c5..6118428547 100644
--- a/code/datums/radiation_wave.dm
+++ b/code/datums/radiation_wave.dm
@@ -103,7 +103,6 @@
/obj/structure/cable,
/obj/machinery/atmospherics,
/obj/item/ammo_casing,
- /obj/item/implant,
/obj/singularity
))
if(!can_contaminate || blacklisted[thing.type])
diff --git a/code/datums/ruins/space.dm b/code/datums/ruins/space.dm
index 22fca23902..9762426608 100644
--- a/code/datums/ruins/space.dm
+++ b/code/datums/ruins/space.dm
@@ -305,3 +305,9 @@
suffix = "spacehermit.dmm"
name = "Space Hermit"
description = "A late awakening cryo pod in a crashed escape pod wakes up to find what befell of his fellow survivors. Contains all the necessary resources to actually make it out alive. Good luck."
+
+/datum/map_template/ruin/space/advancedlab
+ id = "advancedlab"
+ suffix = "advancedlab.dmm"
+ name = "Abductor Replication Lab"
+ description = "Some scientists tried and almost succeeded to recreate abductor tools. Somewhat slower and a bit less modern than their originals, these tools are the best you can get if you aren't an alien."
diff --git a/code/datums/saymode.dm b/code/datums/saymode.dm
index c4f485653e..3b6fae5aee 100644
--- a/code/datums/saymode.dm
+++ b/code/datums/saymode.dm
@@ -11,7 +11,7 @@
/datum/saymode/changeling
- key = "g"
+ key = MODE_KEY_CHANGELING
mode = MODE_CHANGELING
/datum/saymode/changeling/handle_message(mob/living/user, message, datum/language/language)
@@ -73,7 +73,7 @@
/datum/saymode/vocalcords
- key = "x"
+ key = MODE_KEY_VOCALCORDS
mode = MODE_VOCALCORDS
/datum/saymode/vocalcords/handle_message(mob/living/user, message, datum/language/language)
@@ -87,7 +87,7 @@
/datum/saymode/binary //everything that uses .b (silicons, drones, blobbernauts/spores, swarmers)
- key = "b"
+ key = MODE_KEY_BINARY
mode = MODE_BINARY
/datum/saymode/binary/handle_message(mob/living/user, message, datum/language/language)
diff --git a/code/datums/status_effects/buffs.dm b/code/datums/status_effects/buffs.dm
index 0512977d1a..563d06ca57 100644
--- a/code/datums/status_effects/buffs.dm
+++ b/code/datums/status_effects/buffs.dm
@@ -170,7 +170,7 @@
tick_interval = 4
alert_type = /obj/screen/alert/status_effect/his_grace
var/bloodlust = 0
-
+
/obj/screen/alert/status_effect/his_grace
name = "His Grace"
desc = "His Grace hungers, and you must feed Him."
@@ -208,6 +208,7 @@
owner.adjustToxLoss(-grace_heal, TRUE, TRUE)
owner.adjustOxyLoss(-(grace_heal * 2))
owner.adjustCloneLoss(-grace_heal)
+ owner.adjustStaminaLoss(-(grace_heal * 25))
/datum/status_effect/his_grace/on_remove()
owner.log_message("lost His Grace's stun immunity", LOG_ATTACK)
@@ -520,7 +521,7 @@
itemUser.adjustToxLoss(-1.5, forced = TRUE) //Because Slime People are people too
itemUser.adjustOxyLoss(-1.5)
itemUser.adjustStaminaLoss(-1.5)
- itemUser.adjustBrainLoss(-1.5)
+ itemUser.adjustOrganLoss(ORGAN_SLOT_BRAIN, -1.5)
itemUser.adjustCloneLoss(-0.5) //Becasue apparently clone damage is the bastion of all health
//Heal all those around you, unbiased
for(var/mob/living/L in view(7, owner))
@@ -532,7 +533,7 @@
L.adjustToxLoss(-3.5, forced = TRUE) //Because Slime People are people too
L.adjustOxyLoss(-3.5)
L.adjustStaminaLoss(-3.5)
- L.adjustBrainLoss(-3.5)
+ L.adjustOrganLoss(ORGAN_SLOT_BRAIN, -3.5)
L.adjustCloneLoss(-1) //Becasue apparently clone damage is the bastion of all health
else if(issilicon(L))
L.adjustBruteLoss(-3.5)
diff --git a/code/datums/status_effects/debuffs.dm b/code/datums/status_effects/debuffs.dm
index 1c77520108..da59c79ac5 100644
--- a/code/datums/status_effects/debuffs.dm
+++ b/code/datums/status_effects/debuffs.dm
@@ -259,9 +259,9 @@
status_type = STATUS_EFFECT_REPLACE
alert_type = null
var/mutable_appearance/marked_underlay
- var/obj/item/twohanded/required/kinetic_crusher/hammer_synced
+ var/obj/item/twohanded/kinetic_crusher/hammer_synced
-/datum/status_effect/crusher_mark/on_creation(mob/living/new_owner, obj/item/twohanded/required/kinetic_crusher/new_hammer_synced)
+/datum/status_effect/crusher_mark/on_creation(mob/living/new_owner, obj/item/twohanded/kinetic_crusher/new_hammer_synced)
. = ..()
if(.)
hammer_synced = new_hammer_synced
@@ -358,19 +358,20 @@
else
new /obj/effect/temp_visual/bleed(get_turf(owner))
-/mob/living/proc/apply_necropolis_curse(set_curse)
+/mob/living/proc/apply_necropolis_curse(set_curse, duration = 10 MINUTES)
var/datum/status_effect/necropolis_curse/C = has_status_effect(STATUS_EFFECT_NECROPOLIS_CURSE)
if(!set_curse)
set_curse = pick(CURSE_BLINDING, CURSE_SPAWNING, CURSE_WASTING, CURSE_GRASPING)
if(QDELETED(C))
- apply_status_effect(STATUS_EFFECT_NECROPOLIS_CURSE, set_curse)
+ apply_status_effect(STATUS_EFFECT_NECROPOLIS_CURSE, set_curse, duration)
+
else
C.apply_curse(set_curse)
- C.duration += 3000 //additional curses add 5 minutes
+ C.duration += duration * 0.5 //additional curses add half their duration
/datum/status_effect/necropolis_curse
id = "necrocurse"
- duration = 6000 //you're cursed for 10 minutes have fun
+ duration = 10 MINUTES //you're cursed for 10 minutes have fun
tick_interval = 50
alert_type = null
var/curse_flags = NONE
@@ -378,7 +379,9 @@
var/effect_cooldown = 100
var/obj/effect/temp_visual/curse/wasting_effect = new
-/datum/status_effect/necropolis_curse/on_creation(mob/living/new_owner, set_curse)
+/datum/status_effect/necropolis_curse/on_creation(mob/living/new_owner, set_curse, _duration)
+ if(_duration)
+ duration = _duration
. = ..()
if(.)
apply_curse(set_curse)
@@ -507,3 +510,101 @@
desc = "Your body is covered in blue ichor! You can't be revived by vitality matrices."
icon_state = "ichorial_stain"
alerttooltipstyle = "clockcult"
+
+datum/status_effect/pacify
+ id = "pacify"
+ status_type = STATUS_EFFECT_REPLACE
+ tick_interval = 1
+ duration = 100
+ alert_type = null
+
+/datum/status_effect/pacify/on_creation(mob/living/new_owner, set_duration)
+ if(isnum(set_duration))
+ duration = set_duration
+ . = ..()
+
+/datum/status_effect/pacify/on_apply()
+ ADD_TRAIT(owner, TRAIT_PACIFISM, "status_effect")
+ return ..()
+
+/datum/status_effect/pacify/on_remove()
+ REMOVE_TRAIT(owner, TRAIT_PACIFISM, "status_effect")
+
+/datum/status_effect/trance
+ id = "trance"
+ status_type = STATUS_EFFECT_UNIQUE
+ duration = 300
+ tick_interval = 10
+ examine_text = "SUBJECTPRONOUN seems slow and unfocused."
+ var/stun = TRUE
+ var/triggered = FALSE
+ alert_type = null
+
+/obj/screen/alert/status_effect/trance
+ name = "Trance"
+ desc = "Everything feels so distant, and you can feel your thoughts forming loops inside your head..."
+ icon_state = "high"
+
+/datum/status_effect/trance/tick()
+ if(HAS_TRAIT(owner, "hypnotherapy"))
+ if(triggered == TRUE)
+ UnregisterSignal(owner, COMSIG_MOVABLE_HEAR)
+ RegisterSignal(owner, COMSIG_MOVABLE_HEAR, .proc/hypnotize)
+ ADD_TRAIT(owner, TRAIT_MUTE, "trance")
+ if(!owner.has_quirk(/datum/quirk/monochromatic))
+ owner.add_client_colour(/datum/client_colour/monochrome)
+ to_chat(owner, "[pick("You feel your thoughts slow down...", "You suddenly feel extremely dizzy...", "You feel like you're in the middle of a dream...","You feel incredibly relaxed...")]")
+ triggered = FALSE
+ else
+ return
+ if(stun)
+ owner.Stun(60, TRUE, TRUE)
+ owner.dizziness = 20
+
+/datum/status_effect/trance/on_apply()
+ if(!iscarbon(owner))
+ return FALSE
+ if(HAS_TRAIT(owner, "hypnotherapy"))
+ RegisterSignal(owner, COMSIG_MOVABLE_HEAR, .proc/listen)
+ return TRUE
+ alert_type = /obj/screen/alert/status_effect/trance
+ RegisterSignal(owner, COMSIG_MOVABLE_HEAR, .proc/hypnotize)
+ ADD_TRAIT(owner, TRAIT_MUTE, "trance")
+ if(!owner.has_quirk(/datum/quirk/monochromatic))
+ owner.add_client_colour(/datum/client_colour/monochrome)
+ owner.visible_message("[stun ? "[owner] stands still as [owner.p_their()] eyes seem to focus on a distant point." : ""]", \
+ "[pick("You feel your thoughts slow down...", "You suddenly feel extremely dizzy...", "You feel like you're in the middle of a dream...","You feel incredibly relaxed...")]")
+ return TRUE
+
+/datum/status_effect/trance/on_creation(mob/living/new_owner, _duration, _stun = TRUE, source_quirk = FALSE)//hypnoquirk makes no visible message, prevents self antag messages, and places phrase below objectives.
+ duration = _duration
+ stun = _stun
+ if(source_quirk == FALSE && HAS_TRAIT(owner, "hypnotherapy"))
+ REMOVE_TRAIT(owner, "hypnotherapy", ROUNDSTART_TRAIT)
+ return ..()
+
+/datum/status_effect/trance/on_remove()
+ UnregisterSignal(owner, COMSIG_MOVABLE_HEAR)
+ REMOVE_TRAIT(owner, TRAIT_MUTE, "trance")
+ owner.dizziness = 0
+ if(!owner.has_quirk(/datum/quirk/monochromatic))
+ owner.remove_client_colour(/datum/client_colour/monochrome)
+ to_chat(owner, "You snap out of your trance!")
+
+/datum/status_effect/trance/proc/listen(datum/source, message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, message_mode)
+ to_chat(owner, "[speaker] accidentally sets off your implanted trigger, sending you into a hypnotic daze!")
+ triggered = TRUE
+
+/datum/status_effect/trance/proc/hypnotize(datum/source, message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, message_mode)
+ if(!owner.can_hear())
+ return
+ if(speaker == owner)
+ return
+ var/mob/living/carbon/C = owner
+ C.cure_trauma_type(/datum/brain_trauma/hypnosis, TRAUMA_RESILIENCE_SURGERY) //clear previous hypnosis
+ if(HAS_TRAIT(C, "hypnotherapy"))
+ addtimer(CALLBACK(C, /mob/living/carbon.proc/gain_trauma, /datum/brain_trauma/hypnosis, TRAUMA_RESILIENCE_SURGERY, raw_message, TRUE), 10)
+ else
+ addtimer(CALLBACK(C, /mob/living/carbon.proc/gain_trauma, /datum/brain_trauma/hypnosis, TRAUMA_RESILIENCE_SURGERY, raw_message), 10)
+ addtimer(CALLBACK(C, /mob/living.proc/Stun, 60, TRUE, TRUE), 15) //Take some time to think about it
+ qdel(src)
diff --git a/code/datums/traits/_quirk.dm b/code/datums/traits/_quirk.dm
index cc6dd8db3f..12e34b0c90 100644
--- a/code/datums/traits/_quirk.dm
+++ b/code/datums/traits/_quirk.dm
@@ -13,7 +13,6 @@
var/mob/living/quirk_holder
/datum/quirk/New(mob/living/quirk_mob, spawn_effects)
- ..()
if(!quirk_mob || (human_only && !ishuman(quirk_mob)) || quirk_mob.has_quirk(type))
qdel(src)
quirk_holder = quirk_mob
diff --git a/code/datums/traits/good.dm b/code/datums/traits/good.dm
index 300a1264eb..dffdc92630 100644
--- a/code/datums/traits/good.dm
+++ b/code/datums/traits/good.dm
@@ -186,3 +186,20 @@
var/obj/item/autosurgeon/gloweyes/gloweyes = new(get_turf(H))
H.equip_to_slot(gloweyes, SLOT_IN_BACKPACK)
H.regenerate_icons()
+
+/datum/quirk/bloodpressure
+ name = "Polycythemia vera"
+ desc = "You've a treated form of Polycythemia vera that increases the total blood volume inside of you as well as the rate of replenishment!"
+ value = 2 //I honeslty dunno if this is a good trait? I just means you use more of medbays blood and make janitors madder, but you also regen blood a lil faster.
+ mob_trait = TRAIT_HIGH_BLOOD
+ gain_text = "You feel full of blood!"
+ lose_text = "You feel like your blood pressure went down."
+
+/datum/quirk/bloodpressure/add()
+ var/mob/living/M = quirk_holder
+ M.blood_ratio = 1.2
+ M.blood_volume += 150
+
+/datum/quirk/bloodpressure/remove()
+ var/mob/living/M = quirk_holder
+ M.blood_ratio = 1
diff --git a/code/datums/traits/negative.dm b/code/datums/traits/negative.dm
index 36c6733cf7..178e1aaeb0 100644
--- a/code/datums/traits/negative.dm
+++ b/code/datums/traits/negative.dm
@@ -119,7 +119,7 @@
medical_record_text = "Patient has a tumor in their brain that is slowly driving them to brain death."
/datum/quirk/brainproblems/on_process()
- quirk_holder.adjustBrainLoss(0.2)
+ quirk_holder.adjustOrganLoss(ORGAN_SLOT_BRAIN, 0.2)
/datum/quirk/nearsighted //t. errorage
name = "Nearsighted"
@@ -345,12 +345,42 @@
gain_text = "You find yourself unable to speak!"
lose_text = "You feel a growing strength in your vocal chords."
medical_record_text = "Functionally mute, patient is unable to use their voice in any capacity."
+ var/datum/brain_trauma/severe/mute/mute
/datum/quirk/mute/add()
var/mob/living/carbon/human/H = quirk_holder
- H.gain_trauma(TRAIT_MUTE, TRAUMA_RESILIENCE_SURGERY)
+ mute = new
+ H.gain_trauma(mute, TRAUMA_RESILIENCE_SURGERY)
/datum/quirk/mute/on_process()
if(quirk_holder.mind && LAZYLEN(quirk_holder.mind.antag_datums))
to_chat(quirk_holder, "Your antagonistic nature has caused your voice to be heard.")
qdel(src)
+
+
+/datum/quirk/unstable
+ name = "Unstable"
+ desc = "Due to past troubles, you are unable to recover your sanity if you lose it. Be very careful managing your mood!"
+ value = -2
+ mob_trait = TRAIT_UNSTABLE
+ gain_text = "There's a lot on your mind right now."
+ lose_text = "Your mind finally feels calm."
+ medical_record_text = "Patient's mind is in a vulnerable state, and cannot recover from traumatic events."
+
+/datum/quirk/blindness
+ name = "Blind"
+ desc = "You are completely blind, nothing can counteract this."
+ value = -4
+ gain_text = "You can't see anything."
+ lose_text = "You miraculously gain back your vision."
+ medical_record_text = "Subject has permanent blindness."
+
+/datum/quirk/blindness/add()
+ quirk_holder.become_blind(ROUNDSTART_TRAIT)
+
+/datum/quirk/blindness/on_spawn()
+ var/mob/living/carbon/human/H = quirk_holder
+ var/obj/item/clothing/glasses/sunglasses/blindfold/white/glasses = new(get_turf(H))
+ if(!H.equip_to_slot_if_possible(glasses, SLOT_GLASSES, bypass_equip_delay_self = TRUE)) //if you can't put it on the user's eyes, put it in their hands, otherwise put it on their eyes eyes
+ H.put_in_hands(glasses)
+ H.regenerate_icons()
diff --git a/code/datums/wires/_wires.dm b/code/datums/wires/_wires.dm
index f1b08ee18b..fab5a1313c 100644
--- a/code/datums/wires/_wires.dm
+++ b/code/datums/wires/_wires.dm
@@ -231,6 +231,9 @@
// Same for anyone with an abductor multitool.
else if(user.is_holding_item_of_type(/obj/item/multitool/abductor))
reveal_wires = TRUE
+ // and advanced multitool
+ else if(user.is_holding_item_of_type(/obj/item/multitool/advanced))
+ reveal_wires = TRUE
// Station blueprints do that too, but only if the wires are not randomized.
else if(user.is_holding_item_of_type(/obj/item/areaeditor/blueprints) && !randomize)
diff --git a/code/datums/wires/autolathe.dm b/code/datums/wires/autolathe.dm
index ebad7d1469..f83bab5910 100644
--- a/code/datums/wires/autolathe.dm
+++ b/code/datums/wires/autolathe.dm
@@ -30,6 +30,7 @@
addtimer(CALLBACK(A, /obj/machinery/autolathe.proc/reset, wire), 60)
if(WIRE_SHOCK)
A.shocked = !A.shocked
+ A.shock(usr, 50)
addtimer(CALLBACK(A, /obj/machinery/autolathe.proc/reset, wire), 60)
if(WIRE_DISABLE)
A.disabled = !A.disabled
@@ -40,9 +41,11 @@
switch(wire)
if(WIRE_HACK)
A.adjust_hacked(!mend)
- if(WIRE_HACK)
+ if(WIRE_SHOCK)
A.shocked = !mend
+ A.shock(usr, 50)
if(WIRE_DISABLE)
A.disabled = !mend
if(WIRE_ZAP)
- A.shock(usr, 50)
\ No newline at end of file
+ A.shock(usr, 50)
+
diff --git a/code/game/area/Space_Station_13_areas.dm b/code/game/area/Space_Station_13_areas.dm
index 8a40fc8b56..798cd9c026 100644
--- a/code/game/area/Space_Station_13_areas.dm
+++ b/code/game/area/Space_Station_13_areas.dm
@@ -339,6 +339,7 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
/area/crew_quarters/heads/captain
name = "Captain's Office"
icon_state = "captain"
+ clockwork_warp_allowed = FALSE
/area/crew_quarters/heads/captain/private
name = "Captain's Quarters"
diff --git a/code/game/area/areas/ruins/space.dm b/code/game/area/areas/ruins/space.dm
index 00a7fed012..d5ceb833b7 100644
--- a/code/game/area/areas/ruins/space.dm
+++ b/code/game/area/areas/ruins/space.dm
@@ -467,3 +467,8 @@
/area/ruin/space/has_grav/powered/ancient_shuttle
name = "Ancient Shuttle"
icon_state = "yellow"
+
+// Abductor Replication Lab
+/area/ruin/space/has_grav/powered/advancedlab
+ name = "Abductor Replication Lab"
+ icon_state = "yellow"
diff --git a/code/game/atoms.dm b/code/game/atoms.dm
index 88fe2c8d13..7c9dc3f4fe 100644
--- a/code/game/atoms.dm
+++ b/code/game/atoms.dm
@@ -235,6 +235,11 @@
SEND_SIGNAL(src, COMSIG_ATOM_BULLET_ACT, P, def_zone)
. = P.on_hit(src, 0, def_zone)
+//used on altdisarm() for special interactions between the shoved victim (target) and the src, with user being the one shoving the target on it.
+// IMPORTANT: if you wish to add a new own shove_act() to a certain object, remember to add SHOVABLE_ONTO to its obj_flags bitfied var first.
+/atom/proc/shove_act(mob/living/target, mob/living/user)
+ return FALSE
+
/atom/proc/in_contents_of(container)//can take class or object instance as argument
if(ispath(container))
if(istype(src.loc, container))
@@ -379,7 +384,7 @@
SEND_SIGNAL(src, COMSIG_ATOM_ACID_ACT, acidpwr, acid_volume)
/atom/proc/emag_act()
- SEND_SIGNAL(src, COMSIG_ATOM_EMAG_ACT)
+ return SEND_SIGNAL(src, COMSIG_ATOM_EMAG_ACT)
/atom/proc/rad_act(strength)
SEND_SIGNAL(src, COMSIG_ATOM_RAD_ACT, strength)
diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm
index f88f4f8e41..83762240bc 100644
--- a/code/game/atoms_movable.dm
+++ b/code/game/atoms_movable.dm
@@ -14,6 +14,7 @@
var/verb_exclaim = "exclaims"
var/verb_whisper = "whispers"
var/verb_yell = "yells"
+ var/speech_span
var/inertia_dir = 0
var/atom/inertia_last_loc
var/inertia_moving = 0
diff --git a/code/game/communications.dm b/code/game/communications.dm
index 212a99f966..ed470473d9 100644
--- a/code/game/communications.dm
+++ b/code/game/communications.dm
@@ -91,35 +91,35 @@ GLOBAL_LIST_EMPTY(all_radios)
// use in maps, such as in intercoms.
GLOBAL_LIST_INIT(radiochannels, list(
- "Common" = FREQ_COMMON,
- "Science" = FREQ_SCIENCE,
- "Command" = FREQ_COMMAND,
- "Medical" = FREQ_MEDICAL,
- "Engineering" = FREQ_ENGINEERING,
- "Security" = FREQ_SECURITY,
- "CentCom" = FREQ_CENTCOM,
- "Syndicate" = FREQ_SYNDICATE,
- "Supply" = FREQ_SUPPLY,
- "Service" = FREQ_SERVICE,
- "AI Private" = FREQ_AI_PRIVATE,
- "Red Team" = FREQ_CTF_RED,
- "Blue Team" = FREQ_CTF_BLUE
+ RADIO_CHANNEL_COMMON = FREQ_COMMON,
+ RADIO_CHANNEL_SCIENCE = FREQ_SCIENCE,
+ RADIO_CHANNEL_COMMAND = FREQ_COMMAND,
+ RADIO_CHANNEL_MEDICAL = FREQ_MEDICAL,
+ RADIO_CHANNEL_ENGINEERING = FREQ_ENGINEERING,
+ RADIO_CHANNEL_SECURITY = FREQ_SECURITY,
+ RADIO_CHANNEL_CENTCOM = FREQ_CENTCOM,
+ RADIO_CHANNEL_SYNDICATE = FREQ_SYNDICATE,
+ RADIO_CHANNEL_SUPPLY = FREQ_SUPPLY,
+ RADIO_CHANNEL_SERVICE = FREQ_SERVICE,
+ RADIO_CHANNEL_AI_PRIVATE = FREQ_AI_PRIVATE,
+ RADIO_CHANNEL_CTF_RED = FREQ_CTF_RED,
+ RADIO_CHANNEL_CTF_BLUE = FREQ_CTF_BLUE
))
GLOBAL_LIST_INIT(reverseradiochannels, list(
- "[FREQ_COMMON]" = "Common",
- "[FREQ_SCIENCE]" = "Science",
- "[FREQ_COMMAND]" = "Command",
- "[FREQ_MEDICAL]" = "Medical",
- "[FREQ_ENGINEERING]" = "Engineering",
- "[FREQ_SECURITY]" = "Security",
- "[FREQ_CENTCOM]" = "CentCom",
- "[FREQ_SYNDICATE]" = "Syndicate",
- "[FREQ_SUPPLY]" = "Supply",
- "[FREQ_SERVICE]" = "Service",
- "[FREQ_AI_PRIVATE]" = "AI Private",
- "[FREQ_CTF_RED]" = "Red Team",
- "[FREQ_CTF_BLUE]" = "Blue Team"
+ "[FREQ_COMMON]" = RADIO_CHANNEL_COMMON,
+ "[FREQ_SCIENCE]" = RADIO_CHANNEL_SCIENCE,
+ "[FREQ_COMMAND]" = RADIO_CHANNEL_COMMAND,
+ "[FREQ_MEDICAL]" = RADIO_CHANNEL_MEDICAL,
+ "[FREQ_ENGINEERING]" = RADIO_CHANNEL_ENGINEERING,
+ "[FREQ_SECURITY]" = RADIO_CHANNEL_SECURITY,
+ "[FREQ_CENTCOM]" = RADIO_CHANNEL_CENTCOM,
+ "[FREQ_SYNDICATE]" = RADIO_CHANNEL_SYNDICATE,
+ "[FREQ_SUPPLY]" = RADIO_CHANNEL_SUPPLY,
+ "[FREQ_SERVICE]" = RADIO_CHANNEL_SERVICE,
+ "[FREQ_AI_PRIVATE]" = RADIO_CHANNEL_AI_PRIVATE,
+ "[FREQ_CTF_RED]" = RADIO_CHANNEL_CTF_RED,
+ "[FREQ_CTF_BLUE]" = RADIO_CHANNEL_CTF_BLUE
))
/datum/radio_frequency
diff --git a/code/game/data_huds.dm b/code/game/data_huds.dm
index 8506a4133a..65a7c6a954 100644
--- a/code/game/data_huds.dm
+++ b/code/game/data_huds.dm
@@ -18,7 +18,7 @@
/datum/atom_hud/data
/datum/atom_hud/data/human/medical
- hud_icons = list(STATUS_HUD, HEALTH_HUD, NANITE_HUD)
+ hud_icons = list(STATUS_HUD, HEALTH_HUD, NANITE_HUD, RAD_HUD)
/datum/atom_hud/data/human/medical/basic
@@ -162,6 +162,7 @@
holder.icon_state = "hud[RoundHealth(src)]"
var/icon/I = icon(icon, icon_state, dir)
holder.pixel_y = I.Height() - world.icon_size
+ med_hud_set_radstatus()
//for carbon suit sensors
/mob/living/carbon/med_hud_set_health()
@@ -211,6 +212,22 @@
holder.icon_state = "hudhealthy"
+/mob/living/proc/med_hud_set_radstatus()
+ var/image/radholder = hud_list[RAD_HUD]
+ var/icon/I = icon(icon, icon_state, dir)
+ radholder.pixel_y = I.Height() - world.icon_size
+ var/mob/living/M = src
+ var/rads = M.radiation
+ switch(rads)
+ if(-INFINITY to RAD_MOB_SAFE)
+ radholder.icon_state = "hudradsafe"
+ if((RAD_MOB_SAFE+1) to RAD_MOB_MUTATE)
+ radholder.icon_state = "hudraddanger"
+ if((RAD_MOB_MUTATE+1) to RAD_MOB_VOMIT)
+ radholder.icon_state = "hudradlethal"
+ if((RAD_MOB_VOMIT+1) to INFINITY)
+ radholder.icon_state = "hudradnuke"
+
/***********************************************
Security HUDs! Basic mode shows only the job.
************************************************/
diff --git a/code/game/gamemodes/brother/traitor_bro.dm b/code/game/gamemodes/brother/traitor_bro.dm
index e25c4f7716..8bbe7f54ed 100644
--- a/code/game/gamemodes/brother/traitor_bro.dm
+++ b/code/game/gamemodes/brother/traitor_bro.dm
@@ -6,6 +6,7 @@
name = "traitor+brothers"
config_tag = "traitorbro"
restricted_jobs = list("AI", "Cyborg")
+ protected_jobs = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel", "Chief Engineer", "Chief Medical Officer", "Research Director", "Quartermaster")
announce_span = "danger"
announce_text = "There are Syndicate agents and Blood Brothers on the station!\n\
diff --git a/code/game/gamemodes/changeling/changeling.dm b/code/game/gamemodes/changeling/changeling.dm
index feb81d44e0..d6131ce58e 100644
--- a/code/game/gamemodes/changeling/changeling.dm
+++ b/code/game/gamemodes/changeling/changeling.dm
@@ -86,8 +86,11 @@ GLOBAL_VAR(changeling_team_objective_type) //If this is not null, we hand our th
var/datum/dna/chosen_dna = chosen_prof.dna
user.real_name = chosen_prof.name
user.underwear = chosen_prof.underwear
+ user.undie_color = chosen_prof.undie_color
user.undershirt = chosen_prof.undershirt
+ user.shirt_color =chosen_prof.shirt_color
user.socks = chosen_prof.socks
+ user.socks_color =chosen_prof.socks_color
chosen_dna.transfer_identity(user, 1)
user.updateappearance(mutcolor_update=1)
diff --git a/code/game/gamemodes/clock_cult/clock_cult.dm b/code/game/gamemodes/clock_cult/clock_cult.dm
index a24d846f15..51a34f4194 100644
--- a/code/game/gamemodes/clock_cult/clock_cult.dm
+++ b/code/game/gamemodes/clock_cult/clock_cult.dm
@@ -52,7 +52,7 @@ Credit where due:
if(!istype(M))
return FALSE
if(M.mind)
- if(ishuman(M) && (M.mind.assigned_role in list("Captain", "Chaplain")))
+ if(M.mind.assigned_role in list("Captain", "Chaplain"))
return FALSE
if(M.mind.enslaved_to && !is_servant_of_ratvar(M.mind.enslaved_to))
return FALSE
@@ -135,7 +135,7 @@ Credit where due:
required_enemies = 3
recommended_enemies = 5
enemy_minimum_age = 7
- protected_jobs = list("AI", "Cyborg", "Security Officer", "Warden", "Detective", "Head of Security", "Captain") //Silicons can eventually be converted
+ protected_jobs = list("AI", "Cyborg", "Security Officer", "Warden", "Detective", "Head of Security", "Head of Personnel", "Chief Engineer", "Chief Medical Officer", "Research Director", "Quartermaster") //Silicons can eventually be converted
restricted_jobs = list("Chaplain", "Captain")
announce_span = "brass"
announce_text = "Servants of Ratvar are trying to summon the Justiciar!\n\
@@ -275,7 +275,7 @@ Credit where due:
gloves = /obj/item/clothing/gloves/color/yellow
belt = /obj/item/storage/belt/utility/servant
backpack_contents = list(/obj/item/storage/box/engineer = 1, \
- /obj/item/clockwork/replica_fabricator = 1, /obj/item/stack/tile/brass/fifty = 1, /obj/item/paper/servant_primer = 1)
+ /obj/item/clockwork/replica_fabricator = 1, /obj/item/stack/tile/brass/fifty = 1, /obj/item/paper/servant_primer = 1, /obj/item/reagent_containers/food/drinks/bottle/holyoil = 1)
id = /obj/item/pda
var/plasmaman //We use this to determine if we should activate internals in post_equip()
@@ -338,6 +338,10 @@ Credit where due:
CLOCKCULTCHANGELOG\
\
\
+
Zelus oil: A new reagent. It can be used to heal the faithful to Ratvar, or kill heretics and moreso stun blood cultists,\
+ or splashed onto metal sheets to make brass. This chemical can be found in minimal quantities by grinding brass sheets.\
+
Brass Flasks:Intended to store Zelus Oil in, but can also be used as fragile single use throwing weapons in a pinch! \
+ These are crafted with a single sheet of brass and fit in the Clockwork Cuirass' suit storage.\
Good luck!"
/obj/item/paper/servant_primer/Initialize()
diff --git a/code/game/gamemodes/clown_ops/bananium_bomb.dm b/code/game/gamemodes/clown_ops/bananium_bomb.dm
index 0d5f0691c4..b63d5b2e79 100644
--- a/code/game/gamemodes/clown_ops/bananium_bomb.dm
+++ b/code/game/gamemodes/clown_ops/bananium_bomb.dm
@@ -42,18 +42,19 @@
var/obj/item/clothing/C
if(!H.w_uniform || H.dropItemToGround(H.w_uniform))
C = new /obj/item/clothing/under/rank/clown(H)
- C.item_flags |= NODROP //mwahaha
+ ADD_TRAIT(C, TRAIT_NODROP, CLOWN_NUKE_TRAIT)
H.equip_to_slot_or_del(C, SLOT_W_UNIFORM)
if(!H.shoes || H.dropItemToGround(H.shoes))
C = new /obj/item/clothing/shoes/clown_shoes(H)
- C.item_flags |= NODROP
+ ADD_TRAIT(C, TRAIT_NODROP, CLOWN_NUKE_TRAIT)
H.equip_to_slot_or_del(C, SLOT_SHOES)
if(!H.wear_mask || H.dropItemToGround(H.wear_mask))
C = new /obj/item/clothing/mask/gas/clown_hat(H)
- C.item_flags |= NODROP
+ ADD_TRAIT(C, TRAIT_NODROP, CLOWN_NUKE_TRAIT)
H.equip_to_slot_or_del(C, SLOT_WEAR_MASK)
H.dna.add_mutation(CLOWNMUT)
+ H.dna.add_mutation(SMILE)
H.gain_trauma(/datum/brain_trauma/mild/phobia, TRAUMA_RESILIENCE_LOBOTOMY, "clowns") //MWA HA HA
diff --git a/code/game/gamemodes/clown_ops/clown_ops.dm b/code/game/gamemodes/clown_ops/clown_ops.dm
index a666b57afd..66de72bad2 100644
--- a/code/game/gamemodes/clown_ops/clown_ops.dm
+++ b/code/game/gamemodes/clown_ops/clown_ops.dm
@@ -58,6 +58,7 @@
if(visualsOnly)
return
H.dna.add_mutation(CLOWNMUT)
+ H.dna.add_mutation(SMILE)
/datum/outfit/syndicate/clownop/leader
name = "Clown Operative Leader - Basic"
diff --git a/code/game/gamemodes/clown_ops/clown_weapons.dm b/code/game/gamemodes/clown_ops/clown_weapons.dm
index a3440fad39..9d1a3b650c 100644
--- a/code/game/gamemodes/clown_ops/clown_weapons.dm
+++ b/code/game/gamemodes/clown_ops/clown_weapons.dm
@@ -216,11 +216,11 @@
/obj/item/clothing/mask/fakemoustache/sticky/Initialize()
. = ..()
- item_flags |= NODROP
+ ADD_TRAIT(src, TRAIT_NODROP, STICKY_MOUSTACHE_TRAIT)
addtimer(CALLBACK(src, .proc/unstick), unstick_time)
/obj/item/clothing/mask/fakemoustache/sticky/proc/unstick()
- item_flags &= ~NODROP
+ ADD_TRAIT(src, TRAIT_NODROP, STICKY_MOUSTACHE_TRAIT)
//DARK H.O.N.K. AND CLOWN MECH WEAPONS
diff --git a/code/game/gamemodes/cult/cult.dm b/code/game/gamemodes/cult/cult.dm
index 8f091b6372..497cc2f1c3 100644
--- a/code/game/gamemodes/cult/cult.dm
+++ b/code/game/gamemodes/cult/cult.dm
@@ -16,7 +16,7 @@
if(!istype(M))
return FALSE
if(M.mind)
- if(ishuman(M) && (M.mind.assigned_role in list("Captain", "Chaplain")))
+ if(M.mind.assigned_role in list("Captain", "Chaplain"))
return FALSE
if(specific_cult && specific_cult.is_sacrifice_target(M.mind))
return FALSE
@@ -35,8 +35,8 @@
config_tag = "cult"
antag_flag = ROLE_CULTIST
false_report_weight = 10
- restricted_jobs = list("Chaplain","AI", "Cyborg", "Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel")
- protected_jobs = list()
+ restricted_jobs = list("AI", "Cyborg")
+ protected_jobs = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel", "Chief Engineer", "Chief Medical Officer", "Research Director", "Quartermaster")
required_players = 30
required_enemies = 3
recommended_enemies = 5
diff --git a/code/game/gamemodes/dynamic/dynamic.dm b/code/game/gamemodes/dynamic/dynamic.dm
new file mode 100644
index 0000000000..e22b785670
--- /dev/null
+++ b/code/game/gamemodes/dynamic/dynamic.dm
@@ -0,0 +1,748 @@
+#define CURRENT_LIVING_PLAYERS 1
+#define CURRENT_LIVING_ANTAGS 2
+#define CURRENT_DEAD_PLAYERS 3
+#define CURRENT_OBSERVERS 4
+
+#define ONLY_RULESET 1
+#define HIGHLANDER_RULESET 2
+#define TRAITOR_RULESET 4
+#define MINOR_RULESET 8
+
+#define RULESET_STOP_PROCESSING 1
+
+// -- Injection delays
+GLOBAL_VAR_INIT(dynamic_latejoin_delay_min, (5 MINUTES))
+GLOBAL_VAR_INIT(dynamic_latejoin_delay_max, (25 MINUTES))
+
+GLOBAL_VAR_INIT(dynamic_midround_delay_min, (15 MINUTES))
+GLOBAL_VAR_INIT(dynamic_midround_delay_max, (35 MINUTES))
+
+// Are HIGHLANDER_RULESETs allowed to stack?
+GLOBAL_VAR_INIT(dynamic_no_stacking, TRUE)
+// A number between -5 and +5.
+// A negative value will give a more peaceful round and
+// a positive value will give a round with higher threat.
+GLOBAL_VAR_INIT(dynamic_curve_centre, 0)
+// A number between 0.5 and 4.
+// Higher value will favour extreme rounds and
+// lower value rounds closer to the average.
+GLOBAL_VAR_INIT(dynamic_curve_width, 1.8)
+// If enabled only picks a single starting rule and executes only autotraitor midround ruleset.
+GLOBAL_VAR_INIT(dynamic_classic_secret, FALSE)
+// How many roundstart players required for high population override to take effect.
+GLOBAL_VAR_INIT(dynamic_high_pop_limit, 55)
+// If enabled does not accept or execute any rulesets.
+GLOBAL_VAR_INIT(dynamic_forced_extended, FALSE)
+// How high threat is required for HIGHLANDER_RULESETs stacking.
+// This is independent of dynamic_no_stacking.
+GLOBAL_VAR_INIT(dynamic_stacking_limit, 90)
+// List of forced roundstart rulesets.
+GLOBAL_LIST_EMPTY(dynamic_forced_roundstart_ruleset)
+// Forced threat level, setting this to zero or higher forces the roundstart threat to the value.
+GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
+
+/datum/game_mode/dynamic
+ name = "dynamic mode"
+ config_tag = "dynamic"
+
+ announce_span = "danger"
+ announce_text = "Dynamic mode!" // This needs to be changed maybe
+
+ reroll_friendly = FALSE;
+
+ // Threat logging vars
+ /// The "threat cap", threat shouldn't normally go above this and is used in ruleset calculations
+ var/threat_level = 0
+ /// Set at the beginning of the round. Spent by the mode to "purchase" rules.
+ var/threat = 0
+ /// Running information about the threat. Can store text or datum entries.
+ var/list/threat_log = list()
+ /// List of roundstart rules used for selecting the rules.
+ var/list/roundstart_rules = list()
+ /// List of latejoin rules used for selecting the rules.
+ var/list/latejoin_rules = list()
+ /// List of midround rules used for selecting the rules.
+ var/list/midround_rules = list()
+ /** # Pop range per requirement.
+ * If the value is five the range is:
+ * 0-4, 5-9, 10-14, 15-19, 20-24, 25-29, 30-34, 35-39, 40-54, 45+
+ * If it is six the range is:
+ * 0-5, 6-11, 12-17, 18-23, 24-29, 30-35, 36-41, 42-47, 48-53, 54+
+ * If it is seven the range is:
+ * 0-6, 7-13, 14-20, 21-27, 28-34, 35-41, 42-48, 49-55, 56-62, 63+
+ */
+ var/pop_per_requirement = 6
+ /// The requirement used for checking if a second rule should be selected.
+ var/list/second_rule_req = list(100, 100, 80, 70, 60, 50, 30, 20, 10, 0)
+ /// The requirement used for checking if a third rule should be selected.
+ var/list/third_rule_req = list(100, 100, 100, 90, 80, 70, 60, 50, 40, 30)
+ /// Threat requirement for a second ruleset when high pop override is in effect.
+ var/high_pop_second_rule_req = 40
+ /// Threat requirement for a third ruleset when high pop override is in effect.
+ var/high_pop_third_rule_req = 60
+ /// Number of players who were ready on roundstart.
+ var/roundstart_pop_ready = 0
+ /// List of candidates used on roundstart rulesets.
+ var/list/candidates = list()
+ /// Rules that are processed, rule_process is called on the rules in this list.
+ var/list/current_rules = list()
+ /// List of executed rulesets.
+ var/list/executed_rules = list()
+ /// Associative list of current players, in order: living players, living antagonists, dead players and observers.
+ var/list/list/current_players = list(CURRENT_LIVING_PLAYERS, CURRENT_LIVING_ANTAGS, CURRENT_DEAD_PLAYERS, CURRENT_OBSERVERS)
+ /// When world.time is over this number the mode tries to inject a latejoin ruleset.
+ var/latejoin_injection_cooldown = 0
+ /// When world.time is over this number the mode tries to inject a midround ruleset.
+ var/midround_injection_cooldown = 0
+ /// When TRUE GetInjectionChance returns 100.
+ var/forced_injection = FALSE
+ /// Forced ruleset to be executed for the next latejoin.
+ var/datum/dynamic_ruleset/latejoin/forced_latejoin_rule = null
+ /// When current_players was updated last time.
+ var/pop_last_updated = 0
+ /// How many percent of the rounds are more peaceful.
+ var/peaceful_percentage = 50
+ /// If a highlander executed.
+ var/highlander_executed = FALSE
+ /// If a only ruleset has been executed.
+ var/only_ruleset_executed = FALSE
+
+/datum/game_mode/dynamic/admin_panel()
+ var/list/dat = list("Game Mode Panel
Game Mode Panel
")
+ dat += "Dynamic Mode \[VV\] "
+ dat += "Threat Level: [threat_level] "
+
+ dat += "Threat to Spend: [threat]\[Adjust\]\[View Log\] "
+ dat += " "
+ dat += "Parameters: centre = [GLOB.dynamic_curve_centre] ; width = [GLOB.dynamic_curve_width]. "
+ dat += "On average, [peaceful_percentage]% of the rounds are more peaceful. "
+ dat += "Forced extended: [GLOB.dynamic_forced_extended ? "On" : "Off"] "
+ dat += "Classic secret (only autotraitor): [GLOB.dynamic_classic_secret ? "On" : "Off"] "
+ dat += "No stacking (only one round-ender): [GLOB.dynamic_no_stacking ? "On" : "Off"] "
+ dat += "Stacking limit: [GLOB.dynamic_stacking_limit] \[Adjust\]"
+ dat += " "
+ dat += "Executed rulesets: "
+ if (executed_rules.len > 0)
+ dat += " "
+ for (var/datum/dynamic_ruleset/DR in executed_rules)
+ dat += "[DR.ruletype] - [DR.name] "
+ else
+ dat += "none. "
+ dat += " Injection Timers: ([get_injection_chance(TRUE)]% chance) "
+ dat += "Latejoin: [(latejoin_injection_cooldown-world.time)>60*10 ? "[round((latejoin_injection_cooldown-world.time)/60/10,0.1)] minutes" : "[(latejoin_injection_cooldown-world.time)] seconds"] \[Now!\] "
+ dat += "Midround: [(midround_injection_cooldown-world.time)>60*10 ? "[round((midround_injection_cooldown-world.time)/60/10,0.1)] minutes" : "[(midround_injection_cooldown-world.time)] seconds"] \[Now!\] "
+ usr << browse(dat.Join(), "window=gamemode_panel;size=500x500")
+
+/datum/game_mode/dynamic/Topic(href, href_list)
+ if (..()) // Sanity, maybe ?
+ return
+ if(!check_rights(R_ADMIN))
+ message_admins("[usr.key] has attempted to override the game mode panel!")
+ log_admin("[key_name(usr)] tried to use the game mode panel without authorization.")
+ return
+ if (href_list["forced_extended"])
+ GLOB.dynamic_forced_extended = !GLOB.dynamic_forced_extended
+ else if (href_list["no_stacking"])
+ GLOB.dynamic_no_stacking = !GLOB.dynamic_no_stacking
+ else if (href_list["classic_secret"])
+ GLOB.dynamic_classic_secret = !GLOB.dynamic_classic_secret
+ else if (href_list["adjustthreat"])
+ var/threatadd = input("Specify how much threat to add (negative to subtract). This can inflate the threat level.", "Adjust Threat", 0) as null|num
+ if(!threatadd)
+ return
+ if(threatadd > 0)
+ create_threat(threatadd)
+ else
+ spend_threat(-threatadd)
+ else if (href_list["injectlate"])
+ latejoin_injection_cooldown = 0
+ forced_injection = TRUE
+ message_admins("[key_name(usr)] forced a latejoin injection.", 1)
+ else if (href_list["injectmid"])
+ midround_injection_cooldown = 0
+ forced_injection = TRUE
+ message_admins("[key_name(usr)] forced a midround injection.", 1)
+ else if (href_list["threatlog"])
+ show_threatlog(usr)
+ else if (href_list["stacking_limit"])
+ GLOB.dynamic_stacking_limit = input(usr,"Change the threat limit at which round-endings rulesets will start to stack.", "Change stacking limit", null) as num
+
+ admin_panel() // Refreshes the window
+
+// Checks if there are HIGHLANDER_RULESETs and calls the rule's round_result() proc
+/datum/game_mode/dynamic/set_round_result()
+ for(var/datum/dynamic_ruleset/rule in executed_rules)
+ if(rule.flags & HIGHLANDER_RULESET)
+ if(rule.check_finished()) // Only the rule that actually finished the round sets round result.
+ return rule.round_result()
+ // If it got to this part, just pick one highlander if it exists
+ for(var/datum/dynamic_ruleset/rule in executed_rules)
+ if(rule.flags & HIGHLANDER_RULESET)
+ return rule.round_result()
+ return ..()
+
+/datum/game_mode/dynamic/send_intercept()
+ . = "Central Command Status Summary"
+ switch(round(threat_level))
+ if(0 to 19)
+ update_playercounts()
+ if(!current_players[CURRENT_LIVING_ANTAGS].len)
+ . += "Peaceful Waypoint "
+ . += "Your station orbits deep within controlled, core-sector systems and serves as a waypoint for routine traffic through Nanotrasen's trade empire. Due to the combination of high security, interstellar traffic, and low strategic value, it makes any direct threat of violence unlikely. Your primary enemies will be incompetence and bored crewmen: try to organize team-building events to keep staffers interested and productive."
+ else
+ . += "Core Territory "
+ . += "Your station orbits within reliably mundane, secure space. Although Nanotrasen has a firm grip on security in your region, the valuable resources and strategic position aboard your station make it a potential target for infiltrations. Monitor crew for non-loyal behavior, but expect a relatively tame shift free of large-scale destruction. We expect great things from your station."
+ if(20 to 39)
+ . += "Anomalous Exogeology "
+ . += "Although your station lies within what is generally considered Nanotrasen-controlled space, the course of its orbit has caused it to cross unusually close to exogeological features with anomalous readings. Although these features offer opportunities for our research department, it is known that these little understood readings are often correlated with increased activity from competing interstellar organizations and individuals, among them the Wizard Federation and Cult of the Geometer of Blood - all known competitors for Anomaly Type B sites. Exercise elevated caution."
+ if(40 to 65)
+ . += "Contested System "
+ . += "Your station's orbit passes along the edge of Nanotrasen's sphere of influence. While subversive elements remain the most likely threat against your station, hostile organizations are bolder here, where our grip is weaker. Exercise increased caution against elite Syndicate strike forces, or Executives forbid, some kind of ill-conceived unionizing attempt."
+ if(66 to 79)
+ . += "Uncharted Space "
+ . += "Congratulations and thank you for participating in the NT 'Frontier' space program! Your station is actively orbiting a high value system far from the nearest support stations. Little is known about your region of space, and the opportunity to encounter the unknown invites greater glory. You are encouraged to elevate security as necessary to protect Nanotrasen assets."
+ if(80 to 99)
+ . += "Black Orbit "
+ . += "As part of a mandatory security protocol, we are required to inform you that as a result of your orbital pattern directly behind an astrological body (oriented from our nearest observatory), your station will be under decreased monitoring and support. It is anticipated that your extreme location and decreased surveillance could pose security risks. Avoid unnecessary risks and attempt to keep your station in one piece."
+ if(100)
+ . += "Impending Doom "
+ . += "Your station is somehow in the middle of hostile territory, in clear view of any enemy of the corporation. Your likelihood to survive is low, and station destruction is expected and almost inevitable. Secure any sensitive material and neutralize any enemy you will come across. It is important that you at least try to maintain the station. "
+ . += "Good luck."
+
+ if(station_goals.len)
+ . += "Special Orders for [station_name()]:"
+ for(var/datum/station_goal/G in station_goals)
+ G.on_report()
+ . += G.get_report()
+
+ print_command_report(., "Central Command Status Summary", announce=FALSE)
+ priority_announce("A summary has been copied and printed to all communications consoles.", "Security level elevated.", 'sound/ai/intercept.ogg')
+ if(GLOB.security_level < SEC_LEVEL_BLUE)
+ set_security_level(SEC_LEVEL_BLUE)
+
+// Yes, this is copy pasted from game_mode
+/datum/game_mode/dynamic/check_finished(force_ending)
+ if(!SSticker.setup_done || !gamemode_ready)
+ return FALSE
+ if(replacementmode && round_converted == 2)
+ return replacementmode.check_finished()
+ if(SSshuttle.emergency && (SSshuttle.emergency.mode == SHUTTLE_ENDGAME))
+ return TRUE
+ if(station_was_nuked)
+ return TRUE
+ if(force_ending)
+ return TRUE
+ for(var/datum/dynamic_ruleset/rule in executed_rules)
+ if(rule.flags & HIGHLANDER_RULESET)
+ return rule.check_finished()
+
+/datum/game_mode/dynamic/proc/show_threatlog(mob/admin)
+ if(!SSticker.HasRoundStarted())
+ alert("The round hasn't started yet!")
+ return
+
+ if(!check_rights(R_ADMIN))
+ return
+
+ var/list/out = list("Threat LogThreat Log Starting Threat: [threat_level] ")
+
+ for(var/entry in threat_log)
+ if(istext(entry))
+ out += "[entry] "
+
+ out += "Remaining threat/threat_level: [threat]/[threat_level]"
+
+ usr << browse(out.Join(), "window=threatlog;size=700x500")
+
+/// Generates the threat level using lorentz distribution and assigns peaceful_percentage.
+/datum/game_mode/dynamic/proc/generate_threat()
+ var/relative_threat = LORENTZ_DISTRIBUTION(GLOB.dynamic_curve_centre, GLOB.dynamic_curve_width)
+ threat_level = round(lorentz_to_threat(relative_threat), 0.1)
+
+ peaceful_percentage = round(LORENTZ_CUMULATIVE_DISTRIBUTION(relative_threat, GLOB.dynamic_curve_centre, GLOB.dynamic_curve_width), 0.01)*100
+
+ threat = threat_level
+
+/datum/game_mode/dynamic/can_start()
+ /* Disabled for now, had some changes that need to be tested and this might interfere with that.
+ if(GLOB.dynamic_curve_centre == 0)
+ // 10 is when the centre starts to decrease
+ // 6 is just 1 + 5 (from the maximum value and the one decreased)
+ // 1 just makes the curve look better, I don't know.
+ // Limited between 1 and 5 then inverted and rounded
+ // With this you get a centre curve that stays at -5 until 10 then first rapidly decreases but slows down at the end
+ GLOB.dynamic_curve_centre = round(-CLAMP((10*6/GLOB.player_list.len)-1, 0, 5), 0.5)
+ */
+ message_admins("Dynamic mode parameters for the round:")
+ message_admins("Centre is [GLOB.dynamic_curve_centre], Width is [GLOB.dynamic_curve_width], Forced extended is [GLOB.dynamic_forced_extended ? "Enabled" : "Disabled"], No stacking is [GLOB.dynamic_no_stacking ? "Enabled" : "Disabled"].")
+ message_admins("Stacking limit is [GLOB.dynamic_stacking_limit], Classic secret is [GLOB.dynamic_classic_secret ? "Enabled" : "Disabled"], High population limit is [GLOB.dynamic_high_pop_limit].")
+ log_game("DYNAMIC: Dynamic mode parameters for the round:")
+ log_game("DYNAMIC: Centre is [GLOB.dynamic_curve_centre], Width is [GLOB.dynamic_curve_width], Forced extended is [GLOB.dynamic_forced_extended ? "Enabled" : "Disabled"], No stacking is [GLOB.dynamic_no_stacking ? "Enabled" : "Disabled"].")
+ log_game("DYNAMIC: Stacking limit is [GLOB.dynamic_stacking_limit], Classic secret is [GLOB.dynamic_classic_secret ? "Enabled" : "Disabled"], High population limit is [GLOB.dynamic_high_pop_limit].")
+ if(GLOB.dynamic_forced_threat_level >= 0)
+ threat_level = round(GLOB.dynamic_forced_threat_level, 0.1)
+ threat = threat_level
+ else
+ generate_threat()
+
+ 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
+
+ var/midround_injection_cooldown_middle = 0.5*(GLOB.dynamic_midround_delay_max + GLOB.dynamic_midround_delay_min)
+ midround_injection_cooldown = round(CLAMP(EXP_DISTRIBUTION(midround_injection_cooldown_middle), GLOB.dynamic_midround_delay_min, GLOB.dynamic_midround_delay_max)) + world.time
+ log_game("DYNAMIC: Dynamic Mode initialized with a Threat Level of... [threat_level]!")
+ return TRUE
+
+/datum/game_mode/dynamic/pre_setup()
+ for (var/rule in subtypesof(/datum/dynamic_ruleset))
+ var/datum/dynamic_ruleset/ruleset = new rule()
+ // Simple check if the ruleset should be added to the lists.
+ if(ruleset.name == "")
+ continue
+ switch(ruleset.ruletype)
+ if("Roundstart")
+ roundstart_rules += ruleset
+ if ("Latejoin")
+ latejoin_rules += ruleset
+ if ("Midround")
+ if (ruleset.weight)
+ midround_rules += ruleset
+ for(var/mob/dead/new_player/player in GLOB.player_list)
+ if(player.ready == PLAYER_READY_TO_PLAY && player.mind)
+ roundstart_pop_ready++
+ candidates.Add(player)
+ log_game("DYNAMIC: Listing [roundstart_rules.len] round start rulesets, and [candidates.len] players ready.")
+ if (candidates.len <= 0)
+ return TRUE
+ if (roundstart_rules.len <= 0)
+ return TRUE
+
+ if(GLOB.dynamic_forced_roundstart_ruleset.len > 0)
+ rigged_roundstart()
+ else
+ roundstart()
+
+ var/starting_rulesets = ""
+ for (var/datum/dynamic_ruleset/roundstart/DR in executed_rules)
+ starting_rulesets += "[DR.name], "
+ candidates.Cut()
+ return TRUE
+
+/datum/game_mode/dynamic/post_setup(report)
+ update_playercounts()
+
+ for(var/datum/dynamic_ruleset/roundstart/rule in executed_rules)
+ rule.candidates.Cut() // The rule should not use candidates at this point as they all are null.
+ if(!rule.execute())
+ stack_trace("The starting rule \"[rule.name]\" failed to execute.")
+ ..()
+
+/// A simple roundstart proc used when dynamic_forced_roundstart_ruleset has rules in it.
+/datum/game_mode/dynamic/proc/rigged_roundstart()
+ message_admins("[GLOB.dynamic_forced_roundstart_ruleset.len] rulesets being forced. Will now attempt to draft players for them.")
+ log_game("DYNAMIC: [GLOB.dynamic_forced_roundstart_ruleset.len] rulesets being forced. Will now attempt to draft players for them.")
+ for (var/datum/dynamic_ruleset/roundstart/rule in GLOB.dynamic_forced_roundstart_ruleset)
+ message_admins("Drafting players for forced ruleset [rule.name].")
+ log_game("DYNAMIC: Drafting players for forced ruleset [rule.name].")
+ rule.mode = src
+ rule.candidates = candidates.Copy()
+ rule.trim_candidates()
+ if (rule.ready(TRUE))
+ picking_roundstart_rule(list(rule), forced = TRUE)
+
+/datum/game_mode/dynamic/proc/roundstart()
+ if (GLOB.dynamic_forced_extended)
+ log_game("DYNAMIC: Starting a round of forced extended.")
+ return TRUE
+ var/list/drafted_rules = list()
+ for (var/datum/dynamic_ruleset/roundstart/rule in roundstart_rules)
+ if (rule.acceptable(roundstart_pop_ready, threat_level) && threat >= rule.cost) // If we got the population and threat required
+ rule.candidates = candidates.Copy()
+ rule.trim_candidates()
+ if (rule.ready() && rule.candidates.len > 0)
+ drafted_rules[rule] = rule.weight
+
+ var/indice_pop = min(10,round(roundstart_pop_ready/pop_per_requirement)+1)
+ var/extra_rulesets_amount = 0
+ if (GLOB.dynamic_classic_secret)
+ extra_rulesets_amount = 0
+ else
+ if (roundstart_pop_ready > GLOB.dynamic_high_pop_limit)
+ message_admins("High Population Override is in effect! Threat Level will have more impact on which roles will appear, and player population less.")
+ log_game("DYNAMIC: High Population Override is in effect! Threat Level will have more impact on which roles will appear, and player population less.")
+ if (threat_level > high_pop_second_rule_req)
+ extra_rulesets_amount++
+ if (threat_level > high_pop_third_rule_req)
+ extra_rulesets_amount++
+ else
+ if (threat_level >= second_rule_req[indice_pop])
+ extra_rulesets_amount++
+ if (threat_level >= third_rule_req[indice_pop])
+ extra_rulesets_amount++
+
+ if (drafted_rules.len > 0 && picking_roundstart_rule(drafted_rules))
+ if (extra_rulesets_amount > 0) // We've got enough population and threat for a second rulestart rule
+ for (var/datum/dynamic_ruleset/roundstart/rule in drafted_rules)
+ if (rule.cost > threat)
+ drafted_rules -= rule
+ if (drafted_rules.len > 0 && picking_roundstart_rule(drafted_rules))
+ if (extra_rulesets_amount > 1) // We've got enough population and threat for a third rulestart rule
+ for (var/datum/dynamic_ruleset/roundstart/rule in drafted_rules)
+ if (rule.cost > threat)
+ drafted_rules -= rule
+ picking_roundstart_rule(drafted_rules)
+ else
+ return FALSE
+ return TRUE
+
+/// Picks a random roundstart rule from the list given as an argument and executes it.
+/datum/game_mode/dynamic/proc/picking_roundstart_rule(list/drafted_rules = list(), forced = FALSE)
+ var/datum/dynamic_ruleset/roundstart/starting_rule = pickweight(drafted_rules)
+ if(!starting_rule)
+ return FALSE
+
+ if(!forced)
+ if(only_ruleset_executed)
+ return FALSE
+ // Check if a blocking ruleset has been executed.
+ else if(check_blocking(starting_rule.blocking_rules, executed_rules))
+ drafted_rules -= starting_rule
+ if(drafted_rules.len <= 0)
+ return FALSE
+ starting_rule = pickweight(drafted_rules)
+ // Check if the ruleset is highlander and if a highlander ruleset has been executed
+ else if(starting_rule.flags & HIGHLANDER_RULESET)
+ if(threat < GLOB.dynamic_stacking_limit && GLOB.dynamic_no_stacking)
+ if(highlander_executed)
+ drafted_rules -= starting_rule
+ if(drafted_rules.len <= 0)
+ return FALSE
+ starting_rule = pickweight(drafted_rules)
+
+ log_game("DYNAMIC: Picking a [istype(starting_rule, /datum/dynamic_ruleset/roundstart/delayed/) ? " delayed " : ""] ruleset [starting_rule.name]")
+
+ roundstart_rules -= starting_rule
+ drafted_rules -= starting_rule
+
+ if (istype(starting_rule, /datum/dynamic_ruleset/roundstart/delayed/))
+ var/datum/dynamic_ruleset/roundstart/delayed/rule = starting_rule
+ addtimer(CALLBACK(src, .proc/execute_delayed, rule), rule.delay)
+
+ starting_rule.trim_candidates()
+ if (starting_rule.pre_execute())
+ spend_threat(starting_rule.cost)
+ threat_log += "[worldtime2text()]: Roundstart [starting_rule.name] spent [starting_rule.cost]"
+ if(starting_rule.flags & HIGHLANDER_RULESET)
+ highlander_executed = TRUE
+ else if(starting_rule.flags & ONLY_RULESET)
+ only_ruleset_executed = TRUE
+ executed_rules += starting_rule
+ if (starting_rule.persistent)
+ current_rules += starting_rule
+ for(var/mob/M in starting_rule.assigned)
+ for (var/datum/dynamic_ruleset/roundstart/rule in roundstart_rules)
+ if (!rule.ready())
+ drafted_rules -= rule // And removing rules that are no longer elligible
+ return TRUE
+ else
+ stack_trace("The starting rule \"[starting_rule.name]\" failed to pre_execute.")
+ return FALSE
+
+/// Executes delayed roundstart rules and has a hack in it.
+/datum/game_mode/dynamic/proc/execute_delayed(datum/dynamic_ruleset/roundstart/delayed/rule)
+ update_playercounts()
+ rule.candidates = current_players[CURRENT_LIVING_PLAYERS].Copy()
+ rule.trim_candidates()
+ if(rule.execute())
+ executed_rules += rule
+ if (rule.persistent)
+ current_rules += rule
+ return TRUE
+ else
+ stack_trace("The delayed roundstart rule \"[rule.name]\" failed to execute.")
+ return FALSE
+
+/// Picks a random midround OR latejoin rule from the list given as an argument and executes it.
+/// Also this could be named better.
+/datum/game_mode/dynamic/proc/picking_midround_latejoin_rule(list/drafted_rules = list(), forced = FALSE)
+ var/datum/dynamic_ruleset/rule = pickweight(drafted_rules)
+ if(!rule)
+ return FALSE
+
+ if(!forced)
+ if(only_ruleset_executed)
+ return FALSE
+ // Check if a blocking ruleset has been executed.
+ else if(check_blocking(rule.blocking_rules, executed_rules))
+ drafted_rules -= rule
+ if(drafted_rules.len <= 0)
+ return FALSE
+ rule = pickweight(drafted_rules)
+ // Check if the ruleset is highlander and if a highlander ruleset has been executed
+ else if(rule.flags & HIGHLANDER_RULESET)
+ if(threat < GLOB.dynamic_stacking_limit && GLOB.dynamic_no_stacking)
+ if(highlander_executed)
+ drafted_rules -= rule
+ if(drafted_rules.len <= 0)
+ return FALSE
+ rule = pickweight(drafted_rules)
+
+ if(!rule.repeatable)
+ if(rule.ruletype == "Latejoin")
+ latejoin_rules = remove_from_list(latejoin_rules, rule.type)
+ else if(rule.type == "Midround")
+ midround_rules = remove_from_list(midround_rules, rule.type)
+
+ if (rule.execute())
+ log_game("DYNAMIC: Injected a [rule.ruletype == "latejoin" ? "latejoin" : "midround"] ruleset [rule.name].")
+ spend_threat(rule.cost)
+ threat_log += "[worldtime2text()]: [rule.ruletype] [rule.name] spent [rule.cost]"
+ if(rule.flags & HIGHLANDER_RULESET)
+ highlander_executed = TRUE
+ else if(rule.flags & ONLY_RULESET)
+ only_ruleset_executed = TRUE
+ if(rule.ruletype == "Latejoin")
+ var/mob/M = pick(rule.candidates)
+ 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
+ rule.candidates.Cut()
+ if (rule.persistent)
+ current_rules += rule
+ return TRUE
+ else
+ stack_trace("The [rule.ruletype] rule \"[rule.name]\" failed to execute.")
+ return FALSE
+
+/// An experimental proc to allow admins to call rules on the fly or have rules call other rules.
+/datum/game_mode/dynamic/proc/picking_specific_rule(ruletype, forced = FALSE)
+ var/datum/dynamic_ruleset/midround/new_rule
+ if(ispath(ruletype))
+ new_rule = new ruletype() // You should only use it to call midround rules though.
+ else if(istype(ruletype, /datum/dynamic_ruleset))
+ new_rule = ruletype
+ else
+ return FALSE
+
+ if(!new_rule)
+ return FALSE
+
+ if(!forced)
+ if(only_ruleset_executed)
+ return FALSE
+ // Check if a blocking ruleset has been executed.
+ else if(check_blocking(new_rule.blocking_rules, executed_rules))
+ return FALSE
+ // Check if the ruleset is highlander and if a highlander ruleset has been executed
+ else if(new_rule.flags & HIGHLANDER_RULESET)
+ if(threat < GLOB.dynamic_stacking_limit && GLOB.dynamic_no_stacking)
+ if(highlander_executed)
+ return FALSE
+
+ update_playercounts()
+ if ((forced || (new_rule.acceptable(current_players[CURRENT_LIVING_PLAYERS].len, threat_level) && new_rule.cost <= threat)))
+ new_rule.candidates = current_players.Copy()
+ new_rule.trim_candidates()
+ if (new_rule.ready(forced))
+ spend_threat(new_rule.cost)
+ threat_log += "[worldtime2text()]: Forced rule [new_rule.name] spent [new_rule.cost]"
+ if (new_rule.execute()) // This should never fail since ready() returned 1
+ if(new_rule.flags & HIGHLANDER_RULESET)
+ highlander_executed = TRUE
+ else if(new_rule.flags & ONLY_RULESET)
+ only_ruleset_executed = TRUE
+ log_game("DYNAMIC: Making a call to a specific ruleset...[new_rule.name]!")
+ executed_rules += new_rule
+ if (new_rule.persistent)
+ current_rules += new_rule
+ return TRUE
+ else if (forced)
+ log_game("DYNAMIC: The ruleset [new_rule.name] couldn't be executed due to lack of elligible players.")
+ return FALSE
+
+/datum/game_mode/dynamic/process()
+ if (pop_last_updated < world.time - (60 SECONDS))
+ pop_last_updated = world.time
+ update_playercounts()
+
+ 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
+
+ if (midround_injection_cooldown < world.time)
+ if (GLOB.dynamic_forced_extended)
+ return
+
+ // Somehow it manages to trigger midround multiple times so this was moved here.
+ // There is no way this should be able to trigger an injection twice now.
+ var/midround_injection_cooldown_middle = 0.5*(GLOB.dynamic_midround_delay_max + GLOB.dynamic_midround_delay_min)
+ midround_injection_cooldown = (round(CLAMP(EXP_DISTRIBUTION(midround_injection_cooldown_middle), GLOB.dynamic_midround_delay_min, GLOB.dynamic_midround_delay_max)) + world.time)
+
+ // Time to inject some threat into the round
+ if(EMERGENCY_ESCAPED_OR_ENDGAMED) // Unless the shuttle is gone
+ return
+
+ log_game("DYNAMIC: Checking state of the round.")
+
+ update_playercounts()
+
+ if (prob(get_injection_chance()))
+ var/list/drafted_rules = list()
+ for (var/datum/dynamic_ruleset/midround/rule in midround_rules)
+ if (rule.acceptable(current_players[CURRENT_LIVING_PLAYERS].len, threat_level) && threat >= rule.cost)
+ // Classic secret : only autotraitor/minor roles
+ if (GLOB.dynamic_classic_secret && !((rule.flags & TRAITOR_RULESET) || (rule.flags & MINOR_RULESET)))
+ continue
+ rule.candidates = list()
+ rule.candidates = current_players.Copy()
+ rule.trim_candidates()
+ if (rule.ready() && rule.candidates.len > 0)
+ drafted_rules[rule] = rule.get_weight()
+ if (drafted_rules.len > 0)
+ picking_midround_latejoin_rule(drafted_rules)
+
+/// Updates current_players.
+/datum/game_mode/dynamic/proc/update_playercounts()
+ current_players[CURRENT_LIVING_PLAYERS] = list()
+ current_players[CURRENT_LIVING_ANTAGS] = list()
+ current_players[CURRENT_DEAD_PLAYERS] = list()
+ current_players[CURRENT_OBSERVERS] = list()
+ for (var/mob/M in GLOB.player_list)
+ if (istype(M, /mob/dead/new_player))
+ continue
+ if (M.stat != DEAD)
+ current_players[CURRENT_LIVING_PLAYERS].Add(M)
+ if (M.mind && (M.mind.special_role || M.mind.antag_datums?.len > 0))
+ current_players[CURRENT_LIVING_ANTAGS].Add(M)
+ else
+ if (istype(M,/mob/dead/observer))
+ var/mob/dead/observer/O = M
+ if (O.started_as_observer) // Observers
+ current_players[CURRENT_OBSERVERS].Add(M)
+ continue
+ current_players[CURRENT_DEAD_PLAYERS].Add(M) // Players who actually died (and admins who ghosted, would be nice to avoid counting them somehow)
+
+/// Gets the chance for latejoin and midround injection, the dry_run argument is only used for forced injection.
+/datum/game_mode/dynamic/proc/get_injection_chance(dry_run = FALSE)
+ if(forced_injection)
+ forced_injection = !dry_run
+ return 100
+ var/chance = 0
+ // If the high pop override is in effect, we reduce the impact of population on the antag injection chance
+ var/high_pop_factor = (current_players[CURRENT_LIVING_PLAYERS].len >= GLOB.dynamic_high_pop_limit)
+ var/max_pop_per_antag = max(5,15 - round(threat_level/10) - round(current_players[CURRENT_LIVING_PLAYERS].len/(high_pop_factor ? 10 : 5)))
+ if (!current_players[CURRENT_LIVING_ANTAGS].len)
+ chance += 50 // No antags at all? let's boost those odds!
+ else
+ var/current_pop_per_antag = current_players[CURRENT_LIVING_PLAYERS].len / current_players[CURRENT_LIVING_ANTAGS].len
+ if (current_pop_per_antag > max_pop_per_antag)
+ chance += min(50, 25+10*(current_pop_per_antag-max_pop_per_antag))
+ else
+ chance += 25-10*(max_pop_per_antag-current_pop_per_antag)
+ if (current_players[CURRENT_DEAD_PLAYERS].len > current_players[CURRENT_LIVING_PLAYERS].len)
+ chance -= 30 // More than half the crew died? ew, let's calm down on antags
+ if (threat > 70)
+ chance += 15
+ if (threat < 30)
+ chance -= 15
+ return round(max(0,chance))
+
+/// Removes type from the list
+/datum/game_mode/dynamic/proc/remove_from_list(list/type_list, type)
+ for(var/I in type_list)
+ if(istype(I, type))
+ type_list -= I
+ return type_list
+
+/// Checks if a type in blocking_list is in rule_list.
+/datum/game_mode/dynamic/proc/check_blocking(list/blocking_list, list/rule_list)
+ if(blocking_list.len > 0)
+ for(var/blocking in blocking_list)
+ for(var/datum/executed in rule_list)
+ if(blocking == executed.type)
+ return TRUE
+ return FALSE
+
+/// Checks if client age is age or older.
+/datum/game_mode/dynamic/proc/check_age(client/C, age)
+ enemy_minimum_age = age
+ if(get_remaining_days(C) == 0)
+ enemy_minimum_age = initial(enemy_minimum_age)
+ return TRUE // Available in 0 days = available right now = player is old enough to play.
+ enemy_minimum_age = initial(enemy_minimum_age)
+ return FALSE
+
+/datum/game_mode/dynamic/make_antag_chance(mob/living/carbon/human/newPlayer)
+ if (GLOB.dynamic_forced_extended)
+ return
+ if(EMERGENCY_ESCAPED_OR_ENDGAMED) // No more rules after the shuttle has left
+ return
+
+ update_playercounts()
+
+ if (forced_latejoin_rule)
+ forced_latejoin_rule.candidates = list(newPlayer)
+ forced_latejoin_rule.trim_candidates()
+ log_game("DYNAMIC: Forcing ruleset [forced_latejoin_rule]")
+ if (forced_latejoin_rule.ready(TRUE))
+ picking_midround_latejoin_rule(list(forced_latejoin_rule), forced = TRUE)
+ forced_latejoin_rule = null
+
+ else if (latejoin_injection_cooldown < world.time && prob(get_injection_chance()))
+ 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)
+ // Classic secret : only autotraitor/minor roles
+ if (GLOB.dynamic_classic_secret && !((rule.flags & TRAITOR_RULESET) || (rule.flags & MINOR_RULESET)))
+ continue
+ // No stacking : only one round-enter, unless > stacking_limit threat.
+ if (threat < GLOB.dynamic_stacking_limit && GLOB.dynamic_no_stacking)
+ if(rule.flags & HIGHLANDER_RULESET && highlander_executed)
+ continue
+
+ rule.candidates = list(newPlayer)
+ rule.trim_candidates()
+ if (rule.ready())
+ drafted_rules[rule] = rule.get_weight()
+
+ if (drafted_rules.len > 0 && picking_midround_latejoin_rule(drafted_rules))
+ 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)
+
+/// Generate threat and increase the threat_level if it goes beyond, capped at 100
+/datum/game_mode/dynamic/proc/create_threat(gain)
+ threat = min(100, threat+gain)
+ if(threat > threat_level)
+ threat_level = threat
+
+/// Expend threat, can't fall under 0.
+/datum/game_mode/dynamic/proc/spend_threat(cost)
+ threat = max(threat-cost,0)
+
+/// Turns the value generated by lorentz distribution to threat value between 0 and 100.
+/datum/game_mode/dynamic/proc/lorentz_to_threat(x)
+ switch (x)
+ if (-INFINITY to -20)
+ return rand(0, 10)
+ if (-20 to -10)
+ return RULE_OF_THREE(-40, -20, x) + 50
+ if (-10 to -5)
+ return RULE_OF_THREE(-30, -10, x) + 50
+ if (-5 to -2.5)
+ return RULE_OF_THREE(-20, -5, x) + 50
+ if (-2.5 to -0)
+ return RULE_OF_THREE(-10, -2.5, x) + 50
+ if (0 to 2.5)
+ return RULE_OF_THREE(10, 2.5, x) + 50
+ if (2.5 to 5)
+ return RULE_OF_THREE(20, 5, x) + 50
+ if (5 to 10)
+ return RULE_OF_THREE(30, 10, x) + 50
+ if (10 to 20)
+ return RULE_OF_THREE(40, 20, x) + 50
+ if (20 to INFINITY)
+ return rand(90, 100)
diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets.dm b/code/game/gamemodes/dynamic/dynamic_rulesets.dm
new file mode 100644
index 0000000000..66afcbfb92
--- /dev/null
+++ b/code/game/gamemodes/dynamic/dynamic_rulesets.dm
@@ -0,0 +1,211 @@
+/datum/dynamic_ruleset
+ /// For admin logging and round end screen.
+ var/name = ""
+ /// For admin logging and round end screen, do not change this unless making a new rule type.
+ var/ruletype = ""
+ /// If set to TRUE, the rule won't be discarded after being executed, and dynamic will call rule_process() every time it ticks.
+ var/persistent = FALSE
+ /// If set to TRUE, dynamic mode will be able to draft this ruleset again later on. (doesn't apply for roundstart rules)
+ var/repeatable = FALSE
+ /// If set higher than 0 decreases weight by itself causing the ruleset to appear less often the more it is repeated.
+ var/repeatable_weight_decrease = 2
+ /// List of players that are being drafted for this rule
+ var/list/mob/candidates = list()
+ /// List of players that were selected for this rule
+ var/list/datum/mind/assigned = list()
+ /// Preferences flag such as ROLE_WIZARD that need to be turned on for players to be antag
+ var/antag_flag = null
+ /// The antagonist datum that is assigned to the mobs mind on ruleset execution.
+ var/datum/antagonist/antag_datum = null
+ /// The required minimum account age for this ruleset.
+ var/minimum_required_age = 7
+ /// If set, and config flag protect_roles_from_antagonist is false, then the rule will not pick players from these roles.
+ var/list/protected_roles = list()
+ /// If set, rule will deny candidates from those roles always.
+ var/list/restricted_roles = list()
+ /// If set, rule will only accept candidates from those roles, IMPORTANT: DOES NOT WORK ON ROUNDSTART RULESETS.
+ var/list/exclusive_roles = list()
+ /// If set, there needs to be a certain amount of players doing those roles (among the players who won't be drafted) for the rule to be drafted IMPORTANT: DOES NOT WORK ON ROUNDSTART RULESETS.
+ var/list/enemy_roles = list()
+ /// If enemy_roles was set, this is the amount of enemy job workers needed per threat_level range (0-10,10-20,etc) IMPORTANT: DOES NOT WORK ON ROUNDSTART RULESETS.
+ var/required_enemies = list(1,1,0,0,0,0,0,0,0,0)
+ /// The rule needs this many candidates (post-trimming) to be executed (example: Cult needs 4 players at round start)
+ var/required_candidates = 0
+ /// 1 -> 9, probability for this rule to be picked against other rules
+ var/weight = 5
+ /// Threat cost for this rule, this is decreased from the mode's threat when the rule is executed.
+ var/cost = 0
+ /// A flag that determines how the ruleset is handled
+ /// HIGHLANDER_RULESET are rulesets can end the round.
+ /// TRAITOR_RULESET and MINOR_RULESET can't end the round and have no difference right now.
+ var/flags = 0
+ /// Pop range per requirement. If zero defaults to mode's pop_per_requirement.
+ var/pop_per_requirement = 0
+ /// Requirements are the threat level requirements per pop range.
+ /// With the default values, The rule will never get drafted below 10 threat level (aka: "peaceful extended"), and it requires a higher threat level at lower pops.
+ var/list/requirements = list(40,30,20,10,10,10,10,10,10,10)
+ /// An alternative, static requirement used instead when pop is over mode's high_pop_limit.
+ var/high_population_requirement = 10
+ /// Reference to the mode, use this instead of SSticker.mode.
+ var/datum/game_mode/dynamic/mode = null
+ /// If a role is to be considered another for the purpose of banning.
+ var/antag_flag_override = null
+ /// If a ruleset type which is in this list has been executed, then the ruleset will not be executed.
+ var/list/blocking_rules = list()
+ /// The minimum amount of players required for the rule to be considered.
+ var/minimum_players = 0
+ /// The maximum amount of players required for the rule to be considered.
+ /// Anything below zero or exactly zero is ignored.
+ var/maximum_players = 0
+
+
+/datum/dynamic_ruleset/New()
+ ..()
+ if(CONFIG_GET(flag/protect_roles_from_antagonist))
+ restricted_roles += protected_roles
+ if(CONFIG_GET(flag/protect_assistant_from_antagonist))
+ restricted_roles += "Assistant"
+
+ if (istype(SSticker.mode, /datum/game_mode/dynamic))
+ mode = SSticker.mode
+ else if (GLOB.master_mode != "dynamic") // This is here to make roundstart forced ruleset function.
+ qdel(src)
+
+/datum/dynamic_ruleset/roundstart // One or more of those drafted at roundstart
+ ruletype = "Roundstart"
+
+/datum/dynamic_ruleset/roundstart/delayed/ // Executed with a 30 seconds delay
+ var/delay = 30 SECONDS
+ var/required_type = /mob/living/carbon/human // No ghosts, new players or silicons allowed.
+
+// Can be drafted when a player joins the server
+/datum/dynamic_ruleset/latejoin
+ ruletype = "Latejoin"
+
+/// By default, a rule is acceptable if it satisfies the threat level/population requirements.
+/// 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)
+ return FALSE
+ if(maximum_players > 0 && population > maximum_players)
+ return FALSE
+ if (population >= GLOB.dynamic_high_pop_limit)
+ return (threat_level >= high_population_requirement)
+ else
+ pop_per_requirement = pop_per_requirement > 0 ? pop_per_requirement : mode.pop_per_requirement
+ var/indice_pop = min(10,round(population/pop_per_requirement)+1)
+ return (threat_level >= requirements[indice_pop])
+
+/// This is called if persistent variable is true everytime SSTicker ticks.
+/datum/dynamic_ruleset/proc/rule_process()
+ return
+
+/// Called on game mode pre_setup, used for non-delayed roundstart rulesets only.
+/// Do everything you need to do before job is assigned here.
+/// IMPORTANT: ASSIGN special_role HERE
+/datum/dynamic_ruleset/proc/pre_execute()
+ return TRUE
+
+/// Called on post_setup on roundstart and when the rule executes on midround and latejoin.
+/// Give your candidates or assignees equipment and antag datum here.
+/datum/dynamic_ruleset/proc/execute()
+ for(var/datum/mind/M in assigned)
+ M.add_antag_datum(antag_datum)
+ return TRUE
+
+/// Called after delay set in ruleset.
+/// Give your candidates or assignees equipment and antag datum here.
+/datum/dynamic_ruleset/roundstart/delayed/execute()
+ if (SSticker && SSticker.current_state < GAME_STATE_PLAYING)
+ CRASH("The delayed ruleset [name] executed before the round started.")
+
+/// Here you can perform any additional checks you want. (such as checking the map etc)
+/// Remember that on roundstart no one knows what their job is at this point.
+/// 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)
+ return FALSE
+ return TRUE
+
+/// Gets weight of the ruleset
+/// Note that this decreases weight if repeatable is TRUE and repeatable_weight_decrease is higher than 0
+/// Note: If you don't want repeatable rulesets to decrease their weight use the weight variable directly
+/datum/dynamic_ruleset/proc/get_weight()
+ if(repeatable && weight > 1 && repeatable_weight_decrease > 0)
+ for(var/datum/dynamic_ruleset/DR in mode.executed_rules)
+ if(istype(DR, type))
+ weight = max(weight-repeatable_weight_decrease,1)
+ return weight
+
+/// Here you can remove candidates that do not meet your requirements.
+/// This means if their job is not correct or they have disconnected you can remove them from candidates here.
+/// Usually this does not need to be changed unless you need some specific requirements from your candidates.
+/datum/dynamic_ruleset/proc/trim_candidates()
+ return
+
+/// Counts how many players are ready at roundstart.
+/// Used only by non-delayed roundstart rulesets.
+/datum/dynamic_ruleset/proc/num_players()
+ . = 0
+ for(var/mob/dead/new_player/P in GLOB.player_list)
+ if(P.client && P.ready == PLAYER_READY_TO_PLAY)
+ . ++
+
+/// Set mode result and news report here.
+/// Only called if ruleset is flagged as HIGHLANDER_RULESET
+/datum/dynamic_ruleset/proc/round_result()
+
+/// Checks if round is finished, return true to end the round.
+/// Only called if ruleset is flagged as HIGHLANDER_RULESET
+/datum/dynamic_ruleset/proc/check_finished()
+ return FALSE
+
+//////////////////////////////////////////////
+// //
+// ROUNDSTART RULESETS //
+// //
+//////////////////////////////////////////////
+
+/// Checks if candidates are connected and if they are banned or don't want to be the antagonist.
+/datum/dynamic_ruleset/roundstart/trim_candidates()
+ for(var/mob/dead/new_player/P in candidates)
+ if (!P.client || !P.mind) // Are they connected?
+ candidates.Remove(P)
+ continue
+ if(!mode.check_age(P.client, minimum_required_age))
+ candidates.Remove(P)
+ continue
+ if(P.mind.special_role) // We really don't want to give antag to an antag.
+ candidates.Remove(P)
+ continue
+ if (!(antag_flag in P.client.prefs.be_special) || jobban_isbanned(P.ckey, list(antag_flag, ROLE_SYNDICATE)) || (antag_flag_override && jobban_isbanned(P.ckey, list(antag_flag_override, ROLE_SYNDICATE))))//are they willing and not antag-banned?
+ candidates.Remove(P)
+ continue
+
+/// Checks if candidates are required mob type, connected, banned and if the job is exclusive to the role.
+/datum/dynamic_ruleset/roundstart/delayed/trim_candidates()
+ . = ..()
+ for (var/mob/P in candidates)
+ if (!istype(P, required_type))
+ candidates.Remove(P) // Can be a new_player, etc.
+ continue
+ if(!mode.check_age(P.client, minimum_required_age))
+ candidates.Remove(P)
+ continue
+ if (!P.client || !P.mind || !P.mind.assigned_role) // Are they connected?
+ candidates.Remove(P)
+ continue
+ if(P.mind.special_role || P.mind.antag_datums?.len > 0) // Are they an antag already?
+ candidates.Remove(P)
+ continue
+ if (!(antag_flag in P.client.prefs.be_special) || jobban_isbanned(P.ckey, list(antag_flag, ROLE_SYNDICATE)) || (antag_flag_override && jobban_isbanned(P.ckey, list(antag_flag_override, ROLE_SYNDICATE))))//are they willing and not antag-banned?
+ candidates.Remove(P)
+ continue
+ if ((exclusive_roles.len > 0) && !(P.mind.assigned_role in exclusive_roles)) // Is the rule exclusive to their job?
+ candidates.Remove(P)
+ continue
+
+/// Do your checks if the ruleset is ready to be executed here.
+/// Should ignore certain checks if forced is TRUE
+/datum/dynamic_ruleset/roundstart/ready(forced = FALSE)
+ return ..()
diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm
new file mode 100644
index 0000000000..24b4c67357
--- /dev/null
+++ b/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm
@@ -0,0 +1,110 @@
+//////////////////////////////////////////////
+// //
+// LATEJOIN RULESETS //
+// //
+//////////////////////////////////////////////
+
+/datum/dynamic_ruleset/latejoin/trim_candidates()
+ for(var/mob/P in candidates)
+ if (!P.client || !P.mind || !P.mind.assigned_role) // Are they connected?
+ candidates.Remove(P)
+ continue
+ if(!mode.check_age(P.client, minimum_required_age))
+ candidates.Remove(P)
+ continue
+ if (!(antag_flag in P.client.prefs.be_special) || jobban_isbanned(P.ckey, list(antag_flag, ROLE_SYNDICATE)) || (antag_flag_override && jobban_isbanned(P.ckey, list(antag_flag_override))))//are they willing and not antag-banned?
+ candidates.Remove(P)
+ continue
+ if (P.mind.assigned_role in restricted_roles) // Does their job allow for it?
+ candidates.Remove(P)
+ continue
+ if ((exclusive_roles.len > 0) && !(P.mind.assigned_role in exclusive_roles)) // Is the rule exclusive to their job?
+ candidates.Remove(P)
+ continue
+
+/datum/dynamic_ruleset/latejoin/ready(forced = 0)
+ if (!forced)
+ var/job_check = 0
+ if (enemy_roles.len > 0)
+ for (var/mob/M in mode.current_players[CURRENT_LIVING_PLAYERS])
+ if (M.stat == DEAD)
+ 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])
+ return FALSE
+ return ..()
+
+/datum/dynamic_ruleset/latejoin/execute()
+ var/mob/M = pick(candidates)
+ assigned += M.mind
+ M.mind.special_role = antag_flag
+ M.mind.add_antag_datum(antag_datum)
+ return TRUE
+
+//////////////////////////////////////////////
+// //
+// SYNDICATE TRAITORS //
+// //
+//////////////////////////////////////////////
+
+/datum/dynamic_ruleset/latejoin/infiltrator
+ name = "Syndicate Infiltrator"
+ antag_datum = /datum/antagonist/traitor
+ antag_flag = ROLE_TRAITOR
+ restricted_roles = list("AI", "Cyborg")
+ protected_roles = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel", "Chief Engineer", "Chief Medical Officer", "Research Director", "Quartermaster")
+ required_candidates = 1
+ weight = 7
+ cost = 5
+ requirements = list(40,30,20,10,10,10,10,10,10,10)
+ high_population_requirement = 10
+ repeatable = TRUE
+ flags = TRAITOR_RULESET
+
+//////////////////////////////////////////////
+// //
+// REVOLUTIONARY PROVOCATEUR //
+// //
+//////////////////////////////////////////////
+
+/datum/dynamic_ruleset/latejoin/provocateur
+ name = "Provocateur"
+ antag_datum = /datum/antagonist/rev/head
+ antag_flag = ROLE_REV_HEAD
+ antag_flag_override = ROLE_REV
+ restricted_roles = list("AI", "Cyborg", "Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel", "Chief Engineer", "Chief Medical Officer", "Research Director", "Quartermaster")
+ enemy_roles = list("AI", "Cyborg", "Security Officer","Detective","Head of Security", "Captain", "Warden")
+ required_enemies = list(2,2,1,1,1,1,1,0,0,0)
+ required_candidates = 1
+ weight = 2
+ cost = 20
+ requirements = list(101,101,70,40,30,20,20,20,20,20)
+ high_population_requirement = 50
+ flags = HIGHLANDER_RULESET
+ var/required_heads = 3
+
+/datum/dynamic_ruleset/latejoin/provocateur/ready(forced=FALSE)
+ if (forced)
+ required_heads = 1
+ if(!..())
+ return FALSE
+ var/head_check = 0
+ for(var/mob/player in mode.current_players[CURRENT_LIVING_PLAYERS])
+ if (player.mind.assigned_role in GLOB.command_positions)
+ head_check++
+ return (head_check >= required_heads)
+
+/datum/dynamic_ruleset/latejoin/provocateur/execute()
+ var/mob/M = pick(candidates)
+ assigned += M.mind
+ M.mind.special_role = antag_flag
+ var/datum/antagonist/rev/head/new_head = new()
+ new_head.give_flash = TRUE
+ new_head.give_hud = TRUE
+ new_head.remove_clumsy = TRUE
+ new_head = M.mind.add_antag_datum(new_head)
+ new_head.rev_team.max_headrevs = 1 // Only one revhead if it is latejoin.
+ return TRUE
diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm
new file mode 100644
index 0000000000..4da085d5a9
--- /dev/null
+++ b/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm
@@ -0,0 +1,460 @@
+//////////////////////////////////////////////
+// //
+// MIDROUND RULESETS //
+// //
+//////////////////////////////////////////////
+
+/datum/dynamic_ruleset/midround // Can be drafted once in a while during a round
+ ruletype = "Midround"
+ /// If the ruleset should be restricted from ghost roles.
+ var/restrict_ghost_roles = TRUE
+ /// What type the ruleset is restricted to.
+ var/required_type = /mob/living/carbon/human
+ var/list/living_players = list()
+ var/list/living_antags = list()
+ var/list/dead_players = list()
+ var/list/list_observers = list()
+
+/datum/dynamic_ruleset/midround/from_ghosts
+ weight = 0
+ /// Whether the ruleset should call generate_ruleset_body or not.
+ var/makeBody = TRUE
+
+/datum/dynamic_ruleset/midround/trim_candidates()
+ // Unlike the previous two types, these rulesets are not meant for /mob/dead/new_player
+ // And since I want those rulesets to be as flexible as possible, I'm not gonna put much here,
+ //
+ // All you need to know is that here, the candidates list contains 4 lists itself, indexed with the following defines:
+ // Candidates = list(CURRENT_LIVING_PLAYERS, CURRENT_LIVING_ANTAGS, CURRENT_DEAD_PLAYERS, CURRENT_OBSERVERS)
+ // So for example you can get the list of all current dead players with var/list/dead_players = candidates[CURRENT_DEAD_PLAYERS]
+ // Make sure to properly typecheck the mobs in those lists, as the dead_players list could contain ghosts, or dead players still in their bodies.
+ // We're still gonna trim the obvious (mobs without clients, jobbanned players, etc)
+ living_players = trim_list(mode.current_players[CURRENT_LIVING_PLAYERS])
+ living_antags = trim_list(mode.current_players[CURRENT_LIVING_ANTAGS])
+ dead_players = trim_list(mode.current_players[CURRENT_DEAD_PLAYERS])
+ list_observers = trim_list(mode.current_players[CURRENT_OBSERVERS])
+
+/datum/dynamic_ruleset/midround/proc/trim_list(list/L = list())
+ var/list/trimmed_list = L.Copy()
+ var/antag_name = initial(antag_flag)
+ for(var/mob/M in trimmed_list)
+ if (!istype(M, required_type))
+ trimmed_list.Remove(M)
+ continue
+ if (!M.client) // Are they connected?
+ trimmed_list.Remove(M)
+ continue
+ if(!mode.check_age(M.client, minimum_required_age))
+ trimmed_list.Remove(M)
+ continue
+ if (!(antag_name in M.client.prefs.be_special) || jobban_isbanned(M.ckey, list(antag_name, ROLE_SYNDICATE)))//are they willing and not antag-banned?
+ trimmed_list.Remove(M)
+ continue
+ if (M.mind)
+ if (restrict_ghost_roles && M.mind.assigned_role in GLOB.exp_specialmap[EXP_TYPE_SPECIAL]) // Are they playing a ghost role?
+ trimmed_list.Remove(M)
+ continue
+ if (M.mind.assigned_role in restricted_roles || HAS_TRAIT(M, TRAIT_MINDSHIELD)) // Does their job allow it or are they mindshielded?
+ trimmed_list.Remove(M)
+ continue
+ if ((exclusive_roles.len > 0) && !(M.mind.assigned_role in exclusive_roles)) // Is the rule exclusive to their job?
+ trimmed_list.Remove(M)
+ continue
+ return trimmed_list
+
+// You can then for example prompt dead players in execute() to join as strike teams or whatever
+// Or autotator someone
+
+// IMPORTANT, since /datum/dynamic_ruleset/midround may accept candidates from both living, dead, and even antag players, you need to manually check whether there are enough candidates
+// (see /datum/dynamic_ruleset/midround/autotraitor/ready(var/forced = FALSE) for example)
+/datum/dynamic_ruleset/midround/ready(forced = FALSE)
+ if (!forced)
+ var/job_check = 0
+ if (enemy_roles.len > 0)
+ for (var/mob/M in living_players)
+ if (M.stat == DEAD)
+ 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])
+ return FALSE
+ return TRUE
+
+/datum/dynamic_ruleset/midround/from_ghosts/execute()
+ var/list/possible_candidates = list()
+ possible_candidates.Add(dead_players)
+ possible_candidates.Add(list_observers)
+ send_applications(possible_candidates)
+ if(assigned.len > 0)
+ return TRUE
+ else
+ return FALSE
+
+/// This sends a poll to ghosts if they want to be a ghost spawn from a ruleset.
+/datum/dynamic_ruleset/midround/from_ghosts/proc/send_applications(list/possible_volunteers = list())
+ if (possible_volunteers.len <= 0) // This shouldn't happen, as ready() should return FALSE if there is not a single valid candidate
+ message_admins("Possible volunteers was 0. This shouldn't appear, because of ready(), unless you forced it!")
+ return
+ message_admins("Polling [possible_volunteers.len] players to apply for the [name] ruleset.")
+ log_game("DYNAMIC: Polling [possible_volunteers.len] players to apply for the [name] ruleset.")
+
+ candidates = pollGhostCandidates("The mode is looking for volunteers to become [antag_flag] for [name]", antag_flag, SSticker.mode, antag_flag, poll_time = 300)
+
+ if(!candidates || candidates.len <= 0)
+ message_admins("The ruleset [name] received no applications.")
+ log_game("DYNAMIC: The ruleset [name] received no applications.")
+ mode.refund_threat(cost)
+ mode.threat_log += "[worldtime2text()]: Rule [name] refunded [cost] (no applications)"
+ mode.executed_rules -= src
+ return
+
+ message_admins("[candidates.len] players volunteered for the ruleset [name].")
+ log_game("DYNAMIC: [candidates.len] players volunteered for [name].")
+ review_applications()
+
+/// Here is where you can check if your ghost applicants are valid for the ruleset.
+/// Called by send_applications().
+/datum/dynamic_ruleset/midround/from_ghosts/proc/review_applications()
+ for (var/i = 1, i <= required_candidates, i++)
+ if(candidates.len <= 0)
+ if(i == 1)
+ // We have found no candidates so far and we are out of applicants.
+ mode.refund_threat(cost)
+ mode.threat_log += "[worldtime2text()]: Rule [name] refunded [cost] (all applications invalid)"
+ mode.executed_rules -= src
+ break
+ var/mob/applicant = pick(candidates)
+ candidates -= applicant
+ if(!isobserver(applicant))
+ if(applicant.stat == DEAD) // Not an observer? If they're dead, make them one.
+ applicant = applicant.ghostize(FALSE)
+ else // Not dead? Disregard them, pick a new applicant
+ i--
+ continue
+
+ if(!applicant)
+ i--
+ continue
+
+ var/mob/new_character = applicant
+
+ if (makeBody)
+ new_character = generate_ruleset_body(applicant)
+
+ finish_setup(new_character, i)
+ assigned += applicant
+ notify_ghosts("[new_character] has been picked for the ruleset [name]!", source = new_character, action = NOTIFY_ORBIT, header="Something Interesting!")
+
+/datum/dynamic_ruleset/midround/from_ghosts/proc/generate_ruleset_body(mob/applicant)
+ var/mob/living/carbon/human/new_character = makeBody(applicant)
+ new_character.dna.remove_all_mutations()
+ return new_character
+
+/datum/dynamic_ruleset/midround/from_ghosts/proc/finish_setup(mob/new_character, index)
+ var/datum/antagonist/new_role = new antag_datum()
+ setup_role(new_role)
+ new_character.mind.add_antag_datum(new_role)
+ new_character.mind.special_role = antag_flag
+
+/datum/dynamic_ruleset/midround/from_ghosts/proc/setup_role(datum/antagonist/new_role)
+ return
+
+//////////////////////////////////////////////
+// //
+// SYNDICATE TRAITORS //
+// //
+//////////////////////////////////////////////
+
+/datum/dynamic_ruleset/midround/autotraitor
+ name = "Syndicate Sleeper Agent"
+ antag_datum = /datum/antagonist/traitor
+ antag_flag = ROLE_TRAITOR
+ restricted_roles = list("AI", "Cyborg", "Positronic Brain")
+ protected_roles = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel", "Chief Engineer", "Chief Medical Officer", "Research Director", "Quartermaster")
+ required_candidates = 1
+ weight = 7
+ cost = 10
+ requirements = list(50,40,30,20,10,10,10,10,10,10)
+ repeatable = TRUE
+ high_population_requirement = 10
+ flags = TRAITOR_RULESET
+
+/datum/dynamic_ruleset/midround/autotraitor/acceptable(population = 0, threat = 0)
+ var/player_count = mode.current_players[CURRENT_LIVING_PLAYERS].len
+ var/antag_count = mode.current_players[CURRENT_LIVING_ANTAGS].len
+ var/max_traitors = round(player_count / 10) + 1
+ if ((antag_count < max_traitors) && prob(mode.threat_level))//adding traitors if the antag population is getting low
+ return ..()
+ else
+ return FALSE
+
+/datum/dynamic_ruleset/midround/autotraitor/trim_candidates()
+ ..()
+ for(var/mob/living/player in living_players)
+ if(issilicon(player)) // Your assigned role doesn't change when you are turned into a silicon.
+ living_players -= player
+ continue
+ if(is_centcom_level(player.z))
+ living_players -= player // We don't autotator people in CentCom
+ continue
+ if(player.mind && (player.mind.special_role || player.mind.antag_datums?.len > 0))
+ living_players -= player // We don't autotator people with roles already
+
+/datum/dynamic_ruleset/midround/autotraitor/ready(forced = FALSE)
+ if (required_candidates > living_players.len)
+ return FALSE
+ return ..()
+
+/datum/dynamic_ruleset/midround/autotraitor/execute()
+ var/mob/M = pick(living_players)
+ assigned += M
+ living_players -= M
+ var/datum/antagonist/traitor/newTraitor = new
+ M.mind.add_antag_datum(newTraitor)
+ return TRUE
+
+
+//////////////////////////////////////////////
+// //
+// Malfunctioning AI //
+// //
+//////////////////////////////////////////////
+
+/datum/dynamic_ruleset/midround/malf
+ name = "Malfunctioning AI"
+ antag_datum = /datum/antagonist/traitor
+ antag_flag = ROLE_MALF
+ enemy_roles = list("Security Officer", "Warden","Detective","Head of Security", "Captain", "Scientist", "Chemist", "Research Director", "Chief Engineer")
+ exclusive_roles = list("AI")
+ required_enemies = list(4,4,4,4,4,4,2,2,2,0)
+ required_candidates = 1
+ weight = 3
+ cost = 35
+ requirements = list(101,101,80,70,60,60,50,50,40,40)
+ high_population_requirement = 35
+ required_type = /mob/living/silicon/ai
+ var/ion_announce = 33
+ var/removeDontImproveChance = 10
+
+/datum/dynamic_ruleset/midround/malf/trim_candidates()
+ ..()
+ candidates = candidates[CURRENT_LIVING_PLAYERS]
+ for(var/mob/living/player in candidates)
+ if(!isAI(player))
+ candidates -= player
+ continue
+ if(is_centcom_level(player.z))
+ candidates -= player
+ continue
+ if(player.mind && (player.mind.special_role || player.mind.antag_datums?.len > 0))
+ candidates -= player
+
+/datum/dynamic_ruleset/midround/malf/execute()
+ if(!candidates || !candidates.len)
+ return FALSE
+ var/mob/living/silicon/ai/M = pick(candidates)
+ candidates -= M
+ assigned += M.mind
+ var/datum/antagonist/traitor/AI = new
+ M.mind.special_role = antag_flag
+ M.mind.add_antag_datum(AI)
+ if(prob(ion_announce))
+ priority_announce("Ion storm detected near the station. Please check all AI-controlled equipment for errors.", "Anomaly Alert", 'sound/ai/ionstorm.ogg')
+ if(prob(removeDontImproveChance))
+ M.replace_random_law(generate_ion_law(), list(LAW_INHERENT, LAW_SUPPLIED, LAW_ION))
+ else
+ M.add_ion_law(generate_ion_law())
+ return TRUE
+
+//////////////////////////////////////////////
+// //
+// WIZARD (GHOST) //
+// //
+//////////////////////////////////////////////
+
+/datum/dynamic_ruleset/midround/from_ghosts/wizard
+ name = "Wizard"
+ antag_datum = /datum/antagonist/wizard
+ antag_flag = ROLE_WIZARD
+ enemy_roles = list("Security Officer","Detective","Head of Security", "Captain")
+ required_enemies = list(2,2,1,1,1,1,1,0,0,0)
+ required_candidates = 1
+ weight = 1
+ cost = 20
+ requirements = list(90,90,70,40,30,20,10,10,10,10)
+ high_population_requirement = 50
+ repeatable = TRUE
+
+/datum/dynamic_ruleset/midround/from_ghosts/wizard/ready(forced = FALSE)
+ if (required_candidates > (dead_players.len + list_observers.len))
+ return FALSE
+ if(GLOB.wizardstart.len == 0)
+ log_admin("Cannot accept Wizard ruleset. Couldn't find any wizard spawn points.")
+ message_admins("Cannot accept Wizard ruleset. Couldn't find any wizard spawn points.")
+ return FALSE
+ return ..()
+
+/datum/dynamic_ruleset/midround/from_ghosts/wizard/finish_setup(mob/new_character, index)
+ ..()
+ new_character.forceMove(pick(GLOB.wizardstart))
+
+//////////////////////////////////////////////
+// //
+// NUCLEAR OPERATIVES (MIDROUND) //
+// //
+//////////////////////////////////////////////
+
+/datum/dynamic_ruleset/midround/from_ghosts/nuclear
+ name = "Nuclear Assault"
+ antag_flag = ROLE_OPERATIVE
+ antag_datum = /datum/antagonist/nukeop
+ enemy_roles = list("AI", "Cyborg", "Security Officer", "Warden","Detective","Head of Security", "Captain")
+ required_enemies = list(3,3,3,3,3,2,1,1,0,0)
+ required_candidates = 5
+ weight = 5
+ cost = 35
+ requirements = list(90,90,90,80,60,40,30,20,10,10)
+ high_population_requirement = 10
+ var/operative_cap = list(2,2,3,3,4,5,5,5,5,5)
+ var/datum/team/nuclear/nuke_team
+ flags = HIGHLANDER_RULESET
+
+/datum/dynamic_ruleset/midround/from_ghosts/nuclear/acceptable(population=0, threat=0)
+ if (locate(/datum/dynamic_ruleset/roundstart/nuclear) in mode.executed_rules)
+ return FALSE // Unavailable if nuke ops were already sent at roundstart
+ var/indice_pop = min(10,round(living_players.len/5)+1)
+ required_candidates = operative_cap[indice_pop]
+ return ..()
+
+/datum/dynamic_ruleset/midround/from_ghosts/nuclear/ready(forced = FALSE)
+ if (required_candidates > (dead_players.len + list_observers.len))
+ return FALSE
+ return ..()
+
+/datum/dynamic_ruleset/midround/from_ghosts/nuclear/finish_setup(mob/new_character, index)
+ new_character.mind.special_role = "Nuclear Operative"
+ new_character.mind.assigned_role = "Nuclear Operative"
+ if (index == 1) // Our first guy is the leader
+ var/datum/antagonist/nukeop/leader/new_role = new
+ nuke_team = new_role.nuke_team
+ new_character.mind.add_antag_datum(new_role)
+ else
+ return ..()
+
+//////////////////////////////////////////////
+// //
+// BLOB (GHOST) //
+// //
+//////////////////////////////////////////////
+
+/datum/dynamic_ruleset/midround/from_ghosts/blob
+ name = "Blob"
+ antag_datum = /datum/antagonist/blob
+ antag_flag = ROLE_BLOB
+ enemy_roles = list("Security Officer", "Detective", "Head of Security", "Captain")
+ required_enemies = list(2,2,1,1,1,1,1,0,0,0)
+ required_candidates = 1
+ weight = 4
+ cost = 10
+ requirements = list(101,101,101,80,60,50,30,20,10,10)
+ high_population_requirement = 50
+ repeatable = TRUE
+
+/datum/dynamic_ruleset/midround/from_ghosts/blob/generate_ruleset_body(mob/applicant)
+ var/body = applicant.become_overmind()
+ return body
+
+//////////////////////////////////////////////
+// //
+// XENOMORPH (GHOST) //
+// //
+//////////////////////////////////////////////
+
+/datum/dynamic_ruleset/midround/from_ghosts/xenomorph
+ name = "Alien Infestation"
+ antag_datum = /datum/antagonist/xeno
+ antag_flag = ROLE_ALIEN
+ enemy_roles = list("Security Officer", "Detective", "Head of Security", "Captain")
+ required_enemies = list(2,2,1,1,1,1,1,0,0,0)
+ required_candidates = 1
+ weight = 3
+ cost = 10
+ requirements = list(101,101,101,70,50,40,20,15,10,10)
+ high_population_requirement = 50
+ repeatable = TRUE
+ var/list/vents = list()
+
+/datum/dynamic_ruleset/midround/from_ghosts/xenomorph/execute()
+ // 50% chance of being incremented by one
+ required_candidates += prob(50)
+ for(var/obj/machinery/atmospherics/components/unary/vent_pump/temp_vent in GLOB.machines)
+ if(QDELETED(temp_vent))
+ continue
+ if(is_station_level(temp_vent.loc.z) && !temp_vent.welded)
+ var/datum/pipeline/temp_vent_parent = temp_vent.parents[1]
+ if(!temp_vent_parent)
+ continue // No parent vent
+ // Stops Aliens getting stuck in small networks.
+ // See: Security, Virology
+ if(temp_vent_parent.other_atmosmch.len > 20)
+ vents += temp_vent
+ if(!vents.len)
+ return FALSE
+ . = ..()
+
+/datum/dynamic_ruleset/midround/from_ghosts/xenomorph/generate_ruleset_body(mob/applicant)
+ var/obj/vent = pick_n_take(vents)
+ var/mob/living/carbon/alien/larva/new_xeno = new(vent.loc)
+ applicant.transfer_ckey(new_xeno, FALSE)
+ message_admins("[ADMIN_LOOKUPFLW(new_xeno)] has been made into an alien by the midround ruleset.")
+ log_game("DYNAMIC: [key_name(new_xeno)] was spawned as an alien by the midround ruleset.")
+ return new_xeno
+
+//////////////////////////////////////////////
+// //
+// NIGHTMARE (GHOST) //
+// //
+//////////////////////////////////////////////
+
+/datum/dynamic_ruleset/midround/from_ghosts/nightmare
+ name = "Nightmare"
+ antag_datum = /datum/antagonist/nightmare
+ antag_flag = "Nightmare"
+ antag_flag_override = ROLE_ALIEN
+ enemy_roles = list("Security Officer", "Detective", "Head of Security", "Captain")
+ required_enemies = list(2,2,1,1,1,1,1,0,0,0)
+ required_candidates = 1
+ weight = 3
+ cost = 10
+ requirements = list(101,101,101,70,50,40,20,15,10,10)
+ high_population_requirement = 50
+ repeatable = TRUE
+ var/list/spawn_locs = list()
+
+/datum/dynamic_ruleset/midround/from_ghosts/nightmare/execute()
+ for(var/X in GLOB.xeno_spawn)
+ var/turf/T = X
+ var/light_amount = T.get_lumcount()
+ if(light_amount < SHADOW_SPECIES_LIGHT_THRESHOLD)
+ spawn_locs += T
+ if(!spawn_locs.len)
+ return FALSE
+ . = ..()
+
+/datum/dynamic_ruleset/midround/from_ghosts/nightmare/generate_ruleset_body(mob/applicant)
+ var/datum/mind/player_mind = new /datum/mind(applicant.key)
+ player_mind.active = TRUE
+
+ var/mob/living/carbon/human/S = new (pick(spawn_locs))
+ player_mind.transfer_to(S)
+ player_mind.assigned_role = "Nightmare"
+ player_mind.special_role = "Nightmare"
+ player_mind.add_antag_datum(/datum/antagonist/nightmare)
+ S.set_species(/datum/species/shadow/nightmare)
+
+ playsound(S, 'sound/magic/ethereal_exit.ogg', 50, 1, -1)
+ message_admins("[ADMIN_LOOKUPFLW(S)] has been made into a Nightmare by the midround ruleset.")
+ log_game("DYNAMIC: [key_name(S)] was spawned as a Nightmare by the midround ruleset.")
+ return S
diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm
new file mode 100644
index 0000000000..38ce6f68d0
--- /dev/null
+++ b/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm
@@ -0,0 +1,732 @@
+
+//////////////////////////////////////////////
+// //
+// SYNDICATE TRAITORS //
+// //
+//////////////////////////////////////////////
+
+/datum/dynamic_ruleset/roundstart/traitor
+ name = "Traitors"
+ persistent = TRUE
+ antag_flag = ROLE_TRAITOR
+ antag_datum = /datum/antagonist/traitor/
+ minimum_required_age = 0
+ protected_roles = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel", "Chief Engineer", "Chief Medical Officer", "Research Director", "Quartermaster", "Cyborg")
+ restricted_roles = list("Cyborg")
+ required_candidates = 1
+ weight = 5
+ cost = 10
+ requirements = list(10,10,10,10,10,10,10,10,10,10)
+ high_population_requirement = 10
+ var/autotraitor_cooldown = 450 // 15 minutes (ticks once per 2 sec)
+
+/datum/dynamic_ruleset/roundstart/traitor/pre_execute()
+ var/traitor_scaling_coeff = 10 - max(0,round(mode.threat_level/10)-5) // Above 50 threat level, coeff goes down by 1 for every 10 levels
+ var/num_traitors = min(round(mode.candidates.len / traitor_scaling_coeff) + 1, candidates.len)
+ for (var/i = 1 to num_traitors)
+ var/mob/M = pick(candidates)
+ candidates -= M
+ assigned += M.mind
+ M.mind.special_role = ROLE_TRAITOR
+ M.mind.restricted_roles = restricted_roles
+ return TRUE
+
+/datum/dynamic_ruleset/roundstart/traitor/rule_process()
+ if (autotraitor_cooldown > 0)
+ autotraitor_cooldown--
+ else
+ autotraitor_cooldown = 450 // 15 minutes
+ message_admins("Checking if we can turn someone into a traitor.")
+ log_game("DYNAMIC: Checking if we can turn someone into a traitor.")
+ mode.picking_specific_rule(/datum/dynamic_ruleset/midround/autotraitor)
+
+//////////////////////////////////////////
+// //
+// BLOOD BROTHERS //
+// //
+//////////////////////////////////////////
+
+/datum/dynamic_ruleset/roundstart/traitorbro
+ name = "Blood Brothers"
+ antag_flag = ROLE_BROTHER
+ antag_datum = /datum/antagonist/brother/
+ restricted_roles = list("AI", "Cyborg")
+ protected_roles = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel", "Chief Engineer", "Chief Medical Officer", "Research Director", "Quartermaster")
+ required_candidates = 2
+ weight = 4
+ cost = 10
+ requirements = list(40,30,30,20,20,15,15,15,10,10)
+ high_population_requirement = 15
+ var/list/datum/team/brother_team/pre_brother_teams = list()
+ var/const/team_amount = 2 // Hard limit on brother teams if scaling is turned off
+ var/const/min_team_size = 2
+
+/datum/dynamic_ruleset/roundstart/traitorbro/pre_execute()
+ var/num_teams = team_amount
+ var/bsc = CONFIG_GET(number/brother_scaling_coeff)
+ if(bsc)
+ num_teams = max(1, round(num_players() / bsc))
+
+ for(var/j = 1 to num_teams)
+ if(candidates.len < min_team_size || candidates.len < required_candidates)
+ break
+ var/datum/team/brother_team/team = new
+ var/team_size = prob(10) ? min(3, candidates.len) : 2
+ for(var/k = 1 to team_size)
+ var/mob/bro = pick(candidates)
+ candidates -= bro
+ assigned += bro.mind
+ team.add_member(bro.mind)
+ bro.mind.special_role = "brother"
+ bro.mind.restricted_roles = restricted_roles
+ pre_brother_teams += team
+ return TRUE
+
+/datum/dynamic_ruleset/roundstart/traitorbro/execute()
+ for(var/datum/team/brother_team/team in pre_brother_teams)
+ team.pick_meeting_area()
+ team.forge_brother_objectives()
+ for(var/datum/mind/M in team.members)
+ M.add_antag_datum(/datum/antagonist/brother, team)
+ team.update_name()
+ mode.brother_teams += pre_brother_teams
+ return TRUE
+
+//////////////////////////////////////////////
+// //
+// CHANGELINGS //
+// //
+//////////////////////////////////////////////
+
+/datum/dynamic_ruleset/roundstart/changeling
+ name = "Changelings"
+ antag_flag = ROLE_CHANGELING
+ antag_datum = /datum/antagonist/changeling
+ restricted_roles = list("AI", "Cyborg")
+ protected_roles = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel", "Chief Engineer", "Chief Medical Officer", "Research Director", "Quartermaster")
+ required_candidates = 1
+ weight = 3
+ cost = 30
+ requirements = list(80,70,60,50,40,20,20,10,10,10)
+ high_population_requirement = 10
+ var/team_mode_probability = 30
+
+/datum/dynamic_ruleset/roundstart/changeling/pre_execute()
+ var/num_changelings = min(round(mode.candidates.len / 10) + 1, candidates.len)
+ for (var/i = 1 to num_changelings)
+ var/mob/M = pick(candidates)
+ candidates -= M
+ assigned += M.mind
+ M.mind.restricted_roles = restricted_roles
+ M.mind.special_role = ROLE_CHANGELING
+ return TRUE
+
+/datum/dynamic_ruleset/roundstart/changeling/execute()
+ var/team_mode = FALSE
+ if(prob(team_mode_probability))
+ team_mode = TRUE
+ var/list/team_objectives = subtypesof(/datum/objective/changeling_team_objective)
+ var/list/possible_team_objectives = list()
+ for(var/T in team_objectives)
+ var/datum/objective/changeling_team_objective/CTO = T
+ if(assigned.len >= initial(CTO.min_lings))
+ possible_team_objectives += T
+
+ if(possible_team_objectives.len && prob(20*assigned.len))
+ GLOB.changeling_team_objective_type = pick(possible_team_objectives)
+ for(var/datum/mind/changeling in assigned)
+ var/datum/antagonist/changeling/new_antag = new antag_datum()
+ new_antag.team_mode = team_mode
+ changeling.add_antag_datum(new_antag)
+ return TRUE
+
+//////////////////////////////////////////////
+// //
+// WIZARDS //
+// //
+//////////////////////////////////////////////
+
+// Dynamic is a wonderful thing that adds wizards to every round and then adds even more wizards during the round.
+/datum/dynamic_ruleset/roundstart/wizard
+ name = "Wizard"
+ antag_flag = ROLE_WIZARD
+ antag_datum = /datum/antagonist/wizard
+ minimum_required_age = 14
+ restricted_roles = list("Head of Security", "Captain") // Just to be sure that a wizard getting picked won't ever imply a Captain or HoS not getting drafted
+ required_candidates = 1
+ weight = 1
+ cost = 30
+ requirements = list(90,90,70,40,30,20,10,10,10,10)
+ high_population_requirement = 10
+ var/list/roundstart_wizards = list()
+
+/datum/dynamic_ruleset/roundstart/wizard/acceptable(population=0, threat=0)
+ if(GLOB.wizardstart.len == 0)
+ log_admin("Cannot accept Wizard ruleset. Couldn't find any wizard spawn points.")
+ message_admins("Cannot accept Wizard ruleset. Couldn't find any wizard spawn points.")
+ return FALSE
+ return ..()
+
+/datum/dynamic_ruleset/roundstart/wizard/pre_execute()
+ if(GLOB.wizardstart.len == 0)
+ return FALSE
+
+ var/mob/M = pick(candidates)
+ if (M)
+ candidates -= M
+ assigned += M.mind
+ M.mind.assigned_role = ROLE_WIZARD
+ M.mind.special_role = ROLE_WIZARD
+
+ return TRUE
+
+/datum/dynamic_ruleset/roundstart/wizard/execute()
+ for(var/datum/mind/M in assigned)
+ M.current.forceMove(pick(GLOB.wizardstart))
+ M.add_antag_datum(new antag_datum())
+ return TRUE
+
+//////////////////////////////////////////////
+// //
+// BLOOD CULT //
+// //
+//////////////////////////////////////////////
+
+/datum/dynamic_ruleset/roundstart/bloodcult
+ name = "Blood Cult"
+ antag_flag = ROLE_CULTIST
+ antag_datum = /datum/antagonist/cult
+ minimum_required_age = 14
+ restricted_roles = list("AI", "Cyborg")
+ protected_roles = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel", "Chief Engineer", "Chief Medical Officer", "Research Director", "Quartermaster")
+ required_candidates = 2
+ weight = 3
+ cost = 30
+ requirements = list(100,90,80,60,40,30,10,10,10,10)
+ high_population_requirement = 10
+ pop_per_requirement = 5
+ flags = HIGHLANDER_RULESET
+ var/cultist_cap = list(2,2,2,3,3,4,4,4,4,4)
+ var/datum/team/cult/main_cult
+
+/datum/dynamic_ruleset/roundstart/bloodcult/ready(forced = FALSE)
+ var/indice_pop = min(10,round(mode.roundstart_pop_ready/pop_per_requirement)+1)
+ required_candidates = cultist_cap[indice_pop]
+ . = ..()
+
+/datum/dynamic_ruleset/roundstart/bloodcult/pre_execute()
+ var/indice_pop = min(10,round(mode.roundstart_pop_ready/pop_per_requirement)+1)
+ var/cultists = cultist_cap[indice_pop]
+ for(var/cultists_number = 1 to cultists)
+ if(candidates.len <= 0)
+ break
+ var/mob/M = pick(candidates)
+ candidates -= M
+ assigned += M.mind
+ M.mind.special_role = ROLE_CULTIST
+ M.mind.restricted_roles = restricted_roles
+ return TRUE
+
+/datum/dynamic_ruleset/roundstart/bloodcult/execute()
+ main_cult = new
+ for(var/datum/mind/M in assigned)
+ var/datum/antagonist/cult/new_cultist = new antag_datum()
+ new_cultist.cult_team = main_cult
+ new_cultist.give_equipment = TRUE
+ M.add_antag_datum(new_cultist)
+ main_cult.setup_objectives()
+ return TRUE
+
+/datum/dynamic_ruleset/roundstart/bloodcult/round_result()
+ ..()
+ if(main_cult.check_cult_victory())
+ SSticker.mode_result = "win - cult win"
+ SSticker.news_report = CULT_SUMMON
+ else
+ SSticker.mode_result = "loss - staff stopped the cult"
+ SSticker.news_report = CULT_FAILURE
+
+//////////////////////////////////////////////
+// //
+// NUCLEAR OPERATIVES //
+// //
+//////////////////////////////////////////////
+
+/datum/dynamic_ruleset/roundstart/nuclear
+ name = "Nuclear Emergency"
+ antag_flag = ROLE_OPERATIVE
+ antag_datum = /datum/antagonist/nukeop
+ var/datum/antagonist/antag_leader_datum = /datum/antagonist/nukeop/leader
+ minimum_required_age = 14
+ restricted_roles = list("Head of Security", "Captain") // Just to be sure that a nukie getting picked won't ever imply a Captain or HoS not getting drafted
+ required_candidates = 5
+ weight = 3
+ cost = 40
+ requirements = list(90,90,90,80,60,40,30,20,10,10)
+ high_population_requirement = 10
+ pop_per_requirement = 5
+ flags = HIGHLANDER_RULESET
+ var/operative_cap = list(2,2,2,3,3,3,4,4,5,5)
+ var/datum/team/nuclear/nuke_team
+
+/datum/dynamic_ruleset/roundstart/nuclear/ready(forced = FALSE)
+ var/indice_pop = min(10,round(mode.roundstart_pop_ready/pop_per_requirement)+1)
+ required_candidates = operative_cap[indice_pop]
+ . = ..()
+
+/datum/dynamic_ruleset/roundstart/nuclear/pre_execute()
+ // If ready() did its job, candidates should have 5 or more members in it
+
+ var/indice_pop = min(10,round(mode.roundstart_pop_ready/5)+1)
+ var/operatives = operative_cap[indice_pop]
+ for(var/operatives_number = 1 to operatives)
+ if(candidates.len <= 0)
+ break
+ var/mob/M = pick(candidates)
+ candidates -= M
+ assigned += M.mind
+ M.mind.assigned_role = "Nuclear Operative"
+ M.mind.special_role = "Nuclear Operative"
+ return TRUE
+
+/datum/dynamic_ruleset/roundstart/nuclear/execute()
+ var/leader = TRUE
+ for(var/datum/mind/M in assigned)
+ if (leader)
+ leader = FALSE
+ var/datum/antagonist/nukeop/leader/new_op = M.add_antag_datum(antag_leader_datum)
+ nuke_team = new_op.nuke_team
+ else
+ var/datum/antagonist/nukeop/new_op = new antag_datum()
+ M.add_antag_datum(new_op)
+ return TRUE
+
+/datum/dynamic_ruleset/roundstart/nuclear/round_result()
+ var result = nuke_team.get_result()
+ switch(result)
+ if(NUKE_RESULT_FLUKE)
+ SSticker.mode_result = "loss - syndicate nuked - disk secured"
+ SSticker.news_report = NUKE_SYNDICATE_BASE
+ if(NUKE_RESULT_NUKE_WIN)
+ SSticker.mode_result = "win - syndicate nuke"
+ SSticker.news_report = STATION_NUKED
+ if(NUKE_RESULT_NOSURVIVORS)
+ SSticker.mode_result = "halfwin - syndicate nuke - did not evacuate in time"
+ SSticker.news_report = STATION_NUKED
+ if(NUKE_RESULT_WRONG_STATION)
+ SSticker.mode_result = "halfwin - blew wrong station"
+ SSticker.news_report = NUKE_MISS
+ if(NUKE_RESULT_WRONG_STATION_DEAD)
+ SSticker.mode_result = "halfwin - blew wrong station - did not evacuate in time"
+ SSticker.news_report = NUKE_MISS
+ if(NUKE_RESULT_CREW_WIN_SYNDIES_DEAD)
+ SSticker.mode_result = "loss - evacuation - disk secured - syndi team dead"
+ SSticker.news_report = OPERATIVES_KILLED
+ if(NUKE_RESULT_CREW_WIN)
+ SSticker.mode_result = "loss - evacuation - disk secured"
+ SSticker.news_report = OPERATIVES_KILLED
+ if(NUKE_RESULT_DISK_LOST)
+ SSticker.mode_result = "halfwin - evacuation - disk not secured"
+ SSticker.news_report = OPERATIVE_SKIRMISH
+ if(NUKE_RESULT_DISK_STOLEN)
+ SSticker.mode_result = "halfwin - detonation averted"
+ SSticker.news_report = OPERATIVE_SKIRMISH
+ else
+ SSticker.mode_result = "halfwin - interrupted"
+ SSticker.news_report = OPERATIVE_SKIRMISH
+
+//////////////////////////////////////////////
+// //
+// REVS //
+// //
+//////////////////////////////////////////////
+
+/datum/dynamic_ruleset/roundstart/delayed/revs
+ name = "Revolution"
+ persistent = TRUE
+ antag_flag = ROLE_REV_HEAD
+ antag_flag_override = ROLE_REV
+ antag_datum = /datum/antagonist/rev/head
+ minimum_required_age = 14
+ restricted_roles = list("AI", "Cyborg", "Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel", "Chief Engineer", "Chief Medical Officer", "Research Director", "Quartermaster")
+ required_candidates = 3
+ weight = 2
+ cost = 35
+ requirements = list(101,101,70,40,30,20,10,10,10,10)
+ high_population_requirement = 10
+ delay = 5 MINUTES
+ flags = HIGHLANDER_RULESET
+ // I give up, just there should be enough heads with 35 players...
+ minimum_players = 35
+ var/datum/team/revolution/revolution
+ var/finished = 0
+
+/datum/dynamic_ruleset/roundstart/delayed/revs/execute()
+ var/max_canditates = 4
+ revolution = new()
+ for(var/i = 1 to max_canditates)
+ if(candidates.len <= 0)
+ break
+ var/mob/M = pick(candidates)
+ candidates -= M
+ assigned += M.mind
+ M.mind.restricted_roles = restricted_roles
+ M.mind.special_role = antag_flag
+ var/datum/antagonist/rev/head/new_head = new antag_datum()
+ new_head.give_flash = TRUE
+ new_head.give_hud = TRUE
+ new_head.remove_clumsy = TRUE
+ M.mind.add_antag_datum(new_head,revolution)
+
+ revolution.update_objectives()
+ revolution.update_heads()
+ SSshuttle.registerHostileEnvironment(src)
+
+ return TRUE
+
+/datum/dynamic_ruleset/roundstart/delayed/revs/rule_process()
+ if(check_rev_victory())
+ finished = 1
+ else if(check_heads_victory())
+ finished = 2
+ return
+
+/datum/dynamic_ruleset/roundstart/delayed/revs/check_finished()
+ if(CONFIG_GET(keyed_list/continuous)["revolution"])
+ if(finished)
+ SSshuttle.clearHostileEnvironment(src)
+ return ..()
+ if(finished != 0)
+ return TRUE
+ else
+ return ..()
+
+/datum/dynamic_ruleset/roundstart/delayed/revs/proc/check_rev_victory()
+ for(var/datum/objective/mutiny/objective in revolution.objectives)
+ if(!(objective.check_completion()))
+ return FALSE
+ return TRUE
+
+/datum/dynamic_ruleset/roundstart/delayed/revs/proc/check_heads_victory()
+ for(var/datum/mind/rev_mind in revolution.head_revolutionaries())
+ var/turf/T = get_turf(rev_mind.current)
+ if(!considered_afk(rev_mind) && considered_alive(rev_mind) && is_station_level(T.z))
+ if(ishuman(rev_mind.current) || ismonkey(rev_mind.current))
+ return FALSE
+ return TRUE
+
+/datum/dynamic_ruleset/roundstart/delayed/revs/round_result()
+ if(finished == 1)
+ SSticker.mode_result = "win - heads killed"
+ SSticker.news_report = REVS_WIN
+ else if(finished == 2)
+ SSticker.mode_result = "loss - rev heads killed"
+ SSticker.news_report = REVS_LOSE
+
+// Admin only rulesets. The threat requirement is 101 so it is not possible to roll them.
+
+//////////////////////////////////////////////
+// //
+// EXTENDED //
+// //
+//////////////////////////////////////////////
+
+/datum/dynamic_ruleset/roundstart/extended
+ name = "Extended"
+ antag_flag = null
+ antag_datum = null
+ restricted_roles = list()
+ required_candidates = 0
+ weight = 3
+ cost = 0
+ requirements = list(101,101,101,101,101,101,101,101,101,101)
+ high_population_requirement = 101
+
+/datum/dynamic_ruleset/roundstart/extended/pre_execute()
+ message_admins("Starting a round of extended.")
+ log_game("Starting a round of extended.")
+ mode.spend_threat(mode.threat)
+ return TRUE
+
+//////////////////////////////////////////////
+// //
+// CLOCKCULT //
+// //
+//////////////////////////////////////////////
+
+/datum/dynamic_ruleset/roundstart/clockcult
+ name = "Clockcult"
+ antag_flag = ROLE_SERVANT_OF_RATVAR
+ antag_datum = /datum/antagonist/clockcult
+ restricted_roles = list("AI", "Cyborg", "Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel", "Chief Engineer", "Chief Medical Officer", "Research Director", "Quartermaster")
+ required_candidates = 4
+ weight = 3
+ cost = 0
+ requirements = list(101,101,101,101,101,101,101,101,101,101)
+ high_population_requirement = 101
+ flags = HIGHLANDER_RULESET
+ var/ark_time
+
+/datum/dynamic_ruleset/roundstart/clockcult/pre_execute()
+ var/list/errorList = list()
+ var/list/reebes = SSmapping.LoadGroup(errorList, "Reebe", "map_files/generic", "City_of_Cogs.dmm", default_traits = ZTRAITS_REEBE, silent = TRUE)
+ if(errorList.len)
+ message_admins("Reebe failed to load!")
+ log_game("Reebe failed to load!")
+ return FALSE
+ for(var/datum/parsed_map/PM in reebes)
+ PM.initTemplateBounds()
+
+ var/starter_servants = 4
+ var/number_players = num_players()
+ if(number_players > 30)
+ number_players -= 30
+ starter_servants += round(number_players / 10)
+ starter_servants = min(starter_servants, 8)
+ for (var/i in 1 to starter_servants)
+ var/mob/servant = pick(candidates)
+ candidates -= servant
+ assigned += servant.mind
+ servant.mind.assigned_role = ROLE_SERVANT_OF_RATVAR
+ servant.mind.special_role = ROLE_SERVANT_OF_RATVAR
+ ark_time = 30 + round((number_players / 5))
+ ark_time = min(ark_time, 35)
+ return TRUE
+
+/datum/dynamic_ruleset/roundstart/clockcult/execute()
+ var/list/spread_out_spawns = GLOB.servant_spawns.Copy()
+ for(var/datum/mind/servant in assigned)
+ var/mob/S = servant.current
+ if(!spread_out_spawns.len)
+ spread_out_spawns = GLOB.servant_spawns.Copy()
+ log_game("[key_name(servant)] was made an initial servant of Ratvar")
+ var/turf/T = pick_n_take(spread_out_spawns)
+ S.forceMove(T)
+ greet_servant(S)
+ equip_servant(S)
+ add_servant_of_ratvar(S, TRUE)
+ var/obj/structure/destructible/clockwork/massive/celestial_gateway/G = GLOB.ark_of_the_clockwork_justiciar //that's a mouthful
+ G.final_countdown(ark_time)
+ return TRUE
+
+/datum/dynamic_ruleset/roundstart/clockcult/proc/greet_servant(mob/M) //Description of their role
+ if(!M)
+ return 0
+ to_chat(M, "You are a servant of Ratvar, the Clockwork Justiciar!")
+ to_chat(M, "You have approximately [ark_time] minutes until the Ark activates.")
+ to_chat(M, "Unlock Script scripture by converting a new servant.")
+ to_chat(M, "Application scripture will be unlocked halfway until the Ark's activation.")
+ M.playsound_local(get_turf(M), 'sound/ambience/antag/clockcultalr.ogg', 100, FALSE, pressure_affected = FALSE)
+ return 1
+
+/datum/dynamic_ruleset/roundstart/clockcult/proc/equip_servant(mob/living/M) //Grants a clockwork slab to the mob, with one of each component
+ if(!M || !ishuman(M))
+ return FALSE
+ var/mob/living/carbon/human/L = M
+ L.equipOutfit(/datum/outfit/servant_of_ratvar)
+ var/obj/item/clockwork/slab/S = new
+ var/slot = "At your feet"
+ var/list/slots = list("In your left pocket" = SLOT_L_STORE, "In your right pocket" = SLOT_R_STORE, "In your backpack" = SLOT_IN_BACKPACK, "On your belt" = SLOT_BELT)
+ if(ishuman(L))
+ var/mob/living/carbon/human/H = L
+ slot = H.equip_in_one_of_slots(S, slots)
+ if(slot == "In your backpack")
+ slot = "In your [H.back.name]"
+ if(slot == "At your feet")
+ if(!S.forceMove(get_turf(L)))
+ qdel(S)
+ if(S && !QDELETED(S))
+ to_chat(L, "There is a paper in your backpack! It'll tell you if anything's changed, as well as what to expect.")
+ to_chat(L, "[slot] is a clockwork slab, a multipurpose tool used to construct machines and invoke ancient words of power. If this is your first time \
+ as a servant, you can find a concise tutorial in the Recollection category of its interface.")
+ to_chat(L, "If you want more information, you can read the wiki page to learn more.")
+ return TRUE
+ return FALSE
+
+/datum/dynamic_ruleset/roundstart/clockcult/round_result()
+ if(GLOB.clockwork_gateway_activated)
+ SSticker.news_report = CLOCK_SUMMON
+ SSticker.mode_result = "win - servants completed their objective (summon ratvar)"
+ else
+ SSticker.news_report = CULT_FAILURE
+ SSticker.mode_result = "loss - servants failed their objective (summon ratvar)"
+
+//////////////////////////////////////////////
+// //
+// CLOWN OPS //
+// //
+//////////////////////////////////////////////
+
+/datum/dynamic_ruleset/roundstart/nuclear/clown_ops
+ name = "Clown Ops"
+ antag_datum = /datum/antagonist/nukeop/clownop
+ antag_leader_datum = /datum/antagonist/nukeop/leader/clownop
+ requirements = list(101,101,101,101,101,101,101,101,101,101)
+ high_population_requirement = 101
+
+/datum/dynamic_ruleset/roundstart/nuclear/clown_ops/pre_execute()
+ . = ..()
+ if(.)
+ for(var/obj/machinery/nuclearbomb/syndicate/S in GLOB.nuke_list)
+ var/turf/T = get_turf(S)
+ if(T)
+ qdel(S)
+ new /obj/machinery/nuclearbomb/syndicate/bananium(T)
+ for(var/datum/mind/V in assigned)
+ V.assigned_role = "Clown Operative"
+ V.special_role = "Clown Operative"
+
+//////////////////////////////////////////////
+// //
+// DEVIL //
+// //
+//////////////////////////////////////////////
+
+/datum/dynamic_ruleset/roundstart/devil
+ name = "Devil"
+ antag_flag = ROLE_DEVIL
+ antag_datum = /datum/antagonist/devil
+ restricted_roles = list("Lawyer", "Curator", "Chaplain", "Head of Security", "Captain", "AI")
+ required_candidates = 1
+ weight = 3
+ cost = 0
+ requirements = list(101,101,101,101,101,101,101,101,101,101)
+ high_population_requirement = 101
+ var/devil_limit = 4 // Hard limit on devils if scaling is turned off
+
+/datum/dynamic_ruleset/roundstart/devil/pre_execute()
+ var/tsc = CONFIG_GET(number/traitor_scaling_coeff)
+ var/num_devils = 1
+
+ if(tsc)
+ num_devils = max(required_candidates, min(round(num_players() / (tsc * 3)) + 2, round(num_players() / (tsc * 1.5))))
+ else
+ num_devils = max(required_candidates, min(num_players(), devil_limit))
+
+ for(var/j = 0, j < num_devils, j++)
+ if (!candidates.len)
+ break
+ var/mob/devil = pick(candidates)
+ assigned += devil
+ candidates -= devil
+ devil.mind.special_role = ROLE_DEVIL
+ devil.mind.restricted_roles = restricted_roles
+
+ log_game("[key_name(devil)] has been selected as a devil")
+ return TRUE
+
+/datum/dynamic_ruleset/roundstart/devil/execute()
+ for(var/datum/mind/devil in assigned)
+ add_devil(devil.current, ascendable = TRUE)
+ add_devil_objectives(devil,2)
+ return TRUE
+
+/datum/dynamic_ruleset/roundstart/devil/proc/add_devil_objectives(datum/mind/devil_mind, quantity)
+ var/list/validtypes = list(/datum/objective/devil/soulquantity, /datum/objective/devil/soulquality, /datum/objective/devil/sintouch, /datum/objective/devil/buy_target)
+ var/datum/antagonist/devil/D = devil_mind.has_antag_datum(/datum/antagonist/devil)
+ for(var/i = 1 to quantity)
+ var/type = pick(validtypes)
+ var/datum/objective/devil/objective = new type(null)
+ objective.owner = devil_mind
+ D.objectives += objective
+ if(!istype(objective, /datum/objective/devil/buy_target))
+ validtypes -= type
+ else
+ objective.find_target()
+
+//////////////////////////////////////////////
+// //
+// MONKEY //
+// //
+//////////////////////////////////////////////
+
+/datum/dynamic_ruleset/roundstart/monkey
+ name = "Monkey"
+ antag_flag = ROLE_MONKEY
+ antag_datum = /datum/antagonist/monkey/leader
+ restricted_roles = list("Cyborg", "AI")
+ required_candidates = 1
+ weight = 3
+ cost = 0
+ requirements = list(101,101,101,101,101,101,101,101,101,101)
+ high_population_requirement = 101
+ var/players_per_carrier = 30
+ var/monkeys_to_win = 1
+ var/escaped_monkeys = 0
+ var/datum/team/monkey/monkey_team
+
+/datum/dynamic_ruleset/roundstart/monkey/pre_execute()
+ var/carriers_to_make = max(round(num_players()/players_per_carrier, 1), 1)
+
+ for(var/j = 0, j < carriers_to_make, j++)
+ if (!candidates.len)
+ break
+ var/mob/carrier = pick(candidates)
+ candidates -= carrier
+ assigned += carrier.mind
+ carrier.mind.special_role = "Monkey Leader"
+ carrier.mind.restricted_roles = restricted_roles
+ log_game("[key_name(carrier)] has been selected as a Jungle Fever carrier")
+ return TRUE
+
+/datum/dynamic_ruleset/roundstart/monkey/execute()
+ for(var/datum/mind/carrier in assigned)
+ var/datum/antagonist/monkey/M = add_monkey_leader(carrier)
+ if(M)
+ monkey_team = M.monkey_team
+ return TRUE
+
+/datum/dynamic_ruleset/roundstart/monkey/proc/check_monkey_victory()
+ if(SSshuttle.emergency.mode != SHUTTLE_ENDGAME)
+ return FALSE
+ var/datum/disease/D = new /datum/disease/transformation/jungle_fever()
+ for(var/mob/living/carbon/monkey/M in GLOB.alive_mob_list)
+ if (M.HasDisease(D))
+ if(M.onCentCom() || M.onSyndieBase())
+ escaped_monkeys++
+ if(escaped_monkeys >= monkeys_to_win)
+ return TRUE
+ else
+ return FALSE
+
+// This does not get called. Look into making it work.
+/datum/dynamic_ruleset/roundstart/monkey/round_result()
+ if(check_monkey_victory())
+ SSticker.mode_result = "win - monkey win"
+ else
+ SSticker.mode_result = "loss - staff stopped the monkeys"
+
+//////////////////////////////////////////////
+// //
+// METEOR //
+// //
+//////////////////////////////////////////////
+
+/datum/dynamic_ruleset/roundstart/meteor
+ name = "Meteor"
+ persistent = TRUE
+ required_candidates = 0
+ weight = 3
+ cost = 0
+ requirements = list(101,101,101,101,101,101,101,101,101,101)
+ high_population_requirement = 101
+ var/meteordelay = 2000
+ var/nometeors = 0
+ var/rampupdelta = 5
+
+/datum/dynamic_ruleset/roundstart/meteor/rule_process()
+ if(nometeors || meteordelay > world.time - SSticker.round_start_time)
+ return
+
+ var/list/wavetype = GLOB.meteors_normal
+ var/meteorminutes = (world.time - SSticker.round_start_time - meteordelay) / 10 / 60
+
+ if (prob(meteorminutes))
+ wavetype = GLOB.meteors_threatening
+
+ if (prob(meteorminutes/2))
+ wavetype = GLOB.meteors_catastrophic
+
+ var/ramp_up_final = CLAMP(round(meteorminutes/rampupdelta), 1, 10)
+
+ spawn_meteors(ramp_up_final, wavetype)
diff --git a/code/game/gamemodes/dynamic/readme.md b/code/game/gamemodes/dynamic/readme.md
new file mode 100644
index 0000000000..6bd064cf7c
--- /dev/null
+++ b/code/game/gamemodes/dynamic/readme.md
@@ -0,0 +1,57 @@
+# DYNAMIC
+
+## ROUNDSTART
+
+Dynamic rolls threat based on a special sauce formula:
+"dynamic_curve_width \* tan((3.1416 \* (rand() - 0.5) \* 57.2957795)) + dynamic_curve_centre"
+
+Latejoin and midround injection cooldowns are set using exponential distribution between
+5 minutes and 25 for latejoin
+15 minutes and 35 for midround
+this value is then added to world.time and assigned to the injection cooldown variables.
+
+rigged_roundstart() is called instead if there are forced rules (an admin set the mode)
+
+can_start() -> pre_setup() -> roundstart() OR rigged_roundstart() -> picking_roundstart_rule(drafted_rules) -> post_setup()
+
+## PROCESS
+
+Calls rule_process on every rule which is in the current_rules list.
+Every sixty seconds, update_playercounts()
+Midround injection time is checked against world.time to see if an injection should happen.
+If midround injection time is lower than world.time, it updates playercounts again, then tries to inject and generates a new cooldown regardless of whether a rule is picked.
+
+## LATEJOIN
+
+make_antag_chance(newPlayer) -> [For each latespawn rule...]
+-> acceptable(living players, threat_level) -> trim_candidates() -> ready(forced=FALSE)
+**If true, add to drafted rules
+**NOTE that acceptable uses threat_level not threat!
+**NOTE Latejoin timer is ONLY reset if at least one rule was drafted.
+**NOTE the new_player.dm AttemptLateSpawn() calls OnPostSetup for all roles (unless assigned role is MODE)
+[After collecting all draftble rules...]
+-> picking_latejoin_ruleset(drafted_rules) -> spend threat -> ruleset.execute()
+## MIDROUND
+process() -> [For each midround rule...]
+-> acceptable(living players, threat_level) -> trim_candidates() -> ready(forced=FALSE)
+[After collecting all draftble rules...]
+-> picking_midround_ruleset(drafted_rules) -> spend threat -> ruleset.execute()
+## FORCED
+For latejoin, it simply sets forced_latejoin_rule
+make_antag_chance(newPlayer) -> trim_candidates() -> ready(forced=TRUE) **NOTE no acceptable() call
+For midround, calls the below proc with forced = TRUE
+picking_specific_rule(ruletype,forced) -> forced OR acceptable(living_players, threat_level) -> trim_candidates() -> ready(forced) -> spend threat -> execute()
+**NOTE specific rule can be called by RS traitor->MR autotraitor w/ forced=FALSE
+**NOTE that due to short circuiting acceptable() need not be called if forced.
+## RULESET
+acceptable(population,threat) just checks if enough threat_level for population indice.
+**NOTE that we currently only send threat_level as the second arg, not threat.
+ready(forced) checks if enough candidates and calls the map's map_ruleset(dynamic_ruleset) at the parent level
+trim_candidates() varies significantly according to the ruleset type
+Roundstart: All candidates are new_player mobs. Check them for standard stuff: connected, desire role, not banned, etc.
+**NOTE Roundstart deals with both candidates (trimmed list of valid players) and mode.candidates (everyone readied up). Don't confuse them!
+Latejoin: Only one candidate, the latejoiner. Standard checks.
+Midround: Instead of building a single list candidates, candidates contains four lists: living, dead, observing, and living antags. Standard checks in trim_list(list).
+Midround - Rulesets have additional types
+/from_ghosts: execute() -> send_applications() -> review_applications() -> finish_setup(mob/newcharacter, index) -> setup_role(role)
+**NOTE: execute() here adds dead players and observers to candidates list
diff --git a/code/game/gamemodes/game_mode.dm b/code/game/gamemodes/game_mode.dm
index d16cbebb2a..f790053863 100644
--- a/code/game/gamemodes/game_mode.dm
+++ b/code/game/gamemodes/game_mode.dm
@@ -24,6 +24,7 @@
var/list/datum/mind/antag_candidates = list() // List of possible starting antags goes here
var/list/restricted_jobs = list() // Jobs it doesn't make sense to be. I.E chaplain or AI cultist
var/list/protected_jobs = list() // Jobs that can't be traitors because
+ var/list/required_jobs = list() // alternative required job groups eg list(list(cap=1),list(hos=1,sec=2)) translates to one captain OR one hos and two secmans
var/required_players = 0
var/maximum_players = -1 // -1 is no maximum, positive numbers limit the selection of a mode on overstaffed stations
var/required_enemies = 0
@@ -355,7 +356,7 @@
// Ultimate randomizing code right here
for(var/mob/dead/new_player/player in GLOB.player_list)
- if(player.client && player.ready == PLAYER_READY_TO_PLAY)
+ if(player.client && player.ready == PLAYER_READY_TO_PLAY && player.check_preferences())
players += player
// Shuffling, the players list is now ping-independent!!!
@@ -558,3 +559,7 @@
SSticker.news_report = STATION_EVACUATED
if(SSshuttle.emergency.is_hijacked())
SSticker.news_report = SHUTTLE_HIJACK
+
+/// Mode specific admin panel.
+/datum/game_mode/proc/admin_panel()
+ return
diff --git a/code/game/gamemodes/nuclear/nuclear.dm b/code/game/gamemodes/nuclear/nuclear.dm
index 615d55a818..1767d468ec 100644
--- a/code/game/gamemodes/nuclear/nuclear.dm
+++ b/code/game/gamemodes/nuclear/nuclear.dm
@@ -155,9 +155,9 @@
var/obj/item/U = new uplink_type(H, H.key, tc)
H.equip_to_slot_or_del(U, SLOT_IN_BACKPACK)
- var/obj/item/implant/weapons_auth/W = new/obj/item/implant/weapons_auth(H)
+ var/obj/item/implant/weapons_auth/W = new
W.implant(H)
- var/obj/item/implant/explosive/E = new/obj/item/implant/explosive(H)
+ var/obj/item/implant/explosive/E = new
E.implant(H)
H.faction |= ROLE_SYNDICATE
H.update_icons()
diff --git a/code/game/gamemodes/objective_items.dm b/code/game/gamemodes/objective_items.dm
index e56495d808..176f62ef2d 100644
--- a/code/game/gamemodes/objective_items.dm
+++ b/code/game/gamemodes/objective_items.dm
@@ -53,7 +53,7 @@
name = "the chief engineer's advanced magnetic boots."
targetitem = /obj/item/clothing/shoes/magboots/advance
difficulty = 5
- excludefromjob = list("Chief Engineer")
+ excludefromjob = list("Chief Engineer", "Station Engineer", "Atmospheric Technician")
/datum/objective_item/steal/capmedal
name = "the medal of captaincy."
@@ -62,10 +62,10 @@
excludefromjob = list("Captain")
/datum/objective_item/steal/hypo
- name = "the hypospray."
+ name = "the Chief Medical Officer's MKII hypospray."
targetitem = /obj/item/hypospray/mkii/CMO //CITADEL EDIT, changing theft objective for the Hypo MK II
difficulty = 5
- excludefromjob = list("Chief Medical Officer")
+ excludefromjob = list("Chief Medical Officer", "Medical Doctor", "Chemist", "Virologist", "Geneticist")
/datum/objective_item/steal/nukedisc
name = "the nuclear authentication disk."
@@ -83,10 +83,10 @@
excludefromjob = list("Head of Security", "Warden")
/datum/objective_item/steal/reactive
- name = "the reactive teleport armor."
+ name = "a reactive teleport armor."
targetitem = /obj/item/clothing/suit/armor/reactive
difficulty = 5
- excludefromjob = list("Research Director")
+ excludefromjob = list("Research Director","Scientist", "Roboticist")
/datum/objective_item/steal/documents
name = "any set of secret documents of any organization."
@@ -143,7 +143,7 @@
name = "the station blueprints."
targetitem = /obj/item/areaeditor/blueprints
difficulty = 10
- excludefromjob = list("Chief Engineer")
+ excludefromjob = list("Chief Engineer", "Station Engineer", "Atmospheric Technician")
altitems = list(/obj/item/photo)
/datum/objective_item/steal/blueprints/check_special_completion(obj/item/I)
@@ -159,7 +159,7 @@
name = "an unused sample of slime extract."
targetitem = /obj/item/slime_extract
difficulty = 3
- excludefromjob = list("Research Director","Scientist")
+ excludefromjob = list("Research Director","Scientist", "Roboticist")
/datum/objective_item/steal/slime/check_special_completion(obj/item/slime_extract/E)
if(E.Uses > 0)
diff --git a/code/game/gamemodes/overthrow/overthrow.dm b/code/game/gamemodes/overthrow/overthrow.dm
index 1548556515..dca0c1ade1 100644
--- a/code/game/gamemodes/overthrow/overthrow.dm
+++ b/code/game/gamemodes/overthrow/overthrow.dm
@@ -3,7 +3,8 @@
name = "overthrow"
config_tag = "overthrow"
antag_flag = ROLE_OVERTHROW
- restricted_jobs = list("Security Officer", "Warden", "Detective", "AI", "Cyborg","Captain", "Head of Personnel", "Head of Security", "Chief Engineer", "Research Director", "Chief Medical Officer")
+ restricted_jobs = list("AI", "Cyborg")
+ protected_jobs = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel", "Chief Engineer", "Chief Medical Officer", "Research Director", "Quartermaster")
required_players = 20 // the core idea is of a swift, bloodless coup, so it shouldn't be as chaotic as revs.
required_enemies = 2 // minimum two teams, otherwise it's just nerfed revs.
recommended_enemies = 4
diff --git a/code/game/gamemodes/revolution/revolution.dm b/code/game/gamemodes/revolution/revolution.dm
index 09047b05a9..8459819b5b 100644
--- a/code/game/gamemodes/revolution/revolution.dm
+++ b/code/game/gamemodes/revolution/revolution.dm
@@ -12,7 +12,8 @@
config_tag = "revolution"
antag_flag = ROLE_REV
false_report_weight = 10
- restricted_jobs = list("Security Officer", "Warden", "Detective", "AI", "Cyborg","Captain", "Head of Personnel", "Head of Security", "Chief Engineer", "Research Director", "Chief Medical Officer")
+ restricted_jobs = list("AI", "Cyborg")
+ protected_jobs = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel", "Chief Engineer", "Chief Medical Officer", "Research Director", "Quartermaster")
required_players = 30
required_enemies = 2
recommended_enemies = 3
diff --git a/code/game/gamemodes/traitor/traitor.dm b/code/game/gamemodes/traitor/traitor.dm
index 789737ec0f..4a6e72cf67 100644
--- a/code/game/gamemodes/traitor/traitor.dm
+++ b/code/game/gamemodes/traitor/traitor.dm
@@ -96,4 +96,4 @@
/datum/game_mode/traitor/generate_report()
return "Although more specific threats are commonplace, you should always remain vigilant for Syndicate agents aboard your station. Syndicate communications have implied that many \
- Nanotrasen employees are Syndicate agents with hidden memories that may be activated at a moment's notice, so it's possible that these agents might not even know their positions."
\ No newline at end of file
+ Nanotrasen employees are Syndicate agents with hidden memories that may be activated at a moment's notice, so it's possible that these agents might not even know their positions."
diff --git a/code/game/machinery/Sleeper.dm b/code/game/machinery/Sleeper.dm
index aa363428fb..513fea4360 100644
--- a/code/game/machinery/Sleeper.dm
+++ b/code/game/machinery/Sleeper.dm
@@ -88,17 +88,50 @@
return
close_machine(target)
-/obj/machinery/sleeper/attackby(obj/item/I, mob/user, params)
- if(!state_open && !occupant)
- if(default_deconstruction_screwdriver(user, "[initial(icon_state)]-o", initial(icon_state), I))
- return
+/obj/machinery/sleeper/screwdriver_act(mob/living/user, obj/item/I)
+ . = TRUE
+ if(..())
+ return
+ if(occupant)
+ to_chat(user, "[src] is currently occupied!")
+ return
+ if(state_open)
+ to_chat(user, "[src] must be closed to [panel_open ? "close" : "open"] its maintenance hatch!")
+ return
+ if(default_deconstruction_screwdriver(user, "[initial(icon_state)]-o", initial(icon_state), I))
+ return
+ return FALSE
+
+/obj/machinery/sleeper/wrench_act(mob/living/user, obj/item/I)
+ . = ..()
if(default_change_direction_wrench(user, I))
- return
+ return TRUE
+
+/obj/machinery/sleeper/crowbar_act(mob/living/user, obj/item/I)
+ . = ..()
if(default_pry_open(I))
- return
+ return TRUE
if(default_deconstruction_crowbar(I))
+ return TRUE
+
+/obj/machinery/sleeper/default_pry_open(obj/item/I) //wew
+ . = !(state_open || panel_open || (flags_1 & NODECONSTRUCT_1)) && I.tool_behaviour == TOOL_CROWBAR
+ if(.)
+ I.play_tool_sound(src, 50)
+ visible_message("[usr] pries open [src].", "You pry open [src].")
+ open_machine()
+
+/obj/machinery/sleeper/AltClick(mob/user)
+ if(!user.canUseTopic(src, !issilicon(user)))
return
- return ..()
+ if(state_open)
+ close_machine()
+ else
+ open_machine()
+
+/obj/machinery/sleeper/examine(mob/user)
+ ..()
+ to_chat(user, "Alt-click [src] to [state_open ? "close" : "open"] it.")
/obj/machinery/sleeper/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \
datum/tgui/master_ui = null, datum/ui_state/state = GLOB.notcontained_state)
@@ -146,7 +179,7 @@
data["occupant"]["toxLoss"] = mob_occupant.getToxLoss()
data["occupant"]["fireLoss"] = mob_occupant.getFireLoss()
data["occupant"]["cloneLoss"] = mob_occupant.getCloneLoss()
- data["occupant"]["brainLoss"] = mob_occupant.getBrainLoss()
+ data["occupant"]["brainLoss"] = mob_occupant.getOrganLoss(ORGAN_SLOT_BRAIN)
data["occupant"]["reagents"] = list()
if(mob_occupant.reagents && mob_occupant.reagents.reagent_list.len)
for(var/datum/reagent/R in mob_occupant.reagents.reagent_list)
@@ -190,11 +223,13 @@
if(inject_chem(chem, usr))
. = TRUE
if(scrambled_chems && prob(5))
- to_chat(usr, "Chem System Re-route detected, results may not be as expected!")
+ to_chat(usr, "Chemical system re-route detected, results may not be as expected!")
/obj/machinery/sleeper/emag_act(mob/user)
+ . = ..()
scramble_chem_buttons()
to_chat(user, "You scramble the sleeper's user interface!")
+ return TRUE
/obj/machinery/sleeper/proc/inject_chem(chem, mob/user)
if((chem in available_chems) && chem_allowed(chem))
diff --git a/code/game/machinery/_machinery.dm b/code/game/machinery/_machinery.dm
index b0e4c699d7..667c1f6054 100644
--- a/code/game/machinery/_machinery.dm
+++ b/code/game/machinery/_machinery.dm
@@ -113,6 +113,11 @@ Class Procs:
var/atom/movable/occupant = null
var/speed_process = FALSE // Process as fast as possible?
var/obj/item/circuitboard/circuit // Circuit to be created and inserted when the machinery is created
+ var/obj/item/card/id/inserted_scan_id
+ var/obj/item/card/id/inserted_modify_id
+ var/list/region_access = null // For the identification console (card.dm)
+ var/list/head_subordinates = null // For the identification console (card.dm)
+ var/authenticated = 0 // For the identification console (card.dm)
var/interaction_flags_machine = INTERACT_MACHINE_WIRES_IF_OPEN | INTERACT_MACHINE_ALLOW_SILICON | INTERACT_MACHINE_OPEN_SILICON | INTERACT_MACHINE_SET_MACHINE
@@ -131,7 +136,7 @@ Class Procs:
else
START_PROCESSING(SSfastprocess, src)
power_change()
- AddComponent(/datum/component/redirect, list(COMSIG_ENTER_AREA = CALLBACK(src, .proc/power_change)))
+ RegisterSignal(src, COMSIG_ENTER_AREA, .proc/power_change)
if (occupant_typecache)
occupant_typecache = typecacheof(occupant_typecache)
@@ -143,6 +148,10 @@ Class Procs:
else
STOP_PROCESSING(SSfastprocess, src)
dropContents()
+ if(length(component_parts))
+ for(var/atom/A in component_parts)
+ qdel(A)
+ component_parts.Cut()
return ..()
/obj/machinery/proc/locate_machinery()
@@ -177,14 +186,18 @@ Class Procs:
if(isliving(A))
var/mob/living/L = A
L.update_canmove()
+ SEND_SIGNAL(src, COMSIG_MACHINE_EJECT_OCCUPANT, occupant)
occupant = null
+/obj/machinery/proc/can_be_occupant(atom/movable/am)
+ return occupant_typecache ? is_type_in_typecache(am, occupant_typecache) : isliving(am)
+
/obj/machinery/proc/close_machine(atom/movable/target = null)
state_open = FALSE
density = TRUE
if(!target)
for(var/am in loc)
- if (!(occupant_typecache ? is_type_in_typecache(am, occupant_typecache) : isliving(am)))
+ if (!(can_be_occupant(am)))
continue
var/atom/movable/AM = am
if(AM.has_buckled_mobs())
@@ -311,6 +324,7 @@ Class Procs:
spawn_frame(disassembled)
for(var/obj/item/I in component_parts)
I.forceMove(loc)
+ component_parts.Cut()
qdel(src)
/obj/machinery/proc/spawn_frame(disassembled)
@@ -348,8 +362,8 @@ Class Procs:
panel_open = FALSE
icon_state = icon_state_closed
to_chat(user, "You close the maintenance hatch of [src].")
- return 1
- return 0
+ return TRUE
+ return FALSE
/obj/machinery/proc/default_change_direction_wrench(mob/user, obj/item/I)
if(panel_open && I.tool_behaviour == TOOL_WRENCH)
@@ -401,7 +415,7 @@ Class Procs:
var/obj/item/circuitboard/machine/CB = locate(/obj/item/circuitboard/machine) in component_parts
var/P
if(W.works_from_distance)
- display_parts(user)
+ to_chat(user, display_parts(user))
for(var/obj/item/A in component_parts)
for(var/D in CB.req_components)
if(ispath(A.type, D))
@@ -429,34 +443,38 @@ Class Procs:
break
RefreshParts()
else
- display_parts(user)
+ to_chat(user, display_parts(user))
if(shouldplaysound)
W.play_rped_sound()
return TRUE
return FALSE
/obj/machinery/proc/display_parts(mob/user)
- to_chat(user, "It contains the following parts:")
+ . = list()
+ . += "It contains the following parts:"
for(var/obj/item/C in component_parts)
- to_chat(user, "[icon2html(C, user)] \A [C].")
+ . += "[icon2html(C, user)] \A [C]."
+ . = jointext(., "")
/obj/machinery/examine(mob/user)
- ..()
+ . = ..()
if(stat & BROKEN)
- to_chat(user, "It looks broken and non-functional.")
+ . += "It looks broken and non-functional."
if(!(resistance_flags & INDESTRUCTIBLE))
if(resistance_flags & ON_FIRE)
- to_chat(user, "It's on fire!")
+ . += "It's on fire!"
var/healthpercent = (obj_integrity/max_integrity) * 100
switch(healthpercent)
if(50 to 99)
- to_chat(user, "It looks slightly damaged.")
+ . += "It looks slightly damaged."
if(25 to 50)
- to_chat(user, "It appears heavily damaged.")
+ . += "It appears heavily damaged."
if(0 to 25)
- to_chat(user, "It's falling apart!")
+ . += "It's falling apart!"
if(user.research_scanner && component_parts)
- display_parts(user)
+ . += display_parts(user, TRUE)
+ if(inserted_scan_id || inserted_modify_id)
+ . += "Alt-click to eject the ID card."
//called on machinery construction (i.e from frame to machinery) but not on initialization
/obj/machinery/proc/on_construction()
@@ -481,6 +499,7 @@ Class Procs:
/obj/machinery/Exited(atom/movable/AM, atom/newloc)
. = ..()
if (AM == occupant)
+ SEND_SIGNAL(src, COMSIG_MACHINE_EJECT_OCCUPANT, occupant)
occupant = null
/obj/machinery/proc/adjust_item_drop_location(atom/movable/AM) // Adjust item drop location to a 3x3 grid inside the tile, returns slot id from 0 to 8
@@ -490,3 +509,73 @@ Class Procs:
. = . % 9
AM.pixel_x = -8 + ((.%3)*8)
AM.pixel_y = -8 + (round( . / 3)*8)
+
+/obj/machinery/proc/id_insert_scan(mob/user, obj/item/card/id/I)
+ I = user.get_active_held_item()
+ if(istype(I))
+ if(inserted_scan_id)
+ to_chat(user, "There's already an ID card in the console!")
+ return
+ if(!user.transferItemToLoc(I, src))
+ return
+ inserted_scan_id = I
+ user.visible_message("[user] inserts an ID card into the console.", \
+ "You insert the ID card into the console.")
+ playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, FALSE)
+ updateUsrDialog()
+
+/obj/machinery/proc/id_eject_scan(mob/user)
+ if(!inserted_scan_id)
+ to_chat(user, "There's no ID card in the console!")
+ return
+ if(inserted_scan_id)
+ inserted_scan_id.forceMove(drop_location())
+ if(!issilicon(user) && Adjacent(user))
+ user.put_in_hands(inserted_scan_id)
+ inserted_scan_id = null
+ user.visible_message("[user] gets an ID card from the console.", \
+ "You get the ID card from the console.")
+ playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, FALSE)
+ updateUsrDialog()
+
+/obj/machinery/proc/id_eject_modify(mob/user)
+ if(inserted_modify_id)
+ GLOB.data_core.manifest_modify(inserted_modify_id.registered_name, inserted_modify_id.assignment)
+ inserted_modify_id.update_label()
+ inserted_modify_id.forceMove(drop_location())
+ if(!issilicon(user) && Adjacent(user))
+ user.put_in_hands(inserted_modify_id)
+ user.visible_message("[user] gets an ID card from the console.", \
+ "You get the ID card from the console.")
+ playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, FALSE)
+ inserted_modify_id = null
+ region_access = null
+ head_subordinates = null
+ updateUsrDialog()
+
+/obj/machinery/proc/id_insert_modify(mob/user)
+ var/obj/item/card/id/I = user.get_active_held_item()
+ if(istype(I))
+ if(inserted_modify_id)
+ to_chat(user, "There's already an ID card in the console!")
+ return
+ if(!user.transferItemToLoc(I, src))
+ return
+ inserted_modify_id = I
+ user.visible_message("[user] inserts an ID card into the console.", \
+ "You insert the ID card into the console.")
+ playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, FALSE)
+ updateUsrDialog()
+
+/obj/machinery/AltClick(mob/user)
+ . = ..()
+ if(!user.canUseTopic(src, !issilicon(user)) || !is_operational())
+ return
+ if(inserted_modify_id)
+ id_eject_modify(user)
+ authenticated = FALSE
+ return
+ if(inserted_scan_id)
+ id_eject_scan(user)
+ authenticated = FALSE
+ return
diff --git a/code/game/machinery/announcement_system.dm b/code/game/machinery/announcement_system.dm
index 959bcfab4d..56227cdd53 100644
--- a/code/game/machinery/announcement_system.dm
+++ b/code/game/machinery/announcement_system.dm
@@ -96,10 +96,10 @@ GLOBAL_LIST_EMPTY(announcement_systems)
message = "The arrivals shuttle has been damaged. Docking for repairs..."
if(channels.len == 0)
- radio.talk_into(src, message, null, list(SPAN_ROBOT), get_default_language())
+ radio.talk_into(src, message, null)
else
for(var/channel in channels)
- radio.talk_into(src, message, channel, list(SPAN_ROBOT), get_default_language())
+ radio.talk_into(src, message, channel)
//config stuff
@@ -177,7 +177,9 @@ GLOBAL_LIST_EMPTY(announcement_systems)
act_up()
/obj/machinery/announcement_system/emag_act()
+ . = ..()
if(obj_flags & EMAGGED)
return
obj_flags |= EMAGGED
act_up()
+ return TRUE
diff --git a/code/game/machinery/bank_machine.dm b/code/game/machinery/bank_machine.dm
index c751ba007a..0b9ed6bb3f 100644
--- a/code/game/machinery/bank_machine.dm
+++ b/code/game/machinery/bank_machine.dm
@@ -6,7 +6,7 @@
var/siphoning = FALSE
var/next_warning = 0
var/obj/item/radio/radio
- var/radio_channel = "Common"
+ var/radio_channel = RADIO_CHANNEL_COMMON
var/minimum_time_between_warnings = 400
/obj/machinery/computer/bank_machine/Initialize()
@@ -49,12 +49,9 @@
if(next_warning < world.time && prob(15))
var/area/A = get_area(loc)
var/message = "Unauthorized credit withdrawal underway in [A.map_name]!!"
- radio.talk_into(src, message, radio_channel, get_spans())
+ radio.talk_into(src, message, radio_channel)
next_warning = world.time + minimum_time_between_warnings
-/obj/machinery/computer/bank_machine/get_spans()
- . = ..() | SPAN_ROBOT
-
/obj/machinery/computer/bank_machine/ui_interact(mob/user)
. = ..()
var/dat = "[station_name()] secure vault. Authorized personnel only. "
diff --git a/code/game/machinery/buttons.dm b/code/game/machinery/buttons.dm
index 879be046c9..968cbe7254 100644
--- a/code/game/machinery/buttons.dm
+++ b/code/game/machinery/buttons.dm
@@ -101,12 +101,14 @@
return ..()
/obj/machinery/button/emag_act(mob/user)
+ . = ..()
if(obj_flags & EMAGGED)
return
req_access = list()
req_one_access = list()
playsound(src, "sparks", 100, 1)
obj_flags |= EMAGGED
+ return TRUE
/obj/machinery/button/attack_ai(mob/user)
if(!panel_open)
diff --git a/code/game/machinery/cloning.dm b/code/game/machinery/cloning.dm
index 36a5c6ede4..89c5e56b71 100644
--- a/code/game/machinery/cloning.dm
+++ b/code/game/machinery/cloning.dm
@@ -6,7 +6,7 @@
#define CLONE_INITIAL_DAMAGE 150 //Clones in clonepods start with 150 cloneloss damage and 150 brainloss damage, thats just logical
#define MINIMUM_HEAL_LEVEL 40
-#define SPEAK(message) radio.talk_into(src, message, radio_channel, get_spans(), get_default_language())
+#define SPEAK(message) radio.talk_into(src, message, radio_channel)
/obj/machinery/clonepod
name = "cloning pod"
@@ -31,7 +31,7 @@
var/internal_radio = TRUE
var/obj/item/radio/radio
var/radio_key = /obj/item/encryptionkey/headset_med
- var/radio_channel = "Medical"
+ var/radio_channel = RADIO_CHANNEL_MEDICAL
var/obj/effect/countdown/clonepod/countdown
@@ -163,15 +163,8 @@
H.hardset_dna(ui, se, H.real_name, null, mrace, features)
- if(efficiency > 2)
- var/list/unclean_mutations = (GLOB.not_good_mutations|GLOB.bad_mutations)
- H.dna.remove_mutation_group(unclean_mutations)
- if(efficiency > 5 && prob(20))
- H.randmutvg()
- if(efficiency < 3 && prob(50))
- var/mob/M = H.randmutb()
- if(ismob(M))
- H = M
+ if(prob(50 - efficiency*10)) //Chance to give a bad mutation.
+ H.randmutb() //100% bad mutation. Can be cured with mutadone.
H.silent = 20 //Prevents an extreme edge case where clones could speak if they said something at exactly the right moment.
occupant = H
@@ -182,11 +175,12 @@
//Get the clone body ready
maim_clone(H)
- ADD_TRAIT(H, TRAIT_STABLEHEART, "cloning")
- ADD_TRAIT(H, TRAIT_EMOTEMUTE, "cloning")
- ADD_TRAIT(H, TRAIT_MUTE, "cloning")
- ADD_TRAIT(H, TRAIT_NOBREATH, "cloning")
- ADD_TRAIT(H, TRAIT_NOCRITDAMAGE, "cloning")
+ ADD_TRAIT(H, TRAIT_STABLEHEART, CLONING_POD_TRAIT)
+ ADD_TRAIT(H, TRAIT_STABLELIVER, CLONING_POD_TRAIT)
+ ADD_TRAIT(H, TRAIT_EMOTEMUTE, CLONING_POD_TRAIT)
+ ADD_TRAIT(H, TRAIT_MUTE, CLONING_POD_TRAIT)
+ ADD_TRAIT(H, TRAIT_NOBREATH, CLONING_POD_TRAIT)
+ ADD_TRAIT(H, TRAIT_NOCRITDAMAGE, CLONING_POD_TRAIT)
H.Unconscious(80)
clonemind.transfer_to(H)
@@ -248,13 +242,14 @@
var/obj/item/I = pick_n_take(unattached_flesh)
if(isorgan(I))
var/obj/item/organ/O = I
+ O.organ_flags &= ~ORGAN_FROZEN
O.Insert(mob_occupant)
else if(isbodypart(I))
var/obj/item/bodypart/BP = I
BP.attach_limb(mob_occupant)
//Premature clones may have brain damage.
- mob_occupant.adjustBrainLoss(-((speed_coeff / 2) * dmg_mult))
+ mob_occupant.adjustOrganLoss(ORGAN_SLOT_BRAIN, -((speed_coeff / 2) * dmg_mult))
use_power(7500) //This might need tweaking.
@@ -268,6 +263,7 @@
for(var/i in unattached_flesh)
if(isorgan(i))
var/obj/item/organ/O = i
+ O.organ_flags &= ~ORGAN_FROZEN
O.Insert(mob_occupant)
else if(isbodypart(i))
var/obj/item/bodypart/BP = i
@@ -327,10 +323,12 @@
return ..()
/obj/machinery/clonepod/emag_act(mob/user)
+ . = ..()
if(!occupant)
return
to_chat(user, "You corrupt the genetic compiler.")
malfunction()
+ return TRUE
//Put messages in the connected computer's temp var for display.
/obj/machinery/clonepod/proc/connected_message(message)
@@ -351,6 +349,9 @@
if(mess) //Clean that mess and dump those gibs!
for(var/obj/fl in unattached_flesh)
fl.forceMove(T)
+ if(istype(fl, /obj/item/organ))
+ var/obj/item/organ/O = fl
+ O.organ_flags &= ~ORGAN_FROZEN
unattached_flesh.Cut()
mess = FALSE
new /obj/effect/gibspawner/generic(get_turf(src))
@@ -361,11 +362,12 @@
if(!mob_occupant)
return
- REMOVE_TRAIT(mob_occupant, TRAIT_STABLEHEART, "cloning")
- REMOVE_TRAIT(mob_occupant, TRAIT_EMOTEMUTE, "cloning")
- REMOVE_TRAIT(mob_occupant, TRAIT_MUTE, "cloning")
- REMOVE_TRAIT(mob_occupant, TRAIT_NOCRITDAMAGE, "cloning")
- REMOVE_TRAIT(mob_occupant, TRAIT_NOBREATH, "cloning")
+ REMOVE_TRAIT(mob_occupant, TRAIT_STABLEHEART, CLONING_POD_TRAIT)
+ REMOVE_TRAIT(mob_occupant, TRAIT_STABLELIVER, CLONING_POD_TRAIT)
+ REMOVE_TRAIT(mob_occupant, TRAIT_EMOTEMUTE, CLONING_POD_TRAIT)
+ REMOVE_TRAIT(mob_occupant, TRAIT_MUTE, CLONING_POD_TRAIT)
+ REMOVE_TRAIT(mob_occupant, TRAIT_NOCRITDAMAGE, CLONING_POD_TRAIT)
+ REMOVE_TRAIT(mob_occupant, TRAIT_NOBREATH, CLONING_POD_TRAIT)
if(grab_ghost_when == CLONER_MATURE_CLONE)
mob_occupant.grab_ghost()
@@ -447,7 +449,7 @@
unattached_flesh.Cut()
H.setCloneLoss(CLONE_INITIAL_DAMAGE) //Yeah, clones start with very low health, not with random, because why would they start with random health
- H.setBrainLoss(CLONE_INITIAL_DAMAGE)
+ //H.setOrganLoss(ORGAN_SLOT_BRAIN, CLONE_INITIAL_DAMAGE)
// In addition to being cellularly damaged and having barely any
// brain function, they also have no limbs or internal organs.
@@ -463,8 +465,9 @@
for(var/o in H.internal_organs)
var/obj/item/organ/organ = o
- if(!istype(organ) || organ.vital)
+ if(!istype(organ) || (organ.organ_flags & ORGAN_VITAL))
continue
+ organ.organ_flags |= ORGAN_FROZEN
organ.Remove(H, special=TRUE)
organ.forceMove(src)
unattached_flesh += organ
diff --git a/code/game/machinery/computer/Operating.dm b/code/game/machinery/computer/Operating.dm
index 73fdcd5e8b..c317cbba0d 100644
--- a/code/game/machinery/computer/Operating.dm
+++ b/code/game/machinery/computer/Operating.dm
@@ -44,7 +44,7 @@
table.computer = src
break
-/obj/machinery/computer/operating/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = 0, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state)
+/obj/machinery/computer/operating/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = 0, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.not_incapacitated_state)
ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
if(!ui)
ui = new(user, src, ui_key, "operating_computer", name, 350, 470, master_ui, state)
@@ -125,4 +125,4 @@
. = TRUE
#undef MENU_OPERATION
-#undef MENU_SURGERIES
\ No newline at end of file
+#undef MENU_SURGERIES
diff --git a/code/game/machinery/computer/apc_control.dm b/code/game/machinery/computer/apc_control.dm
index e51d623c2f..6d505c8f95 100644
--- a/code/game/machinery/computer/apc_control.dm
+++ b/code/game/machinery/computer/apc_control.dm
@@ -11,7 +11,6 @@
var/list/result_filters //For sorting the results
var/checking_logs = 0
var/list/logs
- var/authenticated = 0
var/auth_id = "\[NULL\]"
/obj/machinery/computer/apc_control/Initialize()
@@ -185,15 +184,19 @@
ui_interact(usr) //Refresh the UI after a filter changes
/obj/machinery/computer/apc_control/emag_act(mob/user)
+ . = ..()
if(!authenticated)
to_chat(user, "You bypass [src]'s access requirements using your emag.")
authenticated = TRUE
log_activity("logged in")
- else if(!(obj_flags & EMAGGED))
+ else
+ if(obj_flags & EMAGGED)
+ return
user.visible_message("You emag [src], disabling precise logging and allowing you to clear logs.")
log_game("[key_name(user)] emagged [src] at [AREACOORD(src)], disabling operator tracking.")
obj_flags |= EMAGGED
- playsound(src, "sparks", 50, 1)
+ playsound(src, "sparks", 50, 1)
+ return TRUE
/obj/machinery/computer/apc_control/proc/log_activity(log_text)
var/op_string = operator && !(obj_flags & EMAGGED) ? operator : "\[NULL OPERATOR\]"
diff --git a/code/game/machinery/computer/arcade.dm b/code/game/machinery/computer/arcade.dm
index 8d0cfb95e8..4bce450359 100644
--- a/code/game/machinery/computer/arcade.dm
+++ b/code/game/machinery/computer/arcade.dm
@@ -45,8 +45,8 @@
/obj/item/gun/ballistic/automatic/toy/pistol/unrestricted = ARCADE_WEIGHT_TRICK,
/obj/item/hot_potato/harmless/toy = ARCADE_WEIGHT_RARE,
/obj/item/twohanded/dualsaber/toy = ARCADE_WEIGHT_RARE,
- /obj/item/twohanded/hypereutactic/toy = ARCADE_WEIGHT_RARE,
- /obj/item/twohanded/hypereutactic/toy/rainbow = ARCADE_WEIGHT_RARE,
+ /obj/item/twohanded/dualsaber/hypereutactic/toy = ARCADE_WEIGHT_RARE,
+ /obj/item/twohanded/dualsaber/hypereutactic/toy/rainbow = ARCADE_WEIGHT_RARE,
/obj/item/storage/box/snappops = ARCADE_WEIGHT_TRICK,
/obj/item/clothing/under/syndicate/tacticool = ARCADE_WEIGHT_TRICK,
@@ -57,6 +57,7 @@
/obj/item/stack/tile/fakespace/loaded = ARCADE_WEIGHT_TRICK,
/obj/item/stack/tile/fakepit/loaded = ARCADE_WEIGHT_TRICK,
/obj/item/restraints/handcuffs/fake = ARCADE_WEIGHT_TRICK,
+ /obj/item/clothing/gloves/rapid/hug = ARCADE_WEIGHT_TRICK,
/obj/item/grenade/chem_grenade/glitter/pink = ARCADE_WEIGHT_TRICK,
/obj/item/grenade/chem_grenade/glitter/blue = ARCADE_WEIGHT_TRICK,
diff --git a/code/game/machinery/computer/arcade/battle.dm b/code/game/machinery/computer/arcade/battle.dm
index ded9cf95f6..b906b1afb5 100644
--- a/code/game/machinery/computer/arcade/battle.dm
+++ b/code/game/machinery/computer/arcade/battle.dm
@@ -186,6 +186,7 @@
/obj/machinery/computer/arcade/battle/emag_act(mob/user)
+ . = ..()
if(obj_flags & EMAGGED)
return
to_chat(user, "A mesmerizing Rhumba beat starts playing from the arcade machine's speakers!")
@@ -202,5 +203,5 @@
enemy_name = "Cuban Pete"
name = "Outbomb Cuban Pete"
-
updateUsrDialog()
+ return TRUE
\ No newline at end of file
diff --git a/code/game/machinery/computer/arcade/minesweeper.dm b/code/game/machinery/computer/arcade/minesweeper.dm
index fe69860f76..641ef1c9cd 100644
--- a/code/game/machinery/computer/arcade/minesweeper.dm
+++ b/code/game/machinery/computer/arcade/minesweeper.dm
@@ -250,11 +250,11 @@
itemname = "a syndicate bomb beacon"
new /obj/item/sbeacondrop/bomb(loc)
if(2)
- itemname = "a grenade launcher"
- new /obj/item/gun/ballistic/revolver/grenadelauncher/unrestricted(loc)
- new /obj/item/ammo_casing/a40mm(loc)
- new /obj/item/ammo_casing/a40mm(loc)
- new /obj/item/ammo_casing/a40mm(loc)
+ itemname = "a rocket launcher"
+ new /obj/item/gun/ballistic/rocketlauncher/unrestricted(loc)
+ new /obj/item/ammo_casing/caseless/rocket(loc)
+ new /obj/item/ammo_casing/caseless/rocket(loc)
+ new /obj/item/ammo_casing/caseless/rocket(loc)
if(3)
itemname = "two bags of c4"
new /obj/item/storage/backpack/duffelbag/syndie/c4(loc)
@@ -284,6 +284,7 @@
return
/obj/machinery/computer/arcade/minesweeper/emag_act(mob/user)
+ . = ..()
if(CHECK_BITFIELD(obj_flags, EMAGGED))
return
desc = "An arcade machine that generates grids. It's clunking and sparking everywhere, almost as if threatening to explode at any moment!"
@@ -298,6 +299,7 @@
to_chat(user, "The machine buzzes and sparks... the game has been reset!")
playsound(user, 'sound/machines/buzz-sigh.ogg', 100, 0, extrarange = 3, falloff = 10) //Loud buzz
game_status = MINESWEEPER_GAME_MAIN_MENU
+ return TRUE
/obj/machinery/computer/arcade/minesweeper/proc/custom_generation(mob/user)
playsound(loc, 'sound/arcade/minesweeper_menuselect.ogg', 50, 0, extrarange = -3, falloff = 10) //Entered into the menu so ping sound
diff --git a/code/game/machinery/computer/arcade/orion_trail.dm b/code/game/machinery/computer/arcade/orion_trail.dm
index 12941dea35..023b1048ce 100644
--- a/code/game/machinery/computer/arcade/orion_trail.dm
+++ b/code/game/machinery/computer/arcade/orion_trail.dm
@@ -736,6 +736,7 @@
desc = "Learn how our ancestors got to Orion, and have fun in the process!"
/obj/machinery/computer/arcade/orion_trail/emag_act(mob/user)
+ . = ..()
if(obj_flags & EMAGGED)
return
to_chat(user, "You override the cheat code menu and skip to Cheat #[rand(1, 50)]: Realism Mode.")
@@ -743,6 +744,7 @@
desc = "Learn how our ancestors got to Orion, and try not to die in the process!"
newgame()
obj_flags |= EMAGGED
+ return TRUE
/mob/living/simple_animal/hostile/syndicate/ranged/smg/orion
name = "spaceport security"
diff --git a/code/game/machinery/computer/camera_advanced.dm b/code/game/machinery/computer/camera_advanced.dm
index 86fcefb340..43582e39bf 100644
--- a/code/game/machinery/computer/camera_advanced.dm
+++ b/code/game/machinery/computer/camera_advanced.dm
@@ -361,14 +361,16 @@
return
button_icon_state = "warp_down"
owner.update_action_buttons()
+ QDEL_NULL(warping)
+ if(!do_teleport(user, T, channel = TELEPORT_CHANNEL_CULT, forced = TRUE))
+ to_chat(user, "Warp Failed. Something deflected our attempt to warp to [AR].")
+ return
T.visible_message("[user] warps in!")
playsound(user, 'sound/magic/magic_missile.ogg', 50, TRUE)
playsound(T, 'sound/magic/magic_missile.ogg', 50, TRUE)
- user.forceMove(get_turf(T))
user.setDir(SOUTH)
flash_color(user, flash_color = "#AF0AAF", flash_time = 5)
R.remove_eye_control(user)
- QDEL_NULL(warping)
/datum/action/innate/servant_warp/proc/is_canceled()
return !cancel
diff --git a/code/game/machinery/computer/card.dm b/code/game/machinery/computer/card.dm
index 09bf401e12..886851a6a8 100644
--- a/code/game/machinery/computer/card.dm
+++ b/code/game/machinery/computer/card.dm
@@ -13,11 +13,8 @@ GLOBAL_VAR_INIT(time_last_changed_position, 0)
circuit = /obj/item/circuitboard/computer/card
var/obj/item/card/id/scan = null
var/obj/item/card/id/modify = null
- var/authenticated = 0
var/mode = 0
var/printing = null
- var/list/region_access = null
- var/list/head_subordinates = null
var/target_dept = 0 //Which department this computer has access to. 0=all departments
//Cooldown for closing positions in seconds
@@ -377,7 +374,6 @@ GLOBAL_VAR_INIT(time_last_changed_position, 0)
else
if((ACCESS_HOP in scan.access) && ((target_dept==1) || !target_dept))
region_access |= 1
- region_access |= 6
get_subordinates("Head of Personnel")
if((ACCESS_HOS in scan.access) && ((target_dept==2) || !target_dept))
region_access |= 2
@@ -391,6 +387,9 @@ GLOBAL_VAR_INIT(time_last_changed_position, 0)
if((ACCESS_CE in scan.access) && ((target_dept==5) || !target_dept))
region_access |= 5
get_subordinates("Chief Engineer")
+ if((ACCESS_QM in scan.access) && ((target_dept==6) || !target_dept))
+ region_access |= 6
+ get_subordinates("Quartermaster")
if(region_access)
authenticated = 1
else if ((!( authenticated ) && issilicon(usr)) && (!modify))
@@ -610,7 +609,7 @@ GLOBAL_VAR_INIT(time_last_changed_position, 0)
typed_circuit.target_dept = target_dept
else
target_dept = typed_circuit.target_dept
- var/list/dept_list = list("general","security","medical","science","engineering")
+ var/list/dept_list = list("civilian","security","medical","science","engineering","cargo")
name = "[dept_list[target_dept]] department console"
/obj/machinery/computer/card/minor/hos
@@ -634,3 +633,9 @@ GLOBAL_VAR_INIT(time_last_changed_position, 0)
icon_screen = "idce"
light_color = LIGHT_COLOR_YELLOW
+
+/obj/machinery/computer/card/minor/qm
+ target_dept = 6
+ icon_screen = "idqm"
+
+ light_color = LIGHT_COLOR_ORANGE
\ No newline at end of file
diff --git a/code/game/machinery/computer/cloning.dm b/code/game/machinery/computer/cloning.dm
index 39d5c3d56f..5cbd2d8975 100644
--- a/code/game/machinery/computer/cloning.dm
+++ b/code/game/machinery/computer/cloning.dm
@@ -1,4 +1,4 @@
-#define AUTOCLONING_MINIMAL_LEVEL 3
+#define AUTOCLONING_MINIMAL_LEVEL 4
/obj/machinery/computer/cloning
name = "cloning console"
@@ -13,17 +13,20 @@
var/scantemp_ckey
var/scantemp = "Ready to Scan"
var/menu = 1 //Which menu screen to display
- var/list/records = list()
var/datum/data/record/active_record = null
var/obj/item/disk/data/diskette = null //Mostly so the geneticist can steal everything.
var/loading = 0 // Nice loading text
var/autoprocess = 0
+ var/list/records = list()
light_color = LIGHT_COLOR_BLUE
/obj/machinery/computer/cloning/Initialize()
. = ..()
updatemodules(TRUE)
+ var/obj/item/circuitboard/computer/cloning/board = circuit
+ records = board.records
+
/obj/machinery/computer/cloning/Destroy()
if(pods)
@@ -61,9 +64,6 @@
if(!(scanner && LAZYLEN(pods) && autoprocess))
return
- if(scanner.occupant && scanner.scan_level > 2)
- scan_occupant(scanner.occupant)
-
for(var/datum/data/record/R in records)
var/obj/machinery/clonepod/pod = GetAvailableEfficientPod(R.fields["mind"])
@@ -159,11 +159,11 @@
if(scanner && HasEfficientPod() && scanner.scan_level >= AUTOCLONING_MINIMAL_LEVEL)
if(!autoprocess)
- dat += "Autoprocess"
+ dat += "Autoclone"
else
- dat += "Stop autoprocess"
+ dat += "Stop autoclone"
else
- dat += "Autoprocess"
+ dat += "Autoclone"
dat += "
Cloning Pod Status
"
dat += "
[temp]
"
@@ -228,7 +228,7 @@
dat += "
[src.active_record.fields["name"]]
"
dat += "Scan ID [src.active_record.fields["id"]] Clone "
- var/obj/item/implant/health/H = locate(src.active_record.fields["imp"])
+ var/obj/item/implant/health/H = locate(active_record.fields["imp"])
if ((H) && (istype(H)))
dat += "Health Implant Data: [H.sensehealth()]
"
+ if(!frozen_crew.len)
+ dat += "There has been no storage usage at this terminal. "
+ else
+ for(var/person in frozen_crew)
+ dat += "[person] "
+ dat += ""
+ if(3)
+ dat += "<< Back
"
+ dat += "
Recently stored objects
"
+ if(!frozen_items.len)
+ dat += "There has been no storage usage at this terminal. "
+ else
+ for(var/obj/item/I in frozen_items)
+ dat += "[I.name] "
+ dat += ""
+
+ var/datum/browser/popup = new(user, "cryopod_console", "Cryogenic System Control")
+ popup.set_content(dat)
+ popup.set_title_image(user.browse_rsc_icon(src.icon, src.icon_state))
+ popup.open()
/obj/machinery/computer/cryopod/Topic(href, href_list)
if(..())
- return 1
+ return TRUE
var/mob/user = usr
add_fingerprint(user)
- if(href_list["log"])
-
- var/dat = "Recently stored [storage_type] "
- for(var/person in frozen_crew)
- dat += "[person] "
- dat += ""
-
- user << browse(dat, "window=cryolog")
-
- if(href_list["view"])
- if(!allow_items) return
-
- var/dat = "Recently stored objects "
- for(var/obj/item/I in frozen_items)
- dat += "[I.name] "
- dat += ""
-
- user << browse(dat, "window=cryoitems")
-
- else if(href_list["item"])
+ if(href_list["item"])
if(!allowed(user))
to_chat(user, "Access Denied.")
+ playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0)
+ updateUsrDialog()
return
if(!allow_items) return
if(frozen_items.len == 0)
to_chat(user, "There is nothing to recover from storage.")
+ playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0)
+ updateUsrDialog()
return
var/obj/item/I = input(user, "Please choose which object to retrieve.","Object recovery",null) as null|anything in frozen_items
+ playsound(src, "terminal_type", 25, 0)
if(!I)
return
if(!(I in frozen_items))
to_chat(user, "\The [I] is no longer in storage.")
+ playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0)
+ updateUsrDialog()
return
visible_message("The console beeps happily as it disgorges \the [I].")
+ playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, 0)
- I.forceMove(get_turf(src))
+ I.forceMove(drop_location())
+ if(user && Adjacent(user) && !issiliconoradminghost(user))
+ user.put_in_hands(I)
frozen_items -= I
+ updateUsrDialog()
else if(href_list["allitems"])
+ playsound(src, "terminal_type", 25, 0)
if(!allowed(user))
to_chat(user, "Access Denied.")
+ playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0)
+ updateUsrDialog()
return
if(!allow_items) return
if(frozen_items.len == 0)
to_chat(user, "There is nothing to recover from storage.")
+ playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0)
return
visible_message("The console beeps happily as it disgorges the desired objects.")
+ playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, 0)
for(var/obj/item/I in frozen_items)
- I.forceMove(get_turf(src))
+ I.forceMove(drop_location())
frozen_items -= I
+ updateUsrDialog()
+ else if (href_list["menu"])
+ src.menu = text2num(href_list["menu"])
+ playsound(src, "terminal_type", 25, 0)
+ updateUsrDialog()
+
+ ui_interact(usr)
updateUsrDialog()
+ return
/obj/item/circuitboard/cryopodcontrol
name = "Circuit board (Cryogenic Oversight Console)"
build_path = "/obj/machinery/computer/cryopod"
+/obj/machinery/computer/cryopod/contents_explosion()
+ return
//Cryopods themselves.
/obj/machinery/cryopod
@@ -176,16 +217,17 @@
/obj/item/gun/energy/laser/cyborg
)
-/obj/machinery/cryopod/Initialize()
+/obj/machinery/cryopod/Initialize(mapload)
. = ..()
update_icon()
- find_control_computer(TRUE)
+ find_control_computer(mapload)
/obj/machinery/cryopod/proc/find_control_computer(urgent = FALSE)
for(var/obj/machinery/computer/cryopod/C in get_area(src))
control_computer = C
if(C)
return C
+ break
// Don't send messages unless we *need* the computer, and less than five minutes have passed since last time we messaged
if(!control_computer && urgent && last_no_computer_message + 5*60*10 < world.time)
@@ -193,7 +235,7 @@
message_admins("Cryopod in [get_area(src)] could not find control computer!")
last_no_computer_message = world.time
- return null
+ return control_computer != null
/obj/machinery/cryopod/close_machine(mob/user)
if(!control_computer)
@@ -242,10 +284,75 @@
despawn_occupant()
+#define CRYO_DESTROY 0
+#define CRYO_PRESERVE 1
+#define CRYO_OBJECTIVE 2
+
+/obj/machinery/cryopod/proc/should_preserve_item(obj/item/I)
+ for(var/datum/objective_item/steal/T in control_computer.theft_cache)
+ if(istype(I, T.targetitem) && T.check_special_completion(I))
+ return CRYO_OBJECTIVE
+ for(var/T in preserve_items)
+ if(istype(I, T) && !(I.type in do_not_preserve_items))
+ return CRYO_PRESERVE
+ return CRYO_DESTROY
+
// This function can not be undone; do not call this unless you are sure
/obj/machinery/cryopod/proc/despawn_occupant()
+ if(!control_computer)
+ find_control_computer()
+
var/mob/living/mob_occupant = occupant
+ //Handle Borg stuff first
+ if(iscyborg(mob_occupant))
+ var/mob/living/silicon/robot/R = mob_occupant
+ if(!istype(R)) return ..()
+
+ R.contents -= R.mmi
+ qdel(R.mmi)
+ for(var/obj/item/I in R.module) // the tools the borg has; metal, glass, guns etc
+ for(var/obj/item/O in I) // the things inside the tools, if anything; mainly for janiborg trash bags
+ if(should_preserve_item(O) != CRYO_DESTROY) // Preserve important things inside the item
+ continue
+ O.forceMove(src)
+ R.module.remove_module(I, TRUE) //delete the module itself so it doesn't transfer over.
+
+ //Drop all items into the pod.
+ for(var/obj/item/I in mob_occupant)
+ mob_occupant.doUnEquip(I)
+ I.forceMove(src)
+
+ if(I.contents.len) //Make sure we catch anything not handled by qdel() on the items.
+ if(should_preserve_item(I) != CRYO_DESTROY) // Don't remove the contents of things that need preservation
+ continue
+ for(var/obj/item/O in I.contents)
+ if(istype(O, /obj/item/tank)) //Stop eating pockets, you fuck!
+ continue
+ O.forceMove(src)
+
+ //Delete all items not on the preservation list.
+ var/list/items = contents
+ items -= mob_occupant // Don't delete the occupant
+
+ for(var/obj/item/I in items)
+ if(istype(I, /obj/item/pda))
+ var/obj/item/pda/P = I
+ QDEL_NULL(P.id)
+ qdel(P)
+ continue
+
+ var/preserve = should_preserve_item(I)
+ if(preserve == CRYO_DESTROY)
+ qdel(I)
+ else if(control_computer && control_computer.allow_items)
+ control_computer.frozen_items += I
+ if(preserve == CRYO_OBJECTIVE)
+ control_computer.objective_items += I
+ I.loc = null
+ else
+ I.forceMove(loc)
+
//Update any existing objectives involving this mob.
for(var/datum/objective/O in GLOB.objectives)
// We don't want revs to get objectives that aren't for heads of staff. Letting
@@ -302,30 +409,6 @@
announcer.announce("CRYOSTORAGE", mob_occupant.real_name, announce_rank, list())
visible_message("\The [src] hums and hisses as it moves [mob_occupant.real_name] into storage.")
-
- for(var/obj/item/W in mob_occupant.GetAllContents())
- if(W.loc.loc && (( W.loc.loc == loc ) || (W.loc.loc == control_computer)))
- continue//means we already moved whatever this thing was in
- //I'm a professional, okay
- for(var/T in preserve_items)
- if(istype(W, T))
- if(control_computer && control_computer.allow_items)
- control_computer.frozen_items += W
- mob_occupant.transferItemToLoc(W, control_computer, TRUE)
- else
- mob_occupant.transferItemToLoc(W, loc, TRUE)
-
- for(var/obj/item/W in mob_occupant.GetAllContents())
- qdel(W)//because we moved all items to preserve away
- //and yes, this totally deletes their bodyparts one by one, I just couldn't bother
-
- if(iscyborg(mob_occupant))
- var/mob/living/silicon/robot/R = occupant
- if(!istype(R)) return ..()
-
- R.contents -= R.mmi
- qdel(R.mmi)
-
// Ghost and delete the mob.
if(!mob_occupant.get_ghost(1))
mob_occupant.ghostize(0) // Players who cryo out may not re-enter the round
@@ -334,6 +417,10 @@
open_machine()
name = initial(name)
+#undef CRYO_DESTROY
+#undef CRYO_PRESERVE
+#undef CRYO_OBJECTIVE
+
/obj/machinery/cryopod/MouseDrop_T(mob/living/target, mob/user)
if(!istype(target) || user.incapacitated() || !target.Adjacent(user) || !Adjacent(user) || !ismob(target) || (!ishuman(user) && !iscyborg(user)) || !istype(user.loc, /turf) || target.buckled)
return
@@ -358,7 +445,7 @@
var/generic_plsnoleave_message = " Please adminhelp before leaving the round, even if there are no administrators online!"
- if(target == user && world.time - target.client.cryo_warned > 5 * 600)//if we haven't warned them in the last 5 minutes
+ if(target == user && world.time - target.client.cryo_warned > 5 MINUTES)//if we haven't warned them in the last 5 minutes
var/caught = FALSE
if(target.mind.assigned_role in GLOB.command_positions)
alert("You're a Head of Staff![generic_plsnoleave_message] Be sure to put your locker items back into your locker!")
@@ -366,13 +453,13 @@
if(iscultist(target) || is_servant_of_ratvar(target))
to_chat(target, "You're a Cultist![generic_plsnoleave_message]")
caught = TRUE
- if(istype(SSticker.mode, /datum/antagonist/blob))
- if(target.mind in GLOB.overminds)
- alert("You're a Blob![generic_plsnoleave_message]")
- caught = TRUE
if(is_devil(target))
alert("You're a Devil![generic_plsnoleave_message]")
caught = TRUE
+ if(istype(SSticker.mode, /datum/antagonist/gang))
+ if(target.mind.has_antag_datum(/datum/antagonist/gang))
+ alert("You're a Gangster![generic_plsnoleave_message]")
+ caught = TRUE
if(istype(SSticker.mode, /datum/antagonist/rev))
if(target.mind.has_antag_datum(/datum/antagonist/rev/head))
alert("You're a Head Revolutionary![generic_plsnoleave_message]")
@@ -385,8 +472,9 @@
target.client.cryo_warned = world.time
return
- if(!Adjacent(user))
+ if(!target || user.incapacitated() || !target.Adjacent(user) || !Adjacent(user) || (!ishuman(user) && !iscyborg(user)) || !istype(user.loc, /turf) || target.buckled)
return
+ //rerun the checks in case of shenanigans
if(target == user)
visible_message("[user] starts climbing into the cryo pod.")
@@ -396,7 +484,6 @@
if(occupant)
to_chat(user, "\The [src] is in use.")
return
-
close_machine(target)
to_chat(target, "If you ghost, log out or close your client now, your character will shortly be permanently removed from the round.")
@@ -407,4 +494,4 @@
//Attacks/effects.
/obj/machinery/cryopod/blob_act()
- return //Sorta gamey, but we don't really want these to be destroyed.
+ return //Sorta gamey, but we don't really want these to be destroyed.
\ No newline at end of file
diff --git a/code/game/machinery/defibrillator_mount.dm b/code/game/machinery/defibrillator_mount.dm
index 7b7c8a3411..4210435f33 100644
--- a/code/game/machinery/defibrillator_mount.dm
+++ b/code/game/machinery/defibrillator_mount.dm
@@ -69,7 +69,7 @@
if(defib)
to_chat(user, "There's already a defibrillator in [src]!")
return
- if(I.item_flags & NODROP || !user.transferItemToLoc(I, src))
+ if(HAS_TRAIT(I, TRAIT_NODROP) || !user.transferItemToLoc(I, src))
to_chat(user, "[I] is stuck to your hand!")
return
user.visible_message("[user] hooks up [I] to [src]!", \
diff --git a/code/game/machinery/dna_scanner.dm b/code/game/machinery/dna_scanner.dm
index 7895fb8c9f..c9e1e7195b 100644
--- a/code/game/machinery/dna_scanner.dm
+++ b/code/game/machinery/dna_scanner.dm
@@ -89,11 +89,11 @@
return C
return null
-/obj/machinery/dna_scannernew/close_machine(mob/living/carbon/user)
+/obj/machinery/dna_scannernew/close_machine(atom/movable/target)
if(!state_open)
return FALSE
- ..(user)
+ ..(target)
// search for ghosts, if the corpse is empty and the scanner is connected to a cloner
var/mob/living/mob_occupant = get_mob_or_brainmob(occupant)
@@ -111,7 +111,7 @@
return TRUE
/obj/machinery/dna_scannernew/open_machine()
- if(state_open)
+ if(state_open || panel_open)
return FALSE
..()
@@ -126,23 +126,48 @@
return
open_machine()
-/obj/machinery/dna_scannernew/attackby(obj/item/I, mob/user, params)
-
- if(!occupant && default_deconstruction_screwdriver(user, icon_state, icon_state, I))//sent icon_state is irrelevant...
- update_icon()//..since we're updating the icon here, since the scanner can be unpowered when opened/closed
+/obj/machinery/dna_scannernew/screwdriver_act(mob/living/user, obj/item/I)
+ . = TRUE
+ if(..())
return
+ if(occupant)
+ to_chat(user, "[src] is currently occupied!")
+ return
+ if(state_open)
+ to_chat(user, "[src] must be closed to [panel_open ? "close" : "open"] its maintenance hatch!")
+ return
+ if(default_deconstruction_screwdriver(user, icon_state, icon_state, I)) //sent icon_state is irrelevant...
+ update_icon() //..since we're updating the icon here, since the scanner can be unpowered when opened/closed
+ return
+ return FALSE
+/obj/machinery/dna_scannernew/wrench_act(mob/living/user, obj/item/I)
+ . = ..()
+ if(default_change_direction_wrench(user, I))
+ return TRUE
+
+/obj/machinery/dna_scannernew/crowbar_act(mob/living/user, obj/item/I)
+ . = ..()
if(default_pry_open(I))
- return
-
+ return TRUE
if(default_deconstruction_crowbar(I))
- return
+ return TRUE
- return ..()
+/obj/machinery/dna_scannernew/default_pry_open(obj/item/I) //wew
+ . = !(state_open || panel_open || (flags_1 & NODECONSTRUCT_1)) && I.tool_behaviour == TOOL_CROWBAR
+ if(.)
+ I.play_tool_sound(src, 50)
+ visible_message("[usr] pries open [src].", "You pry open [src].")
+ open_machine()
/obj/machinery/dna_scannernew/interact(mob/user)
toggle_open(user)
+/obj/machinery/dna_scannernew/AltClick(mob/user)
+ if(!user.canUseTopic(src, !issilicon(user)))
+ return
+ interact(user)
+
/obj/machinery/dna_scannernew/MouseDrop_T(mob/target, mob/user)
if(user.stat || user.lying || !Adjacent(user) || !user.Adjacent(target) || !iscarbon(target) || !user.IsAdvancedToolUser())
return
diff --git a/code/game/machinery/doors/airlock.dm b/code/game/machinery/doors/airlock.dm
index 1081cb5fa7..365d19b826 100644
--- a/code/game/machinery/doors/airlock.dm
+++ b/code/game/machinery/doors/airlock.dm
@@ -1280,20 +1280,23 @@
return !density || (check_access(ID) && !locked && hasPower())
/obj/machinery/door/airlock/emag_act(mob/user)
- if(!operating && density && hasPower() && !(obj_flags & EMAGGED))
- operating = TRUE
- update_icon(AIRLOCK_EMAG, 1)
- sleep(6)
- if(QDELETED(src))
- return
- operating = FALSE
- if(!open())
- update_icon(AIRLOCK_CLOSED, 1)
- obj_flags |= EMAGGED
- lights = FALSE
- locked = TRUE
- loseMainPower()
- loseBackupPower()
+ . = ..()
+ if(operating || !density || !hasPower() || obj_flags & EMAGGED)
+ return
+ operating = TRUE
+ update_icon(AIRLOCK_EMAG, 1)
+ addtimer(CALLBACK(src, .proc/open_sesame), 6)
+ return TRUE
+
+/obj/machinery/door/airlock/proc/open_sesame()
+ operating = FALSE
+ if(!open())
+ update_icon(AIRLOCK_CLOSED, 1)
+ obj_flags |= EMAGGED
+ lights = FALSE
+ locked = TRUE
+ loseMainPower()
+ loseBackupPower()
/obj/machinery/door/airlock/attack_alien(mob/living/carbon/alien/humanoid/user)
add_fingerprint(user)
diff --git a/code/game/machinery/doors/unpowered.dm b/code/game/machinery/doors/unpowered.dm
index 2e7e7d5cba..702f700617 100644
--- a/code/game/machinery/doors/unpowered.dm
+++ b/code/game/machinery/doors/unpowered.dm
@@ -13,9 +13,6 @@
else
return ..()
-/obj/machinery/door/unpowered/emag_act()
- return
-
/obj/machinery/door/unpowered/shuttle
icon = 'icons/turf/shuttle.dmi'
name = "door"
diff --git a/code/game/machinery/doors/windowdoor.dm b/code/game/machinery/doors/windowdoor.dm
index 97ab664b85..933bb4f42a 100644
--- a/code/game/machinery/doors/windowdoor.dm
+++ b/code/game/machinery/doors/windowdoor.dm
@@ -206,15 +206,20 @@
..()
/obj/machinery/door/window/emag_act(mob/user)
- if(!operating && density && !(obj_flags & EMAGGED))
- obj_flags |= EMAGGED
- operating = TRUE
- flick("[src.base_state]spark", src)
- playsound(src, "sparks", 75, 1)
- sleep(6)
- operating = FALSE
- desc += " Its access panel is smoking slightly."
- open(2)
+ . = ..()
+ if(operating || !density || obj_flags & EMAGGED)
+ return
+ obj_flags |= EMAGGED
+ operating = TRUE
+ flick("[src.base_state]spark", src)
+ playsound(src, "sparks", 75, 1)
+ addtimer(CALLBACK(src, .proc/open_windows_me), 6)
+ return TRUE
+
+/obj/machinery/door/window/proc/open_windows_me()
+ operating = FALSE
+ desc += " Its access panel is smoking slightly."
+ open(2)
/obj/machinery/door/window/attackby(obj/item/I, mob/living/user, params)
diff --git a/code/game/machinery/embedded_controller/access_controller.dm b/code/game/machinery/embedded_controller/access_controller.dm
index 70ce3d36ae..3b9322d207 100644
--- a/code/game/machinery/embedded_controller/access_controller.dm
+++ b/code/game/machinery/embedded_controller/access_controller.dm
@@ -26,6 +26,7 @@
findObjsByTag()
/obj/machinery/doorButtons/emag_act(mob/user)
+ . = ..()
if(obj_flags & EMAGGED)
return
obj_flags |= EMAGGED
@@ -33,6 +34,7 @@
req_one_access = list()
playsound(src, "sparks", 100, 1)
to_chat(user, "You short out the access controller.")
+ return TRUE
/obj/machinery/doorButtons/proc/removeMe()
diff --git a/code/game/machinery/exp_cloner.dm b/code/game/machinery/exp_cloner.dm
index e8364d2271..25e5948b51 100644
--- a/code/game/machinery/exp_cloner.dm
+++ b/code/game/machinery/exp_cloner.dm
@@ -42,17 +42,18 @@
icon_state = "pod_1"
//Get the clone body ready
maim_clone(H)
- ADD_TRAIT(H, TRAIT_STABLEHEART, "cloning")
- ADD_TRAIT(H, TRAIT_EMOTEMUTE, "cloning")
- ADD_TRAIT(H, TRAIT_MUTE, "cloning")
- ADD_TRAIT(H, TRAIT_NOBREATH, "cloning")
- ADD_TRAIT(H, TRAIT_NOCRITDAMAGE, "cloning")
+ ADD_TRAIT(H, TRAIT_STABLEHEART, CLONING_POD_TRAIT)
+ ADD_TRAIT(H, TRAIT_STABLELIVER, CLONING_POD_TRAIT)
+ ADD_TRAIT(H, TRAIT_EMOTEMUTE, CLONING_POD_TRAIT)
+ ADD_TRAIT(H, TRAIT_MUTE, CLONING_POD_TRAIT)
+ ADD_TRAIT(H, TRAIT_NOBREATH, CLONING_POD_TRAIT)
+ ADD_TRAIT(H, TRAIT_NOCRITDAMAGE, CLONING_POD_TRAIT)
H.Unconscious(80)
- var/list/candidates = pollCandidatesForMob("Do you want to play as [clonename]'s defective clone?", null, null, null, 100, H)
+ var/list/candidates = pollCandidatesForMob("Do you want and agree to play as a [clonename]'s defective clone, respect their character and not engage in ERP without permission from the original?", null, null, null, 100, H, POLL_IGNORE_CLONE)
if(LAZYLEN(candidates))
var/mob/dead/observer/C = pick(candidates)
- H.key = C.key
+ C.transfer_ckey(H)
if(grab_ghost_when == CLONER_FRESH_CLONE)
H.grab_ghost()
diff --git a/code/game/machinery/firealarm.dm b/code/game/machinery/firealarm.dm
index 8f0d56a49e..5c2676c2b9 100644
--- a/code/game/machinery/firealarm.dm
+++ b/code/game/machinery/firealarm.dm
@@ -97,6 +97,7 @@
alarm()
/obj/machinery/firealarm/emag_act(mob/user)
+ . = ..()
if(obj_flags & EMAGGED)
return
obj_flags |= EMAGGED
@@ -104,6 +105,7 @@
user.visible_message("Sparks fly out of [src]!",
"You emag [src], disabling its thermal sensors.")
playsound(src, "sparks", 50, 1)
+ return TRUE
/obj/machinery/firealarm/temperature_expose(datum/gas_mixture/air, temperature, volume)
if((temperature > T0C + 200 || temperature < BODYTEMP_COLD_DAMAGE_LIMIT) && (last_alarm+FIREALARM_COOLDOWN < world.time) && !(obj_flags & EMAGGED) && detecting && !stat)
diff --git a/code/game/machinery/gulag_item_reclaimer.dm b/code/game/machinery/gulag_item_reclaimer.dm
index c955edbcd0..f51c145635 100644
--- a/code/game/machinery/gulag_item_reclaimer.dm
+++ b/code/game/machinery/gulag_item_reclaimer.dm
@@ -9,7 +9,6 @@
idle_power_usage = 100
active_power_usage = 2500
var/list/stored_items = list()
- var/obj/item/card/id/prisoner/inserted_id = null
var/obj/machinery/gulag_teleporter/linked_teleporter = null
/obj/machinery/gulag_item_reclaimer/Destroy()
@@ -18,28 +17,15 @@
I.forceMove(get_turf(src))
if(linked_teleporter)
linked_teleporter.linked_reclaimer = null
- if(inserted_id)
- inserted_id.forceMove(get_turf(src))
- inserted_id = null
return ..()
/obj/machinery/gulag_item_reclaimer/emag_act(mob/user)
+ . = ..()
if(obj_flags & EMAGGED) // emagging lets anyone reclaim all the items
return
req_access = list()
obj_flags |= EMAGGED
-
-/obj/machinery/gulag_item_reclaimer/attackby(obj/item/I, mob/user)
- if(istype(I, /obj/item/card/id/prisoner))
- if(!inserted_id)
- if(!user.transferItemToLoc(I, src))
- return
- inserted_id = I
- to_chat(user, "You insert [I].")
- return
- else
- to_chat(user, "There's an ID inserted already.")
- return ..()
+ return TRUE
/obj/machinery/gulag_item_reclaimer/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \
datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state)
@@ -55,15 +41,19 @@
if(allowed(user))
can_reclaim = TRUE
- if(inserted_id)
- data["id"] = inserted_id
- data["id_name"] = inserted_id.registered_name
- if(inserted_id.points >= inserted_id.goal)
+ var/obj/item/card/id/I = user.get_idcard(TRUE)
+ if(istype(I, /obj/item/card/id/prisoner))
+ var/obj/item/card/id/prisoner/P = I
+ if(P.points >= P.goal)
can_reclaim = TRUE
var/list/mobs = list()
for(var/i in stored_items)
var/mob/thismob = i
+ if(QDELETED(thismob))
+ say("Alert! Unable to locate vital signals of a previously processed prisoner. Ejecting equipment!")
+ drop_items(thismob)
+ continue
var/list/mob_info = list()
mob_info["name"] = thismob.real_name
mob_info["mob"] = "[REF(thismob)]"
@@ -78,16 +68,6 @@
/obj/machinery/gulag_item_reclaimer/ui_act(action, list/params)
switch(action)
- if("handle_id")
- if(inserted_id)
- usr.put_in_hands(inserted_id)
- inserted_id = null
- else
- var/obj/item/I = usr.is_holding_item_of_type(/obj/item/card/id/prisoner)
- if(I)
- if(!usr.transferItemToLoc(I, src))
- return
- inserted_id = I
if("release_items")
var/mob/M = locate(params["mobref"])
if(M == usr || allowed(usr))
@@ -98,8 +78,9 @@
/obj/machinery/gulag_item_reclaimer/proc/drop_items(mob/user)
if(!stored_items[user])
return
+ var/drop_location = drop_location()
for(var/i in stored_items[user])
var/obj/item/W = i
stored_items[user] -= W
- W.forceMove(get_turf(src))
+ W.forceMove(drop_location)
stored_items -= user
diff --git a/code/game/machinery/gulag_teleporter.dm b/code/game/machinery/gulag_teleporter.dm
index 47136e8c27..6b944f762d 100644
--- a/code/game/machinery/gulag_teleporter.dm
+++ b/code/game/machinery/gulag_teleporter.dm
@@ -136,16 +136,17 @@ The console is located at computer/gulag_teleporter.dm
linked_reclaimer.stored_items[occupant] = list()
var/mob/living/mob_occupant = occupant
for(var/obj/item/W in mob_occupant)
- if(!is_type_in_typecache(W, telegulag_required_items) && mob_occupant.temporarilyRemoveItemFromInventory(W))
- if(istype(W, /obj/item/restraints/handcuffs))
- W.forceMove(get_turf(src))
- continue
- if(linked_reclaimer)
- linked_reclaimer.stored_items[mob_occupant] += W
- linked_reclaimer.contents += W
- W.forceMove(linked_reclaimer)
- else
- W.forceMove(src)
+ if(!is_type_in_typecache(W, telegulag_required_items))
+ if(mob_occupant.temporarilyRemoveItemFromInventory(W))
+ if(istype(W, /obj/item/restraints/handcuffs))
+ W.forceMove(get_turf(src))
+ continue
+ if(linked_reclaimer)
+ linked_reclaimer.stored_items[mob_occupant] += W
+ linked_reclaimer.contents += W
+ W.forceMove(linked_reclaimer)
+ else
+ W.forceMove(src)
/obj/machinery/gulag_teleporter/proc/handle_prisoner(obj/item/id, datum/data/record/R)
if(!ishuman(occupant))
diff --git a/code/game/machinery/harvester.dm b/code/game/machinery/harvester.dm
index 0042da1c92..db015eb7c1 100644
--- a/code/game/machinery/harvester.dm
+++ b/code/game/machinery/harvester.dm
@@ -65,7 +65,7 @@
if(!isitem(A))
continue
var/obj/item/I = A
- if(!(I.item_flags & NODROP))
+ if(!HAS_TRAIT(I, TRAIT_NODROP))
say("Subject may not have abiotic items on.")
playsound(src, 'sound/machines/buzz-sigh.ogg', 30, 1)
return
@@ -138,7 +138,7 @@
to_chat(user, "[src] is currently occupied!")
return
if(state_open)
- to_chat(user, "[src] must be closed to [panel_open ? "close" : "open"] it's maintenance hatch!")
+ to_chat(user, "[src] must be closed to [panel_open ? "close" : "open"] its maintenance hatch!")
return
if(default_deconstruction_screwdriver(user, "[initial(icon_state)]-o", initial(icon_state), I))
return
@@ -158,11 +158,13 @@
open_machine()
/obj/machinery/harvester/emag_act(mob/user)
+ . = ..()
if(obj_flags & EMAGGED)
return
obj_flags |= EMAGGED
allow_living = TRUE
to_chat(user, "You overload [src]'s lifesign scanners.")
+ return TRUE
/obj/machinery/harvester/container_resist(mob/living/user)
if(!harvesting)
diff --git a/code/game/machinery/iv_drip.dm b/code/game/machinery/iv_drip.dm
index 7284b3c738..b217c14e53 100644
--- a/code/game/machinery/iv_drip.dm
+++ b/code/game/machinery/iv_drip.dm
@@ -63,7 +63,7 @@
if(91 to INFINITY)
filling_overlay.icon_state = "reagent100"
- filling_overlay.color = list("#0000", "#0000", "#0000", "#000f", mix_color_from_reagents(beaker.reagents.reagent_list))
+ filling_overlay.color = mix_color_from_reagents(beaker.reagents.reagent_list)
add_overlay(filling_overlay)
/obj/machinery/iv_drip/MouseDrop(mob/living/target)
@@ -81,12 +81,6 @@
to_chat(usr, "The drip beeps: Warning, incompatible creature!")
return
- var/mob/living/L
- if(isliving(target))
- L = target
- if(!L.can_inject(usr, 1))
- return
-
if(Adjacent(target) && usr.Adjacent(target))
if(beaker)
usr.visible_message("[usr] attaches [src] to [target].", "You attach [src] to [target].")
@@ -151,7 +145,7 @@
return
// If the human is losing too much blood, beep.
- if(attached.blood_volume < BLOOD_VOLUME_SAFE && prob(5))
+ if(attached.blood_volume < ( (BLOOD_VOLUME_SAFE*attached.blood_ratio) && prob(5) ) )
visible_message("[src] beeps loudly.")
playsound(loc, 'sound/machines/twobeep.ogg', 50, 1)
attached.transfer_blood_to(beaker, amount)
@@ -224,4 +218,4 @@
to_chat(user, "[attached ? attached : "No one"] is attached.")
#undef IV_TAKING
-#undef IV_INJECTING
\ No newline at end of file
+#undef IV_INJECTING
diff --git a/code/game/machinery/launch_pad.dm b/code/game/machinery/launch_pad.dm
index 4016c32162..db59f3f413 100644
--- a/code/game/machinery/launch_pad.dm
+++ b/code/game/machinery/launch_pad.dm
@@ -127,7 +127,7 @@
if(first_inner)
log_msg += "empty"
log_msg += ")"
- do_teleport(ROI, dest, no_effects = !first)
+ do_teleport(ROI, dest, no_effects = !first, channel = TELEPORT_CHANNEL_BLUESPACE)
first = FALSE
if (first)
@@ -206,7 +206,7 @@
/obj/item/storage/briefcase/launchpad/PopulateContents()
new /obj/item/pen(src)
- new /obj/item/launchpad_remote(src, pad)
+ new /obj/item/launchpad_remote(src, pad)
/obj/item/storage/briefcase/launchpad/attack_self(mob/user)
if(!isturf(user.loc)) //no setting up in a locker
@@ -227,7 +227,7 @@
L.pad = src.pad
to_chat(user, "You link [pad] to [L].")
else
- return ..()
+ return ..()
/obj/item/launchpad_remote
name = "folder"
diff --git a/code/game/machinery/limbgrower.dm b/code/game/machinery/limbgrower.dm
index a3250fe1b0..88ab4ec6f8 100644
--- a/code/game/machinery/limbgrower.dm
+++ b/code/game/machinery/limbgrower.dm
@@ -27,8 +27,10 @@
"human",
"lizard",
"fly",
- "moth",
+ "insect",
"plasmaman",
+ "mammal",
+ "xeno",
"other"
)
@@ -215,6 +217,7 @@
return dat
/obj/machinery/limbgrower/emag_act(mob/user)
+ . = ..()
if(obj_flags & EMAGGED)
return
for(var/id in SSresearch.techweb_designs)
@@ -223,3 +226,4 @@
stored_research.add_design(D)
to_chat(user, "A warning flashes onto the screen, stating that safety overrides have been deactivated!")
obj_flags |= EMAGGED
+ return TRUE
diff --git a/code/game/machinery/porta_turret/portable_turret.dm b/code/game/machinery/porta_turret/portable_turret.dm
index b0204ddeb8..b0a75c99dc 100644
--- a/code/game/machinery/porta_turret/portable_turret.dm
+++ b/code/game/machinery/porta_turret/portable_turret.dm
@@ -290,6 +290,7 @@
return ..()
/obj/machinery/porta_turret/emag_act(mob/user)
+ . = ..()
if(obj_flags & EMAGGED)
return
to_chat(user, "You short out [src]'s threat assessment circuits.")
@@ -300,6 +301,7 @@
update_icon()
sleep(60) //6 seconds for the traitor to gtfo of the area before the turret decides to ruin his shit
on = TRUE //turns it back on. The cover popUp() popDown() are automatically called in process(), no need to define it here
+ return TRUE
/obj/machinery/porta_turret/emp_act(severity)
@@ -837,6 +839,7 @@
to_chat(user, "Access denied.")
/obj/machinery/turretid/emag_act(mob/user)
+ . = ..()
if(obj_flags & EMAGGED)
return
to_chat(user, "You short out the turret controls' access analysis module.")
@@ -844,6 +847,7 @@
locked = FALSE
if(user && user.machine == src)
attack_hand(user)
+ return TRUE
/obj/machinery/turretid/attack_ai(mob/user)
if(!ailock || IsAdminGhost(user))
diff --git a/code/game/machinery/porta_turret/portable_turret_cover.dm b/code/game/machinery/porta_turret/portable_turret_cover.dm
index 6ff795c1cc..afb00d4ee2 100644
--- a/code/game/machinery/porta_turret/portable_turret_cover.dm
+++ b/code/game/machinery/porta_turret/portable_turret_cover.dm
@@ -86,10 +86,13 @@
. = 0
/obj/machinery/porta_turret_cover/emag_act(mob/user)
- if(!(parent_turret.obj_flags & EMAGGED))
- to_chat(user, "You short out [parent_turret]'s threat assessment circuits.")
- visible_message("[parent_turret] hums oddly...")
- parent_turret.obj_flags |= EMAGGED
- parent_turret.on = 0
- spawn(40)
- parent_turret.on = 1
+ . = ..()
+ if(parent_turret.obj_flags & EMAGGED)
+ return
+ to_chat(user, "You short out [parent_turret]'s threat assessment circuits.")
+ visible_message("[parent_turret] hums oddly...")
+ parent_turret.obj_flags |= EMAGGED
+ parent_turret.on = 0
+ spawn(40)
+ parent_turret.on = 1
+ return TRUE
diff --git a/code/game/machinery/quantum_pad.dm b/code/game/machinery/quantum_pad.dm
index 7a8552607b..b5ef38b42c 100644
--- a/code/game/machinery/quantum_pad.dm
+++ b/code/game/machinery/quantum_pad.dm
@@ -36,7 +36,7 @@
to_chat(user, "The panel is screwed in, obstructing the linking device.")
else
to_chat(user, "The linking device is now able to be scanned with a multitool.")
-
+
/obj/machinery/quantumpad/RefreshParts()
var/E = 0
for(var/obj/item/stock_parts/capacitor/C in component_parts)
@@ -74,15 +74,26 @@
to_chat(user, "There is no quantum pad data saved in [I]'s buffer!")
return TRUE
+ else if(istype(I, /obj/item/quantum_keycard))
+ var/obj/item/quantum_keycard/K = I
+ if(K.qpad)
+ to_chat(user, "You insert [K] into [src]'s card slot, activating it.")
+ interact(user, K.qpad)
+ else
+ to_chat(user, "You insert [K] into [src]'s card slot, initiating the link procedure.")
+ if(do_after(user, 40, target = src))
+ to_chat(user, "You complete the link between [K] and [src].")
+ K.qpad = src
+
if(default_deconstruction_crowbar(I))
return
return ..()
-/obj/machinery/quantumpad/interact(mob/user)
- if(!linked_pad || QDELETED(linked_pad))
+/obj/machinery/quantumpad/interact(mob/user, obj/machinery/quantumpad/target_pad = linked_pad)
+ if(!target_pad || QDELETED(target_pad))
if(!map_pad_link_id || !initMappedLink())
- to_chat(user, "There is no linked pad!")
+ to_chat(user, "Target pad not found!")
return
if(world.time < last_teleport + teleport_cooldown)
@@ -93,18 +104,18 @@
to_chat(user, "[src] is charging up. Please wait.")
return
- if(linked_pad.teleporting)
- to_chat(user, "Linked pad is busy. Please wait.")
+ if(target_pad.teleporting)
+ to_chat(user, "Target pad is busy. Please wait.")
return
- if(linked_pad.stat & NOPOWER)
- to_chat(user, "Linked pad is not responding to ping.")
+ if(target_pad.stat & NOPOWER)
+ to_chat(user, "Target pad is not responding to ping.")
return
add_fingerprint(user)
- doteleport(user)
+ doteleport(user, target_pad)
/obj/machinery/quantumpad/proc/sparks()
- var/datum/effect_system/spark_spread/s = new /datum/effect_system/spark_spread
+ var/datum/effect_system/spark_spread/quantum/s = new
s.set_up(5, 1, get_turf(src))
s.start()
@@ -117,8 +128,8 @@
if(linked_pad)
ghost.forceMove(get_turf(linked_pad))
-/obj/machinery/quantumpad/proc/doteleport(mob/user)
- if(linked_pad)
+/obj/machinery/quantumpad/proc/doteleport(mob/user, obj/machinery/quantumpad/target_pad = linked_pad)
+ if(target_pad)
playsound(get_turf(src), 'sound/weapons/flash.ogg', 25, 1)
teleporting = TRUE
@@ -130,7 +141,7 @@
to_chat(user, "[src] is unpowered!")
teleporting = FALSE
return
- if(!linked_pad || QDELETED(linked_pad) || linked_pad.stat & NOPOWER)
+ if(!target_pad || QDELETED(target_pad) || target_pad.stat & NOPOWER)
to_chat(user, "Linked pad is not responding to ping. Teleport aborted.")
teleporting = FALSE
return
@@ -141,26 +152,30 @@
// use a lot of power
use_power(10000 / power_efficiency)
sparks()
- linked_pad.sparks()
+ target_pad.sparks()
flick("qpad-beam", src)
playsound(get_turf(src), 'sound/weapons/emitter2.ogg', 25, 1, extrarange = 3, falloff = 5)
- flick("qpad-beam", linked_pad)
- playsound(get_turf(linked_pad), 'sound/weapons/emitter2.ogg', 25, 1, extrarange = 3, falloff = 5)
+ flick("qpad-beam", target_pad)
+ playsound(get_turf(target_pad), 'sound/weapons/emitter2.ogg', 25, 1, extrarange = 3, falloff = 5)
for(var/atom/movable/ROI in get_turf(src))
+ if(QDELETED(ROI))
+ continue //sleeps in CHECK_TICK
+
// if is anchored, don't let through
if(ROI.anchored)
if(isliving(ROI))
var/mob/living/L = ROI
- if(L.buckled)
- // TP people on office chairs
- if(L.buckled.anchored)
- continue
+ //only TP living mobs buckled to non anchored items
+ if(!L.buckled || L.buckled.anchored)
+ continue
else
continue
+ //Don't TP camera mobs
else if(!isobserver(ROI))
continue
- do_teleport(ROI, get_turf(linked_pad))
+ do_teleport(ROI, get_turf(target_pad),null,TRUE,null,null,null,null,TRUE, channel = TELEPORT_CHANNEL_QUANTUM)
+ CHECK_TICK
/obj/machinery/quantumpad/proc/initMappedLink()
. = FALSE
diff --git a/code/game/machinery/recycler.dm b/code/game/machinery/recycler.dm
index ff40a8539a..9d431487e0 100644
--- a/code/game/machinery/recycler.dm
+++ b/code/game/machinery/recycler.dm
@@ -65,6 +65,7 @@
return ..()
/obj/machinery/recycler/emag_act(mob/user)
+ . = ..()
if(obj_flags & EMAGGED)
return
obj_flags |= EMAGGED
@@ -73,6 +74,7 @@
update_icon()
playsound(src, "sparks", 75, 1, -1)
to_chat(user, "You use the cryptographic sequencer on [src].")
+ return TRUE
/obj/machinery/recycler/update_icon()
..()
diff --git a/code/game/machinery/requests_console.dm b/code/game/machinery/requests_console.dm
index f2c216ca98..ad9a846bdc 100644
--- a/code/game/machinery/requests_console.dm
+++ b/code/game/machinery/requests_console.dm
@@ -323,7 +323,7 @@ GLOBAL_LIST_EMPTY(allConsoles)
emergency = "Medical"
if(radio_freq)
Radio.set_frequency(radio_freq)
- Radio.talk_into(src,"[emergency] emergency in [department]!!",radio_freq,get_spans(),get_default_language())
+ Radio.talk_into(src,"[emergency] emergency in [department]!!",radio_freq)
update_icon()
addtimer(CALLBACK(src, .proc/clear_emergency), 3000)
@@ -382,7 +382,7 @@ GLOBAL_LIST_EMPTY(allConsoles)
screen = 6
if(radio_freq)
- Radio.talk_into(src,"[alert]: [message]",radio_freq,get_spans(),get_default_language())
+ Radio.talk_into(src, "[alert]: [message]", radio_freq)
switch(priority)
if(2)
diff --git a/code/game/machinery/shieldgen.dm b/code/game/machinery/shieldgen.dm
index e2aebd2370..fc6577a4f1 100644
--- a/code/game/machinery/shieldgen.dm
+++ b/code/game/machinery/shieldgen.dm
@@ -195,6 +195,7 @@
return ..()
/obj/machinery/shieldgen/emag_act(mob/user)
+ . = ..()
if(obj_flags & EMAGGED)
to_chat(user, "The access controller is damaged!")
return
@@ -202,6 +203,7 @@
locked = FALSE
playsound(src, "sparks", 100, 1)
to_chat(user, "You short out the access controller.")
+ return TRUE
/obj/machinery/shieldgen/update_icon()
if(active)
@@ -387,6 +389,7 @@
add_fingerprint(user)
/obj/machinery/shieldwallgen/emag_act(mob/user)
+ . = ..()
if(obj_flags & EMAGGED)
to_chat(user, "The access controller is damaged!")
return
@@ -394,6 +397,7 @@
locked = FALSE
playsound(src, "sparks", 100, 1)
to_chat(user, "You short out the access controller.")
+ return TRUE
//////////////Containment Field START
/obj/machinery/shieldwall
diff --git a/code/game/machinery/slotmachine.dm b/code/game/machinery/slotmachine.dm
index 298587cdea..4b73032a8a 100644
--- a/code/game/machinery/slotmachine.dm
+++ b/code/game/machinery/slotmachine.dm
@@ -99,6 +99,7 @@
return ..()
/obj/machinery/computer/slot_machine/emag_act()
+ . = ..()
if(obj_flags & EMAGGED)
return
obj_flags |= EMAGGED
@@ -106,6 +107,7 @@
spark_system.set_up(4, 0, src.loc)
spark_system.start()
playsound(src, "sparks", 50, 1)
+ return TRUE
/obj/machinery/computer/slot_machine/ui_interact(mob/living/user)
. = ..()
diff --git a/code/game/machinery/suit_storage_unit.dm b/code/game/machinery/suit_storage_unit.dm
index 90166dacf0..d308d180d0 100644
--- a/code/game/machinery/suit_storage_unit.dm
+++ b/code/game/machinery/suit_storage_unit.dm
@@ -210,13 +210,13 @@
add_fingerprint(user)
/obj/machinery/suit_storage_unit/proc/cook()
+ var/mob/living/mob_occupant = occupant
if(uv_cycles)
uv_cycles--
uv = TRUE
locked = TRUE
update_icon()
if(occupant)
- var/mob/living/mob_occupant = occupant
if(uv_super)
mob_occupant.adjustFireLoss(rand(20, 36))
else
@@ -245,10 +245,27 @@
visible_message("[src]'s door slides open. The glowing yellow lights dim to a gentle green.")
else
visible_message("[src]'s door slides open, barraging you with the nauseating smell of charred flesh.")
+ mob_occupant.radiation = 0
playsound(src, 'sound/machines/airlockclose.ogg', 25, 1)
- for(var/obj/item/I in src) //Scorches away blood and forensic evidence, although the SSU itself is unaffected
- SEND_SIGNAL(I, COMSIG_COMPONENT_CLEAN_ACT, CLEAN_STRONG)
- var/datum/component/radioactive/contamination = I.GetComponent(/datum/component/radioactive)
+ var/list/things_to_clear = list() //Done this way since using GetAllContents on the SSU itself would include circuitry and such.
+ if(suit)
+ things_to_clear += suit
+ things_to_clear += suit.GetAllContents()
+ if(helmet)
+ things_to_clear += helmet
+ things_to_clear += helmet.GetAllContents()
+ if(mask)
+ things_to_clear += mask
+ things_to_clear += mask.GetAllContents()
+ if(storage)
+ things_to_clear += storage
+ things_to_clear += storage.GetAllContents()
+ if(occupant)
+ things_to_clear += occupant
+ things_to_clear += occupant.GetAllContents()
+ for(var/atom/movable/AM in things_to_clear) //Scorches away blood and forensic evidence, although the SSU itself is unaffected
+ SEND_SIGNAL(AM, COMSIG_COMPONENT_CLEAN_ACT, CLEAN_STRONG)
+ var/datum/component/radioactive/contamination = AM.GetComponent(/datum/component/radioactive)
if(contamination)
qdel(contamination)
open_machine(FALSE)
diff --git a/code/game/machinery/syndicatebeacon.dm b/code/game/machinery/syndicatebeacon.dm
index a1ed7fb848..8a33241d46 100644
--- a/code/game/machinery/syndicatebeacon.dm
+++ b/code/game/machinery/syndicatebeacon.dm
@@ -1,3 +1,7 @@
+GLOBAL_VAR_INIT(singularity_counter, 0)
+
+#define METEOR_DISASTER_MODIFIER 0.5
+
////////////////////////////////////////
//Singularity beacon
////////////////////////////////////////
@@ -13,39 +17,57 @@
stat = 0
verb_say = "states"
var/cooldown = 0
-
- var/active = 0
+ var/active = FALSE
+ var/meteor_buff = FALSE
var/icontype = "beacon"
-
/obj/machinery/power/singularity_beacon/proc/Activate(mob/user = null)
+ if(active)
+ return FALSE
if(surplus() < 1500)
if(user)
to_chat(user, "The connected wire doesn't have enough current.")
- return
+ return FALSE
+ if(is_station_level(z))
+ increment_meteor_waves()
for(var/obj/singularity/singulo in GLOB.singularities)
if(singulo.z == z)
singulo.target = src
icon_state = "[icontype]1"
- active = 1
+ active = TRUE
if(user)
to_chat(user, "You activate the beacon.")
+ return TRUE
-
-/obj/machinery/power/singularity_beacon/proc/Deactivate(mob/user = null)
+/obj/machinery/power/singularity_beacon/proc/Deactivate(mob/user)
+ if(!active)
+ return FALSE
for(var/obj/singularity/singulo in GLOB.singularities)
if(singulo.target == src)
singulo.target = null
icon_state = "[icontype]0"
- active = 0
+ active = FALSE
if(user)
to_chat(user, "You deactivate the beacon.")
+ if(meteor_buff)
+ decrement_meteor_waves()
+ return TRUE
+/obj/machinery/power/singularity_beacon/proc/increment_meteor_waves()
+ meteor_buff = TRUE
+ GLOB.singularity_counter++
+ for(var/datum/round_event_control/meteor_wave/W in SSevents.control)
+ W.weight += round(initial(W.weight) * METEOR_DISASTER_MODIFIER)
+
+/obj/machinery/power/singularity_beacon/proc/decrement_meteor_waves()
+ meteor_buff = FALSE
+ GLOB.singularity_counter--
+ for(var/datum/round_event_control/meteor_wave/W in SSevents.control)
+ W.weight -= round(initial(W.weight) * METEOR_DISASTER_MODIFIER)
/obj/machinery/power/singularity_beacon/attack_ai(mob/user)
return
-
/obj/machinery/power/singularity_beacon/attack_hand(mob/user)
. = ..()
if(.)
@@ -86,6 +108,12 @@
if(!active)
return
+ var/is_on_station = is_station_level(z)
+ if(meteor_buff && !is_on_station)
+ decrement_meteor_waves()
+ else if(!meteor_buff && is_on_station)
+ increment_meteor_waves()
+
if(surplus() >= 1500)
add_load(1500)
if(cooldown <= world.time)
@@ -133,3 +161,5 @@
/obj/item/sbeacondrop/clownbomb
desc = "A label on it reads: Warning: Activating this device will send a silly explosive to your location."
droptype = /obj/machinery/syndicatebomb/badmin/clown
+
+#undef METEOR_DISASTER_MODIFIER
diff --git a/code/game/machinery/telecomms/computers/message.dm b/code/game/machinery/telecomms/computers/message.dm
index b11c6c102d..64f4cc7835 100644
--- a/code/game/machinery/telecomms/computers/message.dm
+++ b/code/game/machinery/telecomms/computers/message.dm
@@ -42,21 +42,23 @@
return ..()
/obj/machinery/computer/message_monitor/emag_act(mob/user)
+ . = ..()
if(obj_flags & EMAGGED)
return
- if(!isnull(linkedServer))
- obj_flags |= EMAGGED
- screen = 2
- spark_system.set_up(5, 0, src)
- spark_system.start()
- var/obj/item/paper/monitorkey/MK = new(loc, linkedServer)
- // Will help make emagging the console not so easy to get away with.
- MK.info += "
�%@%(*$%&(�&?*(%&�/{}"
- var/time = 100 * length(linkedServer.decryptkey)
- addtimer(CALLBACK(src, .proc/UnmagConsole), time)
- message = rebootmsg
- else
+ if(isnull(linkedServer))
to_chat(user, "A no server error appears on the screen.")
+ return
+ obj_flags |= EMAGGED
+ screen = 2
+ spark_system.set_up(5, 0, src)
+ spark_system.start()
+ var/obj/item/paper/monitorkey/MK = new(loc, linkedServer)
+ // Will help make emagging the console not so easy to get away with.
+ MK.info += "
"
@@ -65,6 +66,7 @@
icon_state = "motion2"
w_class = WEIGHT_CLASS_SMALL
var/ai_beacon = FALSE //If this beacon allows for AI control. Exists to avoid using istype() on checking.
+ var/recharging = 0
/obj/item/mecha_parts/mecha_tracking/proc/get_mecha_info()
if(!in_mecha())
@@ -102,10 +104,16 @@
return 0
/obj/item/mecha_parts/mecha_tracking/proc/shock()
+ if(recharging)
+ return
var/obj/mecha/M = in_mecha()
if(M)
- M.emp_act(EMP_LIGHT)
- qdel(src)
+ M.emp_act(EMP_HEAVY)
+ addtimer(CALLBACK(src, /obj/item/mecha_parts/mecha_tracking/proc/recharge), 15 SECONDS, TIMER_UNIQUE | TIMER_OVERRIDE)
+ recharging = 1
+
+/obj/item/mecha_parts/mecha_tracking/proc/recharge()
+ recharging = 0
/obj/item/mecha_parts/mecha_tracking/proc/get_mecha_log()
if(!ismecha(loc))
diff --git a/code/game/mecha/mecha_defense.dm b/code/game/mecha/mecha_defense.dm
index 367c750787..62a62b569d 100644
--- a/code/game/mecha/mecha_defense.dm
+++ b/code/game/mecha/mecha_defense.dm
@@ -149,7 +149,14 @@
use_power((cell.charge/3)/(severity*2))
take_damage(30 / severity, BURN, "energy", 1)
log_message("EMP detected", color="red")
- check_for_internal_damage(list(MECHA_INT_FIRE,MECHA_INT_TEMP_CONTROL,MECHA_INT_CONTROL_LOST,MECHA_INT_SHORT_CIRCUIT),1)
+
+ if(istype(src, /obj/mecha/combat))
+ mouse_pointer = 'icons/mecha/mecha_mouse-disable.dmi'
+ occupant?.update_mouse_pointer()
+ if(!equipment_disabled && occupant) //prevent spamming this message with back-to-back EMPs
+ to_chat(occupant, "Error -- Connection to equipment control unit has been lost.")
+ addtimer(CALLBACK(src, /obj/mecha/proc/restore_equipment), 3 SECONDS, TIMER_UNIQUE | TIMER_OVERRIDE)
+ equipment_disabled = 1
/obj/mecha/temperature_expose(datum/gas_mixture/air, exposed_temperature, exposed_volume)
if(exposed_temperature>max_temperature)
diff --git a/code/game/mecha/medical/medical.dm b/code/game/mecha/medical/medical.dm
index e86b5174cc..8b4e48cd3e 100644
--- a/code/game/mecha/medical/medical.dm
+++ b/code/game/mecha/medical/medical.dm
@@ -1,8 +1,3 @@
-/obj/mecha/medical/Initialize()
- . = ..()
- trackers += new /obj/item/mecha_parts/mecha_tracking(src)
-
-
/obj/mecha/medical/mechturn(direction)
setDir(direction)
playsound(src,'sound/mecha/mechmove01.ogg',40,1)
@@ -18,4 +13,4 @@
var/result = step_rand(src)
if(result)
playsound(src,'sound/mecha/mechstep.ogg',25,1)
- return result
\ No newline at end of file
+ return result
diff --git a/code/game/mecha/working/working.dm b/code/game/mecha/working/working.dm
index 498ccd94fa..c9e7d99ac0 100644
--- a/code/game/mecha/working/working.dm
+++ b/code/game/mecha/working/working.dm
@@ -1,6 +1,3 @@
/obj/mecha/working
internal_damage_threshold = 60
-/obj/mecha/working/Initialize()
- . = ..()
- trackers += new /obj/item/mecha_parts/mecha_tracking(src)
diff --git a/code/game/objects/buckling.dm b/code/game/objects/buckling.dm
index 9c3df5395f..693d32e545 100644
--- a/code/game/objects/buckling.dm
+++ b/code/game/objects/buckling.dm
@@ -89,6 +89,7 @@
buckled_mob.clear_alert("buckled")
buckled_mobs -= buckled_mob
SEND_SIGNAL(src, COMSIG_MOVABLE_UNBUCKLE, buckled_mob, force)
+ SEND_SIGNAL(src, COMSIG_MOVABLE_UNBUCKLE, src, force)
post_unbuckle_mob(.)
diff --git a/code/game/objects/effects/alien_acid.dm b/code/game/objects/effects/alien_acid.dm
index 1a605d64b0..3b5a029df4 100644
--- a/code/game/objects/effects/alien_acid.dm
+++ b/code/game/objects/effects/alien_acid.dm
@@ -17,7 +17,7 @@
target = get_turf(src)
if(acid_amt)
- acid_level = min(acid_amt*acid_pwr, 12000) //capped so the acid effect doesn't last a half hour on the floor.
+ acid_level = min( (CLAMP(round(acid_amt, 1), 0, INFINITY)) *acid_pwr, 12000) //capped so the acid effect doesn't last a half hour on the floor.
//handle APCs and newscasters and stuff nicely
pixel_x = target.pixel_x + rand(-4,4)
diff --git a/code/game/objects/effects/anomalies.dm b/code/game/objects/effects/anomalies.dm
index 91df57052e..fbe25c5d1b 100644
--- a/code/game/objects/effects/anomalies.dm
+++ b/code/game/objects/effects/anomalies.dm
@@ -190,11 +190,11 @@
/obj/effect/anomaly/bluespace/anomalyEffect()
..()
for(var/mob/living/M in range(1,src))
- do_teleport(M, locate(M.x, M.y, M.z), 4)
+ do_teleport(M, locate(M.x, M.y, M.z), 4, channel = TELEPORT_CHANNEL_BLUESPACE)
/obj/effect/anomaly/bluespace/Bumped(atom/movable/AM)
if(isliving(AM))
- do_teleport(AM, locate(AM.x, AM.y, AM.z), 8)
+ do_teleport(AM, locate(AM.x, AM.y, AM.z), 8, channel = TELEPORT_CHANNEL_BLUESPACE)
/obj/effect/anomaly/bluespace/detonate()
var/turf/T = safepick(get_area_turfs(impact_area))
diff --git a/code/game/objects/effects/blessing.dm b/code/game/objects/effects/blessing.dm
index 06ba2bb47c..5df90d65c7 100644
--- a/code/game/objects/effects/blessing.dm
+++ b/code/game/objects/effects/blessing.dm
@@ -16,3 +16,12 @@
I.alpha = 64
I.appearance_flags = RESET_ALPHA
add_alt_appearance(/datum/atom_hud/alternate_appearance/basic/blessedAware, "blessing", I)
+ RegisterSignal(loc, COMSIG_ATOM_INTERCEPT_TELEPORT, .proc/block_cult_teleport)
+
+/obj/effect/blessing/Destroy()
+ UnregisterSignal(loc, COMSIG_ATOM_INTERCEPT_TELEPORT)
+ return ..()
+
+/obj/effect/blessing/proc/block_cult_teleport(datum/source, channel, turf/origin, turf/destination)
+ if(channel == TELEPORT_CHANNEL_CULT)
+ return COMPONENT_BLOCK_TELEPORT
\ No newline at end of file
diff --git a/code/game/objects/effects/decals/cleanable/misc.dm b/code/game/objects/effects/decals/cleanable/misc.dm
index 55a162ad38..9f072c48b6 100644
--- a/code/game/objects/effects/decals/cleanable/misc.dm
+++ b/code/game/objects/effects/decals/cleanable/misc.dm
@@ -42,6 +42,9 @@
/obj/effect/decal/cleanable/glass/ex_act()
qdel(src)
+/obj/effect/decal/cleanable/glass/plasma
+ icon_state = "plasmatiny"
+
/obj/effect/decal/cleanable/dirt
name = "dirt"
desc = "Someone should clean that up."
@@ -219,7 +222,7 @@
name = "stabilized plasma"
desc = "A puddle of stabilized plasma."
icon_state = "flour"
- color = "#C8A5DC"
+ color = "#9e0089"
/obj/effect/decal/cleanable/insectguts
name = "insect guts"
diff --git a/code/game/objects/effects/decals/turfdecal/tilecoloring.dm b/code/game/objects/effects/decals/turfdecal/tilecoloring.dm
index 1708753dc1..1e755bc7e9 100644
--- a/code/game/objects/effects/decals/turfdecal/tilecoloring.dm
+++ b/code/game/objects/effects/decals/turfdecal/tilecoloring.dm
@@ -36,4 +36,203 @@
/obj/effect/turf_decal/tile/neutral
name = "neutral corner"
color = "#D4D4D4"
- alpha = 50
\ No newline at end of file
+ alpha = 50
+
+/obj/effect/turf_decal/trimline
+ layer = TURF_PLATING_DECAL_LAYER
+ alpha = 110
+ icon_state = "trimline_box"
+
+/obj/effect/turf_decal/trimline/white
+ color = "#FFFFFF"
+
+/obj/effect/turf_decal/trimline/white/line
+ name = "trim decal"
+ icon_state = "trimline"
+
+/obj/effect/turf_decal/trimline/white/corner
+ icon_state = "trimline_corner"
+
+/obj/effect/turf_decal/trimline/white/end
+ icon_state = "trimline_end"
+
+/obj/effect/turf_decal/trimline/white/filled
+ icon_state = "trimline_box_fill"
+
+/obj/effect/turf_decal/trimline/white/filled/line
+ icon_state = "trimline_fill"
+
+/obj/effect/turf_decal/trimline/white/filled/corner
+ icon_state = "trimline_corner_fill"
+
+/obj/effect/turf_decal/trimline/white/filled/end
+ icon_state = "trimline_end_fill"
+
+/obj/effect/turf_decal/trimline/red
+ color = "#DE3A3A"
+
+/obj/effect/turf_decal/trimline/red/line
+ icon_state = "trimline"
+
+/obj/effect/turf_decal/trimline/red/corner
+ icon_state = "trimline_corner"
+
+/obj/effect/turf_decal/trimline/red/end
+ icon_state = "trimline_end"
+
+/obj/effect/turf_decal/trimline/red/filled
+ icon_state = "trimline_box_fill"
+
+/obj/effect/turf_decal/trimline/red/filled/line
+ icon_state = "trimline_fill"
+
+/obj/effect/turf_decal/trimline/red/filled/corner
+ icon_state = "trimline_corner_fill"
+
+/obj/effect/turf_decal/trimline/red/filled/end
+ icon_state = "trimline_end_fill"
+
+/obj/effect/turf_decal/trimline/green
+ color = "#9FED58"
+
+/obj/effect/turf_decal/trimline/green/line
+ icon_state = "trimline"
+
+/obj/effect/turf_decal/trimline/green/corner
+ icon_state = "trimline_corner"
+
+/obj/effect/turf_decal/trimline/green/end
+ icon_state = "trimline_end"
+
+/obj/effect/turf_decal/trimline/green/filled
+ icon_state = "trimline_box_fill"
+
+/obj/effect/turf_decal/trimline/green/filled/line
+ icon_state = "trimline_fill"
+
+/obj/effect/turf_decal/trimline/green/filled/corner
+ icon_state = "trimline_corner_fill"
+
+/obj/effect/turf_decal/trimline/green/filled/end
+ icon_state = "trimline_end_fill"
+
+/obj/effect/turf_decal/trimline/blue
+ color = "#52B4E9"
+
+/obj/effect/turf_decal/trimline/blue/line
+ icon_state = "trimline"
+
+/obj/effect/turf_decal/trimline/blue/corner
+ icon_state = "trimline_corner"
+
+/obj/effect/turf_decal/trimline/blue/end
+ icon_state = "trimline_end"
+
+/obj/effect/turf_decal/trimline/blue/filled
+ icon_state = "trimline_box_fill"
+
+/obj/effect/turf_decal/trimline/blue/filled/line
+ icon_state = "trimline_fill"
+
+/obj/effect/turf_decal/trimline/blue/filled/corner
+ icon_state = "trimline_corner_fill"
+
+/obj/effect/turf_decal/trimline/blue/filled/end
+ icon_state = "trimline_end_fill"
+
+/obj/effect/turf_decal/trimline/yellow
+ color = "#EFB341"
+
+/obj/effect/turf_decal/trimline/yellow/line
+ icon_state = "trimline"
+
+/obj/effect/turf_decal/trimline/yellow/corner
+ icon_state = "trimline_corner"
+
+/obj/effect/turf_decal/trimline/yellow/end
+ icon_state = "trimline_end"
+
+/obj/effect/turf_decal/trimline/yellow/filled
+ icon_state = "trimline_box_fill"
+
+/obj/effect/turf_decal/trimline/yellow/filled/line
+ icon_state = "trimline_fill"
+
+/obj/effect/turf_decal/trimline/yellow/filled/corner
+ icon_state = "trimline_corner_fill"
+
+/obj/effect/turf_decal/trimline/yellow/filled/end
+ icon_state = "trimline_end_fill"
+
+/obj/effect/turf_decal/trimline/purple
+ color = "#D381C9"
+
+/obj/effect/turf_decal/trimline/purple/line
+ icon_state = "trimline"
+
+/obj/effect/turf_decal/trimline/purple/corner
+ icon_state = "trimline_corner"
+
+/obj/effect/turf_decal/trimline/purple/end
+ icon_state = "trimline_end"
+
+/obj/effect/turf_decal/trimline/purple/filled
+ icon_state = "trimline_box_fill"
+
+/obj/effect/turf_decal/trimline/purple/filled/line
+ icon_state = "trimline_fill"
+
+/obj/effect/turf_decal/trimline/purple/filled/corner
+ icon_state = "trimline_corner_fill"
+
+/obj/effect/turf_decal/trimline/purple/filled/end
+ icon_state = "trimline_end_fill"
+
+/obj/effect/turf_decal/trimline/brown
+ color = "#A46106"
+
+/obj/effect/turf_decal/trimline/brown/line
+ icon_state = "trimline"
+
+/obj/effect/turf_decal/trimline/brown/corner
+ icon_state = "trimline_corner"
+
+/obj/effect/turf_decal/trimline/brown/end
+ icon_state = "trimline_end"
+
+/obj/effect/turf_decal/trimline/brown/filled
+ icon_state = "trimline_box_fill"
+
+/obj/effect/turf_decal/trimline/brown/filled/line
+ icon_state = "trimline_fill"
+
+/obj/effect/turf_decal/trimline/brown/filled/corner
+ icon_state = "trimline_corner_fill"
+
+/obj/effect/turf_decal/trimline/brown/filled/end
+ icon_state = "trimline_end_fill"
+
+/obj/effect/turf_decal/trimline/neutral
+ color = "#D4D4D4"
+ alpha = 50
+
+/obj/effect/turf_decal/trimline/neutral/line
+ icon_state = "trimline"
+
+/obj/effect/turf_decal/trimline/neutral/corner
+ icon_state = "trimline_corner"
+
+/obj/effect/turf_decal/trimline/neutral/end
+ icon_state = "trimline_end"
+
+/obj/effect/turf_decal/trimline/neutral/filled
+ icon_state = "trimline_box_fill"
+
+/obj/effect/turf_decal/trimline/neutral/filled/line
+ icon_state = "trimline_fill"
+
+/obj/effect/turf_decal/trimline/neutral/filled/corner
+ icon_state = "trimline_corner_fill"
+
+/obj/effect/turf_decal/trimline/brown/filled/end
+ icon_state = "trimline_end_fill"
\ No newline at end of file
diff --git a/code/game/objects/effects/effect_system/effects_smoke.dm b/code/game/objects/effects/effect_system/effects_smoke.dm
index 79deac475b..028110170a 100644
--- a/code/game/objects/effects/effect_system/effects_smoke.dm
+++ b/code/game/objects/effects/effect_system/effects_smoke.dm
@@ -288,7 +288,7 @@
contained = "\[[contained]\]"
var/where = "[AREACOORD(location)]"
- if(carry.my_atom.fingerprintslast)
+ if(carry.my_atom && carry.my_atom.fingerprintslast)
var/mob/M = get_mob_by_key(carry.my_atom.fingerprintslast)
var/more = ""
if(M)
diff --git a/code/game/objects/effects/effect_system/effects_sparks.dm b/code/game/objects/effects/effect_system/effects_sparks.dm
index 0656d9b3ca..19b0dc76dd 100644
--- a/code/game/objects/effects/effect_system/effects_sparks.dm
+++ b/code/game/objects/effects/effect_system/effects_sparks.dm
@@ -26,7 +26,7 @@
/obj/effect/particle_effect/sparks/Initialize()
. = ..()
- flick("sparks", src) // replay the animation
+ flick(icon_state, src) // replay the animation
playsound(src, "sparks", 100, TRUE)
var/turf/T = loc
if(isturf(T))
@@ -48,6 +48,8 @@
/datum/effect_system/spark_spread
effect_type = /obj/effect/particle_effect/sparks
+/datum/effect_system/spark_spread/quantum
+ effect_type = /obj/effect/particle_effect/sparks/quantum
//electricity
@@ -55,5 +57,9 @@
name = "lightning"
icon_state = "electricity"
+/obj/effect/particle_effect/sparks/quantum
+ name = "quantum sparks"
+ icon_state = "quantum_sparks"
+
/datum/effect_system/lightning_spread
effect_type = /obj/effect/particle_effect/sparks/electricity
diff --git a/code/game/objects/effects/mines.dm b/code/game/objects/effects/mines.dm
index 7e8094c9e7..32d91ee76a 100644
--- a/code/game/objects/effects/mines.dm
+++ b/code/game/objects/effects/mines.dm
@@ -131,7 +131,7 @@
var/obj/item/twohanded/required/chainsaw/doomslayer/chainsaw = new(victim.loc)
victim.log_message("entered a blood frenzy", LOG_ATTACK)
- chainsaw.item_flags |= NODROP
+ ADD_TRAIT(chainsaw, TRAIT_NODROP, CHAINSAW_FRENZY_TRAIT)
victim.drop_all_held_items()
victim.put_in_hands(chainsaw, forced = TRUE)
chainsaw.attack_self(victim)
diff --git a/code/game/objects/effects/portals.dm b/code/game/objects/effects/portals.dm
index d69cfd38ef..631b87cada 100644
--- a/code/game/objects/effects/portals.dm
+++ b/code/game/objects/effects/portals.dm
@@ -20,6 +20,7 @@
var/mech_sized = FALSE
var/obj/effect/portal/linked
var/hardlinked = TRUE //Requires a linked portal at all times. Destroy if there's no linked portal, if there is destroy it when this one is deleted.
+ var/teleport_channel = TELEPORT_CHANNEL_BLUESPACE
var/creator
var/turf/hard_target //For when a portal needs a hard target and isn't to be linked.
var/atmos_link = FALSE //Link source/destination atmos.
@@ -34,6 +35,7 @@
icon = 'icons/obj/objects.dmi'
icon_state = "anom"
mech_sized = TRUE
+ teleport_channel = TELEPORT_CHANNEL_WORMHOLE
/obj/effect/portal/Move(newloc)
for(var/T in newloc)
@@ -160,7 +162,7 @@
no_effect = TRUE
else
last_effect = world.time
- if(do_teleport(M, real_target, innate_accuracy_penalty, no_effects = no_effect))
+ if(do_teleport(M, real_target, innate_accuracy_penalty, no_effects = no_effect, channel = teleport_channel))
if(istype(M, /obj/item/projectile))
var/obj/item/projectile/P = M
P.ignore_source_check = TRUE
diff --git a/code/game/objects/effects/spawners/bundle.dm b/code/game/objects/effects/spawners/bundle.dm
index 2fe8d2a460..b9acba70d9 100644
--- a/code/game/objects/effects/spawners/bundle.dm
+++ b/code/game/objects/effects/spawners/bundle.dm
@@ -133,7 +133,7 @@
/obj/effect/spawner/bundle/costume/holiday_priest
name = "holiday priest costume spawner"
items = list(
- /obj/item/clothing/suit/holidaypriest)
+ /obj/item/clothing/suit/chaplain/holidaypriest)
/obj/effect/spawner/bundle/costume/marisawizard
name = "marisa wizard costume spawner"
diff --git a/code/game/objects/effects/spawners/lootdrop.dm b/code/game/objects/effects/spawners/lootdrop.dm
index 0e543a3642..bdb949a570 100644
--- a/code/game/objects/effects/spawners/lootdrop.dm
+++ b/code/game/objects/effects/spawners/lootdrop.dm
@@ -30,6 +30,18 @@
loot_spawned++
return INITIALIZE_HINT_QDEL
+/obj/effect/spawner/lootdrop/bedsheet
+ icon = 'icons/obj/bedsheets.dmi'
+ icon_state = "random_bedsheet"
+ name = "random dorms bedsheet"
+ loot = list(/obj/item/bedsheet = 8, /obj/item/bedsheet/blue = 8, /obj/item/bedsheet/green = 8,
+ /obj/item/bedsheet/grey = 8, /obj/item/bedsheet/orange = 8, /obj/item/bedsheet/purple = 8,
+ /obj/item/bedsheet/red = 8, /obj/item/bedsheet/yellow = 8, /obj/item/bedsheet/brown = 8,
+ /obj/item/bedsheet/black = 8, /obj/item/bedsheet/patriot = 3, /obj/item/bedsheet/rainbow = 3,
+ /obj/item/bedsheet/ian = 3, /obj/item/bedsheet/runtime = 3, /obj/item/bedsheet/nanotrasen = 3,
+ /obj/item/bedsheet/pirate = 1, /obj/item/bedsheet/cosmos = 1, /obj/item/bedsheet/gondola = 1
+ )
+
/obj/effect/spawner/lootdrop/armory_contraband
name = "armory contraband gun spawner"
lootdoubles = FALSE
@@ -108,6 +120,33 @@
loot = GLOB.maintenance_loot
. = ..()
+/obj/effect/spawner/lootdrop/glowstick
+ name = "random colored glowstick"
+ icon = 'icons/obj/lighting.dmi'
+ icon_state = "random_glowstick"
+
+/obj/effect/spawner/lootdrop/glowstick/Initialize()
+ loot = typesof(/obj/item/flashlight/glowstick)
+ . = ..()
+
+
+/obj/effect/spawner/lootdrop/gloves
+ name = "random gloves"
+ desc = "These gloves are supposed to be a random color..."
+ icon = 'icons/obj/clothing/gloves.dmi'
+ icon_state = "random_gloves"
+ loot = list(
+ /obj/item/clothing/gloves/color/orange = 1,
+ /obj/item/clothing/gloves/color/red = 1,
+ /obj/item/clothing/gloves/color/blue = 1,
+ /obj/item/clothing/gloves/color/purple = 1,
+ /obj/item/clothing/gloves/color/green = 1,
+ /obj/item/clothing/gloves/color/grey = 1,
+ /obj/item/clothing/gloves/color/light_brown = 1,
+ /obj/item/clothing/gloves/color/brown = 1,
+ /obj/item/clothing/gloves/color/white = 1,
+ /obj/item/clothing/gloves/color/rainbow = 1)
+
/obj/effect/spawner/lootdrop/crate_spawner
name = "lootcrate spawner" //USE PROMO CODE "SELLOUT" FOR 20% OFF!
lootdoubles = FALSE
diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm
index 2af7be2564..6a706fede9 100644
--- a/code/game/objects/items.dm
+++ b/code/game/objects/items.dm
@@ -33,6 +33,7 @@ GLOBAL_VAR_INIT(rpg_loot_items, FALSE)
var/usesound = null
var/throwhitsound = null
var/w_class = WEIGHT_CLASS_NORMAL
+ var/total_mass //Total mass in arbitrary pound-like values. If there's no balance reasons for an item to have otherwise, this var should be the item's weight in pounds.
var/slot_flags = 0 //This is used to determine on which slots an item can fit.
pass_flags = PASSTABLE
pressure_resistance = 4
@@ -67,6 +68,7 @@ GLOBAL_VAR_INIT(rpg_loot_items, FALSE)
var/strip_delay = 40 //In deciseconds, how long an item takes to remove from another person
var/breakouttime = 0
var/list/materials
+ var/reskinned = FALSE
var/list/attack_verb //Used in attackby() to say how something was attacked "[x] has been [z.attack_verb] by [y] with [z]"
var/list/species_exception = null // list() of species types, if a species cannot put items in a certain slot, but species type is in list, it will be able to wear that item
@@ -230,9 +232,6 @@ GLOBAL_VAR_INIT(rpg_loot_items, FALSE)
research_msg += "."
to_chat(user, research_msg.Join())
-/obj/item/proc/speechModification(message) //for message modding by mask slot.
- return message
-
/obj/item/interact(mob/user)
add_fingerprint(user)
ui_interact(user)
@@ -422,10 +421,10 @@ GLOBAL_VAR_INIT(rpg_loot_items, FALSE)
//the mob M is attempting to equip this item into the slot passed through as 'slot'. Return 1 if it can do this and 0 if it can't.
//if this is being done by a mob other than M, it will include the mob equipper, who is trying to equip the item to mob M. equipper will be null otherwise.
//If you are making custom procs but would like to retain partial or complete functionality of this one, include a 'return ..()' to where you want this to happen.
-//Set disable_warning to 1 if you wish it to not give you outputs.
+//Set disable_warning to TRUE if you wish it to not give you outputs.
/obj/item/proc/mob_can_equip(mob/living/M, mob/living/equipper, slot, disable_warning = FALSE, bypass_equip_delay_self = FALSE)
if(!M)
- return 0
+ return FALSE
return M.can_equip(src, slot, disable_warning, bypass_equip_delay_self)
@@ -515,12 +514,12 @@ GLOBAL_VAR_INIT(rpg_loot_items, FALSE)
log_combat(user, M, "attacked", "[src.name]", "(INTENT: [uppertext(user.a_intent)])")
- M.adjust_blurriness(3)
- M.adjust_eye_damage(rand(2,4))
var/obj/item/organ/eyes/eyes = M.getorganslot(ORGAN_SLOT_EYES)
if (!eyes)
return
- if(eyes.eye_damage >= 10)
+ M.adjust_blurriness(3)
+ eyes.applyOrganDamage(rand(2,4))
+ if(eyes.damage >= 10)
M.adjust_blurriness(15)
if(M.stat != DEAD)
to_chat(M, "Your eyes start to bleed profusely!")
@@ -534,7 +533,7 @@ GLOBAL_VAR_INIT(rpg_loot_items, FALSE)
M.adjust_blurriness(10)
M.Unconscious(20)
M.Knockdown(40)
- if (prob(eyes.eye_damage - 10 + 1))
+ if (prob(eyes.damage - 10 + 1))
M.become_blind(EYE_DAMAGE)
to_chat(M, "You go blind!")
@@ -582,6 +581,9 @@ GLOBAL_VAR_INIT(rpg_loot_items, FALSE)
/obj/item/proc/get_belt_overlay() //Returns the icon used for overlaying the object on a belt
return mutable_appearance('icons/obj/clothing/belt_overlays.dmi', icon_state)
+/obj/item/proc/get_worn_belt_overlay(icon_file)
+ return
+
/obj/item/proc/update_slot_icon()
if(!ismob(loc))
return
@@ -647,11 +649,6 @@ GLOBAL_VAR_INIT(rpg_loot_items, FALSE)
else
. = ""
-
-//when an item modify our speech spans when in our active hand. Override this to modify speech spans.
-/obj/item/proc/get_held_item_speechspans(mob/living/carbon/user)
- return
-
/obj/item/hitby(atom/movable/AM)
return
@@ -825,6 +822,6 @@ GLOBAL_VAR_INIT(rpg_loot_items, FALSE)
return ..()
/obj/item/throw_at(atom/target, range, speed, mob/thrower, spin=TRUE, diagonals_first = FALSE, var/datum/callback/callback)
- if (item_flags & NODROP)
+ if (HAS_TRAIT(src, TRAIT_NODROP))
return
return ..()
diff --git a/code/game/objects/items/RCD.dm b/code/game/objects/items/RCD.dm
index 38d64be99c..cf706359f7 100644
--- a/code/game/objects/items/RCD.dm
+++ b/code/game/objects/items/RCD.dm
@@ -37,6 +37,7 @@ RLD
var/has_ammobar = FALSE //controls whether or not does update_icon apply ammo indicator overlays
var/ammo_sections = 10 //amount of divisions in the ammo indicator overlay/number of ammo indicator states
var/custom_range = 7
+ var/upgrade = FALSE
/obj/item/construction/Initialize()
. = ..()
@@ -82,6 +83,11 @@ RLD
loaded = loadwithsheets(W, sheetmultiplier * 0.25, user) // 1 matter for 1 floortile, as 4 tiles are produced from 1 metal
if(loaded)
to_chat(user, "[src] now holds [matter]/[max_matter] matter-units.")
+ else if(istype(W, /obj/item/rcd_upgrade))
+ to_chat(user, "You upgrade the RCD with the [W]!")
+ upgrade = TRUE
+ playsound(src.loc, 'sound/machines/click.ogg', 50, 1)
+ qdel(W)
else
return ..()
update_icon() //ensures that ammo counters (if present) get updated
@@ -148,6 +154,7 @@ RLD
has_ammobar = TRUE
var/mode = 1
var/ranged = FALSE
+ var/computer_dir = 1
var/airlock_type = /obj/machinery/door/airlock
var/airlock_glass = FALSE // So the floor's rcd_act knows how much ammo to use
var/window_type = /obj/structure/window/fulltile
@@ -270,6 +277,28 @@ RLD
return FALSE
return TRUE
+/obj/item/construction/rcd/proc/change_computer_dir(mob/user)
+ if(!user)
+ return
+ var/list/computer_dirs = list(
+ "NORTH" = image(icon = 'icons/mob/radial.dmi', icon_state = "cnorth"),
+ "EAST" = image(icon = 'icons/mob/radial.dmi', icon_state = "ceast"),
+ "SOUTH" = image(icon = 'icons/mob/radial.dmi', icon_state = "csouth"),
+ "WEST" = image(icon = 'icons/mob/radial.dmi', icon_state = "cwest")
+ )
+ var/computerdirs = show_radial_menu(user, src, computer_dirs, custom_check = CALLBACK(src, .proc/check_menu, user), require_near = TRUE, tooltips = TRUE)
+ if(!check_menu(user))
+ return
+ switch(computerdirs)
+ if("NORTH")
+ computer_dir = 1
+ if("EAST")
+ computer_dir = 4
+ if("SOUTH")
+ computer_dir = 2
+ if("WEST")
+ computer_dir = 8
+
/obj/item/construction/rcd/proc/change_airlock_setting(mob/user)
if(!user)
return
@@ -414,10 +443,12 @@ RLD
return FALSE
if(do_after(user, rcd_results["delay"] * delay_mod, target = A))
if(checkResource(rcd_results["cost"], user))
+ var/atom/cached = A
if(A.rcd_act(user, src, rcd_results["mode"]))
useResource(rcd_results["cost"], user)
activate()
- playsound(src.loc, 'sound/machines/click.ogg', 50, 1)
+ investigate_log("[user] used [src] on [cached] (now [A]) with cost [rcd_results["cost"]], delay [rcd_results["delay"]], mode [rcd_results["mode"]].", INVESTIGATE_RCD)
+ playsound(src, 'sound/machines/click.ogg', 50, 1)
return TRUE
/obj/item/construction/rcd/Initialize()
@@ -432,10 +463,15 @@ RLD
..()
var/list/choices = list(
"Airlock" = image(icon = 'icons/mob/radial.dmi', icon_state = "airlock"),
- "Deconstruct" = image(icon= 'icons/mob/radial.dmi', icon_state = "delete"),
"Grilles & Windows" = image(icon = 'icons/mob/radial.dmi', icon_state = "grillewindow"),
"Floors & Walls" = image(icon = 'icons/mob/radial.dmi', icon_state = "wallfloor")
)
+ if(upgrade)
+ choices += list(
+ "Deconstruct" = image(icon= 'icons/mob/radial.dmi', icon_state = "delete"),
+ "Machine Frames" = image(icon = 'icons/mob/radial.dmi', icon_state = "machine"),
+ "Computer Frames" = image(icon = 'icons/mob/radial.dmi', icon_state = "computer_dir"),
+ )
if(mode == RCD_AIRLOCK)
choices += list(
"Change Access" = image(icon = 'icons/mob/radial.dmi', icon_state = "access"),
@@ -457,6 +493,12 @@ RLD
mode = RCD_DECONSTRUCT
if("Grilles & Windows")
mode = RCD_WINDOWGRILLE
+ if("Machine Frames")
+ mode = RCD_MACHINE
+ if("Computer Frames")
+ mode = RCD_COMPUTER
+ change_computer_dir(user)
+ return
if("Change Access")
change_airlock_access(user)
return
@@ -509,6 +551,7 @@ RLD
no_ammo_message = "Insufficient charge."
desc = "A device used to rapidly build walls and floors."
canRturf = TRUE
+ upgrade = TRUE
/obj/item/construction/rcd/borg/useResource(amount, mob/user)
@@ -540,6 +583,9 @@ RLD
/obj/item/construction/rcd/loaded
matter = 160
+/obj/item/construction/rcd/loaded/upgraded
+ upgrade = TRUE
+
/obj/item/construction/rcd/combat
name = "Combat RCD"
desc = "A device used to rapidly build and deconstruct. Reload with metal, plasteel, glass or compressed matter cartridges. This RCD has been upgraded to be able to remove Rwalls!"
@@ -580,7 +626,7 @@ RLD
name = "admin RCD"
max_matter = INFINITY
matter = INFINITY
-
+ upgrade = TRUE
// Ranged RCD
@@ -614,7 +660,7 @@ RLD
name = "rapid-light-device (RLD)"
desc = "A device used to rapidly provide lighting sources to an area. Reload with metal, plasteel, glass or compressed matter cartridges."
icon = 'icons/obj/tools.dmi'
- icon_state = "rld-5"
+ icon_state = "rld"
lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi'
righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi'
matter = 500
@@ -622,6 +668,8 @@ RLD
sheetmultiplier = 16
var/mode = LIGHT_MODE
actions_types = list(/datum/action/item_action/pick_color)
+ ammo_sections = 5
+ has_ammobar = TRUE
var/wallcost = 10
var/floorcost = 15
@@ -635,6 +683,10 @@ RLD
var/color_choice = null
+/obj/item/construction/rld/Initialize()
+ . = ..()
+ update_icon()
+
/obj/item/construction/rld/ui_action_click(mob/user, var/datum/action/A)
if(istype(A, /datum/action/item_action/pick_color))
color_choice = input(user,"","Choose Color",color_choice) as color
@@ -642,9 +694,10 @@ RLD
..()
/obj/item/construction/rld/update_icon()
- icon_state = "rld-[round(matter/35)]"
..()
-
+ var/ratio = CEILING((matter / max_matter) * ammo_sections, 1)
+ cut_overlays() //To prevent infinite stacking of overlays
+ add_overlay("rld_light[ratio]")
/obj/item/construction/rld/attack_self(mob/user)
..()
@@ -767,6 +820,12 @@ RLD
return TRUE
return FALSE
+/obj/item/rcd_upgrade
+ name = "RCD advanced design disk"
+ desc = "It contains the design for machine frames, computer frames, and deconstruction."
+ icon = 'icons/obj/module.dmi'
+ icon_state = "datadisk3"
+
#undef GLOW_MODE
#undef LIGHT_MODE
#undef REMOVE_MODE
diff --git a/code/game/objects/items/RCL.dm b/code/game/objects/items/RCL.dm
index cea8165e02..63f460f9aa 100644
--- a/code/game/objects/items/RCL.dm
+++ b/code/game/objects/items/RCL.dm
@@ -2,7 +2,7 @@
name = "rapid cable layer"
desc = "A device used to rapidly deploy cables. It has screws on the side which can be removed to slide off the cables. Do not use without insulation!"
icon = 'icons/obj/tools.dmi'
- icon_state = "rcl-0"
+ icon_state = "rcl-empty"
item_state = "rcl-0"
var/obj/structure/cable/last
var/obj/item/stack/cable_coil/loaded
@@ -92,22 +92,32 @@
/obj/item/twohanded/rcl/update_icon()
if(!loaded)
- icon_state = "rcl-0"
- item_state = "rcl-0"
+ icon_state = "rcl-empty"
+ item_state = "rcl-empty"
return
+ cut_overlays()
+ var/cable_amount = 0
switch(loaded.amount)
if(61 to INFINITY)
- icon_state = "rcl-30"
- item_state = "rcl"
+ cable_amount = 3
if(31 to 60)
- icon_state = "rcl-20"
- item_state = "rcl"
+ cable_amount = 2
if(1 to 30)
- icon_state = "rcl-10"
- item_state = "rcl"
+ cable_amount = 1
else
- icon_state = "rcl-0"
- item_state = "rcl-0"
+ cable_amount = 0
+
+ var/mutable_appearance/cable_overlay = mutable_appearance(icon, "rcl-[cable_amount]")
+ cable_overlay.color = GLOB.cable_colors[colors[current_color_index]]
+ if(cable_amount >= 1)
+ icon_state = "rcl"
+ item_state = "rcl"
+ add_overlay(cable_overlay)
+ else
+ icon_state = "rcl-empty"
+ item_state = "rcl-0"
+ add_overlay(cable_overlay)
+
/obj/item/twohanded/rcl/proc/is_empty(mob/user, loud = 1)
update_icon()
@@ -302,6 +312,7 @@ obj/item/twohanded/rcl/proc/getMobhook(mob/to_hook)
to_chat(user, "Color changed to [cwname]!")
if(loaded)
loaded.item_color= colors[current_color_index]
+ update_icon()
if(wiring_gui_menu)
wiringGuiUpdate(user)
else if(istype(action, /datum/action/item_action/rcl_gui))
@@ -318,13 +329,29 @@ obj/item/twohanded/rcl/proc/getMobhook(mob/to_hook)
/obj/item/twohanded/rcl/ghetto/update_icon()
if(!loaded)
- icon_state = "rclg-0"
+ icon_state = "rclg-empty"
item_state = "rclg-0"
return
+ cut_overlays()
+ var/cable_amount = 0
switch(loaded.amount)
- if(1 to INFINITY)
- icon_state = "rclg-1"
- item_state = "rcl"
+ if(20 to INFINITY)
+ cable_amount = 3
+ if(10 to 19)
+ cable_amount = 2
+ if(1 to 9)
+ cable_amount = 1
else
- icon_state = "rclg-1"
- item_state = "rclg-1"
+ cable_amount = 0
+
+ var/mutable_appearance/cable_overlay = mutable_appearance(icon, "rcl-[cable_amount]")
+ cable_overlay.color = GLOB.cable_colors[colors[current_color_index]]
+ if(cable_amount >= 1)
+ icon_state = "rclg"
+ item_state = "rclg"
+ add_overlay(cable_overlay)
+ else
+ icon_state = "rclg-empty"
+ item_state = "rclg-0"
+ add_overlay(cable_overlay)
+
diff --git a/code/game/objects/items/RPD.dm b/code/game/objects/items/RPD.dm
index 0549ebc474..141f7e510a 100644
--- a/code/game/objects/items/RPD.dm
+++ b/code/game/objects/items/RPD.dm
@@ -177,6 +177,8 @@ GLOBAL_LIST_INIT(transit_tube_recipes, list(
desc = "A device used to rapidly pipe things."
icon = 'icons/obj/tools.dmi'
icon_state = "rpd"
+ lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi'
flags_1 = CONDUCT_1
force = 10
throwforce = 10
@@ -233,6 +235,10 @@ GLOBAL_LIST_INIT(transit_tube_recipes, list(
playsound(get_turf(user), 'sound/items/deconstruct.ogg', 50, 1)
return(BRUTELOSS)
+/obj/item/pipe_dispenser/ui_base_html(html)
+ var/datum/asset/spritesheet/assets = get_asset_datum(/datum/asset/spritesheet/pipes)
+ . = replacetext(html, "", assets.css_tag())
+
/obj/item/pipe_dispenser/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \
datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state)
ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
@@ -431,6 +437,10 @@ GLOBAL_LIST_INIT(transit_tube_recipes, list(
to_chat(user, "You start building a transit tube...")
playsound(get_turf(src), 'sound/machines/click.ogg', 50, 1)
if(do_after(user, transit_build_speed, target = A))
+ for(var/obj/structure/c_transit_tube/tube in A)
+ if(tube.dir == queued_p_dir || (queued_p_flipped && (tube.dir == turn(queued_p_dir, 45))))
+ to_chat(user, "[src]'s error light flickers; there's already a pipe in the way!")
+ return
activate()
if(queued_p_type == /obj/structure/c_transit_tube_pod)
var/obj/structure/c_transit_tube_pod/pod = new /obj/structure/c_transit_tube_pod(A)
diff --git a/code/game/objects/items/RSF.dm b/code/game/objects/items/RSF.dm
index f0d15cc3a2..9c343c2e06 100644
--- a/code/game/objects/items/RSF.dm
+++ b/code/game/objects/items/RSF.dm
@@ -134,12 +134,14 @@ RSF
return
/obj/item/cookiesynth/emag_act(mob/user)
+ . = ..()
obj_flags ^= EMAGGED
if(obj_flags & EMAGGED)
to_chat(user, "You short out [src]'s reagent safety checker!")
else
to_chat(user, "You reset [src]'s reagent safety checker!")
- toxin = 0
+ toxin = FALSE
+ return TRUE
/obj/item/cookiesynth/attack_self(mob/user)
var/mob/living/silicon/robot/P = null
@@ -180,7 +182,7 @@ RSF
to_chat(user, "Fabricating Cookie..")
var/obj/item/reagent_containers/food/snacks/cookie/S = new /obj/item/reagent_containers/food/snacks/cookie(T)
if(toxin)
- S.reagents.add_reagent("chloralhydratedelayed", 10)
+ S.reagents.add_reagent("chloralhydrate", 10)
if (iscyborg(user))
var/mob/living/silicon/robot/R = user
R.cell.charge -= 100
diff --git a/code/game/objects/items/body_egg.dm b/code/game/objects/items/body_egg.dm
index 80fc0f43fd..ea72197cf0 100644
--- a/code/game/objects/items/body_egg.dm
+++ b/code/game/objects/items/body_egg.dm
@@ -17,26 +17,27 @@
/obj/item/organ/body_egg/Insert(var/mob/living/carbon/M, special = 0)
..()
ADD_TRAIT(owner, TRAIT_XENO_HOST, TRAIT_GENERIC)
- START_PROCESSING(SSobj, src)
owner.med_hud_set_status()
INVOKE_ASYNC(src, .proc/AddInfectionImages, owner)
/obj/item/organ/body_egg/Remove(var/mob/living/carbon/M, special = 0)
- STOP_PROCESSING(SSobj, src)
if(owner)
REMOVE_TRAIT(owner, TRAIT_XENO_HOST, TRAIT_GENERIC)
owner.med_hud_set_status()
INVOKE_ASYNC(src, .proc/RemoveInfectionImages, owner)
..()
-/obj/item/organ/body_egg/process()
+/obj/item/organ/body_egg/on_death()
+ . = ..()
if(!owner)
return
- if(!(src in owner.internal_organs))
- Remove(owner)
- return
egg_process()
+/obj/item/organ/body_egg/on_life()
+ . = ..()
+ egg_process()
+
+
/obj/item/organ/body_egg/proc/egg_process()
return
diff --git a/code/game/objects/items/cards_ids.dm b/code/game/objects/items/cards_ids.dm
index 96bbe759ca..05ffcbf2fd 100644
--- a/code/game/objects/items/cards_ids.dm
+++ b/code/game/objects/items/cards_ids.dm
@@ -93,34 +93,22 @@
/obj/item/card/emag/afterattack(atom/target, mob/user, proximity)
. = ..()
var/atom/A = target
- if(!proximity && prox_check)
+ if(!proximity && prox_check || !(isobj(A) || issilicon(A) || isbot(A) || isdrone(A)))
+ return
+ if(istype(A, /obj/item/storage) && !(istype(A, /obj/item/storage/lockbox) || istype(A, /obj/item/storage/pod)))
return
- //Citadel changes: modular code misfiring, so we're bypassing into main code.
if(!uses)
user.visible_message("[src] emits a weak spark. It's burnt out!")
playsound(src, 'sound/effects/light_flicker.ogg', 100, 1)
return
else if(uses <= 3)
playsound(src, 'sound/effects/light_flicker.ogg', 30, 1) //Tiiiiiiny warning sound to let ya know your emag's almost dead
-
- if(isturf(A))
+ if(!A.emag_act(user))
return
- if(istype(A,/obj/item/storage/lockbox) || istype(A, /obj/item/storage/pod))
- A.emag_act(user)
- uses = max(uses - 1, 0)
- if(!uses)
- user.visible_message("[src] fizzles and sparks. It seems like it's out of charges.")
- playsound(src, 'sound/effects/light_flicker.ogg', 100, 1)
- if(istype(A,/obj/item/storage))
- return
- if(!(isobj(A) || issilicon(A) || isbot(A) || isdrone(A)))
- return
- else
- A.emag_act(user)
- uses = max(uses - 1, 0)
- if(!uses)
- user.visible_message("[src] fizzles and sparks. It seems like it's out of charges.")
- playsound(src, 'sound/effects/light_flicker.ogg', 100, 1)
+ uses = max(uses - 1, 0)
+ if(!uses)
+ user.visible_message("[src] fizzles and sparks. It seems like it's out of charges.")
+ playsound(src, 'sound/effects/light_flicker.ogg', 100, 1)
/obj/item/card/emagfake
diff --git a/code/game/objects/items/chrono_eraser.dm b/code/game/objects/items/chrono_eraser.dm
index 344e7c5472..5db5aa416e 100644
--- a/code/game/objects/items/chrono_eraser.dm
+++ b/code/game/objects/items/chrono_eraser.dm
@@ -48,7 +48,7 @@
icon_state = "chronogun"
item_state = "chronogun"
w_class = WEIGHT_CLASS_NORMAL
- item_flags = NODROP | DROPDEL
+ item_flags = DROPDEL
ammo_type = list(/obj/item/ammo_casing/energy/chrono_beam)
can_charge = 0
fire_delay = 50
@@ -58,6 +58,7 @@
/obj/item/gun/energy/chrono_gun/Initialize()
. = ..()
+ ADD_TRAIT(src, TRAIT_NODROP, CHRONO_GUN_TRAIT)
if(istype(loc, /obj/item/chrono_eraser))
TED = loc
else //admin must have spawned it
@@ -150,8 +151,8 @@
gun = loc
. = ..()
-
-
+
+
/obj/effect/chrono_field
diff --git a/code/game/objects/items/cigs_lighters.dm b/code/game/objects/items/cigs_lighters.dm
index 000c52ae43..09a128c558 100644
--- a/code/game/objects/items/cigs_lighters.dm
+++ b/code/game/objects/items/cigs_lighters.dm
@@ -764,20 +764,22 @@ CIGARETTE PACKETS ARE IN FANCY.DM
..()
/obj/item/clothing/mask/vape/emag_act(mob/user)// I WON'T REGRET WRITTING THIS, SURLY.
- if(screw)
- if(!(obj_flags & EMAGGED))
- cut_overlays()
- obj_flags |= EMAGGED
- super = FALSE
- to_chat(user, "You maximize the voltage of [src].")
- add_overlay("vapeopen_high")
- var/datum/effect_system/spark_spread/sp = new /datum/effect_system/spark_spread //for effect
- sp.set_up(5, 1, src)
- sp.start()
- else
- to_chat(user, "[src] is already emagged!")
- else
+ . = ..()
+ if(!screw)
to_chat(user, "You need to open the cap to do that.")
+ return
+ if(obj_flags & EMAGGED)
+ to_chat(user, "[src] is already emagged!")
+ return
+ cut_overlays()
+ obj_flags |= EMAGGED
+ super = FALSE
+ to_chat(user, "You maximize the voltage of [src].")
+ add_overlay("vapeopen_high")
+ var/datum/effect_system/spark_spread/sp = new /datum/effect_system/spark_spread //for effect
+ sp.set_up(5, 1, src)
+ sp.start()
+ return TRUE
/obj/item/clothing/mask/vape/attack_self(mob/user)
if(reagents.total_volume > 0)
diff --git a/code/game/objects/items/circuitboards/computer_circuitboards.dm b/code/game/objects/items/circuitboards/computer_circuitboards.dm
index a1ee62e2eb..ac7879f9ec 100644
--- a/code/game/objects/items/circuitboards/computer_circuitboards.dm
+++ b/code/game/objects/items/circuitboards/computer_circuitboards.dm
@@ -59,7 +59,7 @@
name = "Department Management Console (Computer Board)"
build_path = /obj/machinery/computer/card/minor
var/target_dept = 1
- var/list/dept_list = list("General","Security","Medical","Science","Engineering")
+ var/list/dept_list = list("Civilian","Security","Medical","Science","Engineering","Cargo")
/obj/item/circuitboard/computer/card/minor/attackby(obj/item/I, mob/user, params)
if(istype(I, /obj/item/screwdriver))
@@ -110,6 +110,7 @@
/obj/item/circuitboard/computer/cloning
name = "Cloning (Computer Board)"
build_path = /obj/machinery/computer/cloning
+ var/list/records = list()
/obj/item/circuitboard/computer/prototype_cloning
name = "Prototype Cloning (Computer Board)"
@@ -161,10 +162,11 @@
/obj/item/circuitboard/computer/prisoner
name = "Prisoner Management Console (Computer Board)"
- build_path = /obj/machinery/computer/prisoner
+ build_path = /obj/machinery/computer/prisoner/management
+
/obj/item/circuitboard/computer/gulag_teleporter_console
name = "Labor Camp teleporter console (Computer Board)"
- build_path = /obj/machinery/computer/gulag_teleporter_computer
+ build_path = /obj/machinery/computer/prisoner/gulag_teleporter_computer
/obj/item/circuitboard/computer/rdconsole/production
name = "R&D Console Production Only (Computer Board)"
@@ -216,10 +218,13 @@
to_chat(user, "The spectrum chip is unresponsive.")
/obj/item/circuitboard/computer/cargo/emag_act(mob/living/user)
- if(!(obj_flags & EMAGGED))
- contraband = TRUE
- obj_flags |= EMAGGED
- to_chat(user, "You adjust [src]'s routing and receiver spectrum, unlocking special supplies and contraband.")
+ . = ..()
+ if(obj_flags & EMAGGED)
+ return
+ contraband = TRUE
+ obj_flags |= EMAGGED
+ to_chat(user, "You adjust [src]'s routing and receiver spectrum, unlocking special supplies and contraband.")
+ return TRUE
/obj/item/circuitboard/computer/cargo/express
name = "Express Supply Console (Computer Board)"
@@ -233,8 +238,12 @@
obj_flags &= ~EMAGGED
/obj/item/circuitboard/computer/cargo/express/emag_act(mob/living/user)
- to_chat(user, "You change the routing protocols, allowing the Drop Pod to land anywhere on the station.")
- obj_flags |= EMAGGED
+ . = SEND_SIGNAL(src, COMSIG_ATOM_EMAG_ACT)
+ if(obj_flags & EMAGGED)
+ return
+ to_chat(user, "You change the routing protocols, allowing the Drop Pod to land anywhere on the station.")
+ obj_flags |= EMAGGED
+ return TRUE
/obj/item/circuitboard/computer/cargo/request
name = "Supply Request Console (Computer Board)"
@@ -365,4 +374,4 @@
/obj/item/circuitboard/computer/nanite_cloud_controller
name = "Nanite Cloud Control (Computer Board)"
- build_path = /obj/machinery/computer/nanite_cloud_controller
\ No newline at end of file
+ build_path = /obj/machinery/computer/nanite_cloud_controller
diff --git a/code/game/objects/items/circuitboards/machine_circuitboards.dm b/code/game/objects/items/circuitboards/machine_circuitboards.dm
index 2a01a7e18f..639d570462 100644
--- a/code/game/objects/items/circuitboards/machine_circuitboards.dm
+++ b/code/game/objects/items/circuitboards/machine_circuitboards.dm
@@ -399,6 +399,7 @@
/obj/machinery/smartfridge/food = "food",
/obj/machinery/smartfridge/drinks = "drinks",
/obj/machinery/smartfridge/extract = "slimes",
+ /obj/machinery/smartfridge/organ = "organs",
/obj/machinery/smartfridge/chemistry = "chems",
/obj/machinery/smartfridge/chemistry/virology = "viruses",
/obj/machinery/smartfridge/disks = "disks")
diff --git a/code/game/objects/items/crayons.dm b/code/game/objects/items/crayons.dm
index 99d6c874e8..102c6a8eed 100644
--- a/code/game/objects/items/crayons.dm
+++ b/code/game/objects/items/crayons.dm
@@ -1,6 +1,9 @@
#define RANDOM_GRAFFITI "Random Graffiti"
#define RANDOM_LETTER "Random Letter"
+#define RANDOM_PUNCTUATION "Random Punctuation"
#define RANDOM_NUMBER "Random Number"
+#define RANDOM_SYMBOL "Random Symbol"
+#define RANDOM_DRAWING "Random Drawing"
#define RANDOM_ORIENTED "Random Oriented"
#define RANDOM_RUNE "Random Rune"
#define RANDOM_ANY "Random Anything"
@@ -32,16 +35,16 @@
var/drawtype
var/text_buffer = ""
- var/list/graffiti = list("amyjon","face","matt","revolution","engie","guy","end","dwarf","uboa","body","cyka","arrow","star","poseur tag","prolizard","antilizard", "tile") //cit edit
- var/list/letters = list("a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z")
- var/list/numerals = list("0","1","2","3","4","5","6","7","8","9")
- var/list/oriented = list("arrow","body") // These turn to face the same way as the drawer
- var/list/runes = list("rune1","rune2","rune3","rune4","rune5","rune6")
- var/list/randoms = list(RANDOM_ANY, RANDOM_RUNE, RANDOM_ORIENTED,
- RANDOM_NUMBER, RANDOM_GRAFFITI, RANDOM_LETTER)
- var/list/graffiti_large_h = list("yiffhell", "secborg", "paint")
+ var/static/list/graffiti = list("amyjon","face","matt","revolution","engie","guy","end","dwarf","uboa","body","cyka","star","poseur tag","prolizard","antilizard", "tile")
+ var/static/list/symbols = list("danger","firedanger","electricdanger","biohazard","radiation","safe","evac","space","med","trade","shop","food","peace","like","skull","nay","heart","credit")
+ var/static/list/drawings = list("smallbrush","brush","largebrush","splatter","snake","stickman","carp","ghost","clown","taser","disk","fireaxe","toolbox","corgi","cat","toilet","blueprint","beepsky","scroll","bottle","shotgun")
+ var/static/list/oriented = list("arrow","line","thinline","shortline","body","chevron","footprint","clawprint","pawprint") // These turn to face the same way as the drawer
+ var/static/list/runes = list("rune1","rune2","rune3","rune4","rune5","rune6")
+ var/static/list/randoms = list(RANDOM_ANY, RANDOM_RUNE, RANDOM_ORIENTED,
+ RANDOM_NUMBER, RANDOM_GRAFFITI, RANDOM_LETTER, RANDOM_SYMBOL, RANDOM_PUNCTUATION, RANDOM_DRAWING)
+ var/static/list/graffiti_large_h = list("yiffhell", "secborg", "paint")
- var/list/all_drawables
+ var/static/list/all_drawables = graffiti + symbols + drawings + oriented + runes + graffiti_large_h
var/paint_mode = PAINT_NORMAL
@@ -54,8 +57,6 @@
var/instant = FALSE
var/self_contained = TRUE // If it deletes itself when it is empty
- var/list/validSurfaces = list(/turf/open/floor)
-
var/edible = TRUE // That doesn't mean eating it is a good idea
var/list/reagent_contents = list("nutriment" = 1)
@@ -71,6 +72,9 @@
var/datum/team/gang/gang //For marking territory.
var/gang_tag_delay = 30 //this is the delay for gang mode tag applications on anything that gang = true on.
+/obj/item/toy/crayon/proc/isValidSurface(surface)
+ return istype(surface, /turf/open/floor)
+
/obj/item/toy/crayon/suicide_act(mob/user)
user.visible_message("[user] is jamming [src] up [user.p_their()] nose and into [user.p_their()] brain. It looks like [user.p_theyre()] trying to commit suicide!")
return (BRUTELOSS|OXYLOSS)
@@ -81,7 +85,6 @@
if(name == "crayon")
name = "[item_color] crayon"
- all_drawables = graffiti + letters + numerals + oriented + runes + graffiti_large_h
drawtype = pick(all_drawables)
refill()
@@ -153,55 +156,62 @@
to_chat(user, "The cap on [src] is now [is_capped ? "on" : "off"].")
update_icon()
-/obj/item/toy/crayon/ui_data()
- var/list/data = list()
- data["drawables"] = list()
- var/list/D = data["drawables"]
+/obj/item/toy/crayon/proc/staticDrawables()
+
+ . = list()
var/list/g_items = list()
- D += list(list("name" = "Graffiti", "items" = g_items))
+ . += list(list("name" = "Graffiti", "items" = g_items))
for(var/g in graffiti)
g_items += list(list("item" = g))
var/list/glh_items = list()
- D += list(list("name" = "Graffiti Large Horizontal", "items" = glh_items))
+ . += list(list("name" = "Graffiti Large Horizontal", "items" = glh_items))
for(var/glh in graffiti_large_h)
glh_items += list(list("item" = glh))
- var/list/L_items = list()
- D += list(list("name" = "Letters", "items" = L_items))
- for(var/L in letters)
- L_items += list(list("item" = L))
+ var/list/S_items = list()
+ . += list(list("name" = "Symbols", "items" = S_items))
+ for(var/S in symbols)
+ S_items += list(list("item" = S))
- var/list/N_items = list()
- D += list(list(name = "Numerals", "items" = N_items))
- for(var/N in numerals)
- N_items += list(list("item" = N))
+ var/list/D_items = list()
+ . += list(list("name" = "Drawings", "items" = D_items))
+ for(var/D in drawings)
+ D_items += list(list("item" = D))
var/list/O_items = list()
- D += list(list(name = "Oriented", "items" = O_items))
+ . += list(list(name = "Oriented", "items" = O_items))
for(var/O in oriented)
O_items += list(list("item" = O))
var/list/R_items = list()
- D += list(list(name = "Runes", "items" = R_items))
+ . += list(list(name = "Runes", "items" = R_items))
for(var/R in runes)
R_items += list(list("item" = R))
var/list/rand_items = list()
- D += list(list(name = "Random", "items" = rand_items))
+ . += list(list(name = "Random", "items" = rand_items))
for(var/i in randoms)
rand_items += list(list("item" = i))
- data["selected_stencil"] = drawtype
- data["text_buffer"] = text_buffer
- data["has_cap"] = has_cap
- data["is_capped"] = is_capped
- data["can_change_colour"] = can_change_colour
- data["current_colour"] = paint_color
+/obj/item/toy/crayon/ui_data()
- return data
+ var/static/list/crayon_drawables
+
+ if (!crayon_drawables)
+ crayon_drawables = staticDrawables()
+
+ . = list()
+ .["drawables"] = crayon_drawables
+ .["selected_stencil"] = drawtype
+ .["text_buffer"] = text_buffer
+
+ .["has_cap"] = has_cap
+ .["is_capped"] = is_capped
+ .["can_change_colour"] = can_change_colour
+ .["current_colour"] = paint_color
/obj/item/toy/crayon/ui_act(action, list/params)
if(..())
@@ -216,6 +226,7 @@
if(stencil in all_drawables + randoms)
drawtype = stencil
. = TRUE
+ text_buffer = ""
if(stencil in graffiti_large_h)
paint_mode = PAINT_LARGE_HORIZONTAL
text_buffer = ""
@@ -223,8 +234,13 @@
paint_mode = PAINT_NORMAL
if("select_colour")
if(can_change_colour)
- paint_color = input(usr,"","Choose Color",paint_color) as color|null
- . = TRUE
+ var/chosen_colour = input(usr,"","Choose Color",paint_color) as color|null
+
+ if (!isnull(chosen_colour))
+ paint_color = chosen_colour
+ . = TRUE
+ else
+ . = FALSE
if("enter_text")
var/txt = stripped_input(usr,"Choose what to write.",
"Scribbles",default = text_buffer)
@@ -235,18 +251,16 @@
update_icon()
/obj/item/toy/crayon/proc/crayon_text_strip(text)
- var/list/base = string2charlist(lowertext(text))
- var/list/out = list()
- for(var/a in base)
- if(a in (letters|numerals))
- out += a
- return jointext(out,"")
+ var/static/regex/crayon_r = new /regex(@"[^\w!?,.=%#&+\/\-]")
+ return replacetext(lowertext(text), crayon_r, "")
/obj/item/toy/crayon/afterattack(atom/target, mob/user, proximity, params)
. = ..()
if(!proximity || !check_allowed_items(target))
return
+ var/static/list/punctuation = list("!","?",".",",","/","+","-","=","%","#","&")
+
var/cost = 1
if(paint_mode == PAINT_LARGE_HORIZONTAL)
cost = 5
@@ -264,13 +278,19 @@
if(istype(target, /obj/effect/decal/cleanable))
target = target.loc
- if(!is_type_in_list(target,validSurfaces))
+ if(!isValidSurface(target))
return
var/drawing = drawtype
switch(drawtype)
if(RANDOM_LETTER)
- drawing = pick(letters)
+ drawing = ascii2text(rand(97, 122)) // a-z
+ if(RANDOM_PUNCTUATION)
+ drawing = pick(punctuation)
+ if(RANDOM_SYMBOL)
+ drawing = pick(symbols)
+ if(RANDOM_DRAWING)
+ drawing = pick(drawings)
if(RANDOM_GRAFFITI)
drawing = pick(graffiti)
if(RANDOM_RUNE)
@@ -278,17 +298,24 @@
if(RANDOM_ORIENTED)
drawing = pick(oriented)
if(RANDOM_NUMBER)
- drawing = pick(numerals)
+ drawing = ascii2text(rand(48, 57)) // 0-9
if(RANDOM_ANY)
drawing = pick(all_drawables)
var/temp = "rune"
- if(drawing in letters)
+ var/ascii = (length(drawing) == 1) ? TRUE : FALSE
+ if(ascii && is_alpha(drawing))
temp = "letter"
- else if(drawing in graffiti)
- temp = "graffiti"
- else if(drawing in numerals)
+ else if(ascii && is_digit(drawing))
temp = "number"
+ else if(drawing in punctuation)
+ temp = "punctuation mark"
+ else if(drawing in symbols)
+ temp = "symbol"
+ else if(drawing in drawings)
+ temp = "drawing"
+ else if(drawing in graffiti|oriented)
+ temp = "graffiti"
// If a gang member is using a gang spraycan, it'll behave differently
var/gang_mode = FALSE
@@ -338,7 +365,7 @@
return
if(length(text_buffer))
- drawing = copytext(text_buffer,1,2)
+ drawing = text_buffer[1]
var/list/turf/affected_turfs = list()
@@ -362,7 +389,7 @@
if(PAINT_LARGE_HORIZONTAL)
var/turf/left = locate(target.x-1,target.y,target.z)
var/turf/right = locate(target.x+1,target.y,target.z)
- if(is_type_in_list(left, validSurfaces) && is_type_in_list(right, validSurfaces))
+ if(isValidSurface(left) && isValidSurface(right))
var/obj/effect/decal/cleanable/crayon/C = new(left, paint_color, drawing, temp, graf_rot, PAINT_LARGE_HORIZONTAL_ICON)
C.add_hiddenprint(user)
affected_turfs += left
@@ -377,8 +404,9 @@
else
to_chat(user, "You spray a [temp] on \the [target.name]")
- if(length(text_buffer))
+ if(length(text_buffer) > 1)
text_buffer = copytext(text_buffer,2)
+ SStgui.update_uis(src)
if(post_noise)
audible_message("You hear spraying.")
@@ -516,7 +544,7 @@
charges = -1
-/obj/item/toy/crayon/rainbow/afterattack(atom/target, mob/user, proximity)
+/obj/item/toy/crayon/rainbow/afterattack(atom/target, mob/user, proximity, params)
paint_color = rgb(rand(0,255), rand(0,255), rand(0,255))
. = ..()
@@ -591,12 +619,14 @@
can_change_colour = TRUE
gang = TRUE //Gang check is true for all things upon the honored hierarchy of spraycans, except those that are FALSE.
- validSurfaces = list(/turf/open/floor, /turf/closed/wall)
reagent_contents = list("welding_fuel" = 1, "ethanol" = 1)
pre_noise = TRUE
post_noise = FALSE
+/obj/item/toy/crayon/spraycan/isValidSurface(surface)
+ return (istype(surface, /turf/open/floor) || istype(surface, /turf/closed/wall))
+
/obj/item/toy/crayon/spraycan/suicide_act(mob/user)
var/mob/living/carbon/human/H = user
if(is_capped || !actually_paints)
@@ -622,8 +652,8 @@
return (OXYLOSS)
-/obj/item/toy/crayon/spraycan/New()
- ..()
+/obj/item/toy/crayon/spraycan/Initialize()
+ . = ..()
// If default crayon red colour, pick a more fun spraycan colour
if(!paint_color)
paint_color = pick("#DA0000","#FF9300","#FFF200","#A8E61D","#00B7EF",
@@ -640,7 +670,7 @@
to_chat(user, "It is empty.")
to_chat(user, "Alt-click [src] to [ is_capped ? "take the cap off" : "put the cap on"].")
-/obj/item/toy/crayon/spraycan/afterattack(atom/target, mob/user, proximity)
+/obj/item/toy/crayon/spraycan/afterattack(atom/target, mob/user, proximity, params)
if(!proximity)
return
@@ -656,8 +686,7 @@
playsound(user.loc, 'sound/effects/spray.ogg', 25, 1, 5)
var/mob/living/carbon/C = target
- user.visible_message("[user] sprays [src] into the face of [target]!")
- to_chat(target, "[user] sprays [src] into your face!")
+ C.visible_message("[user] sprays [src] into the face of [C]!", "[user] sprays [src] into your face!")
if(C.client)
C.blur_eyes(3)
@@ -678,13 +707,20 @@
return
- if(istype(target, /obj/structure/window))
+ if(isobj(target))
if(actually_paints)
+ if(color_hex2num(paint_color) < 350 && !istype(target, /obj/structure/window) && !istype(target, /obj/effect/decal/cleanable/crayon)) //Colors too dark are rejected
+ to_chat(usr, "A color that dark on an object like this? Surely not...")
+ return FALSE
+
target.add_atom_colour(paint_color, WASHABLE_COLOUR_PRIORITY)
- if(color_hex2num(paint_color) < 255)
- target.set_opacity(255)
- else
- target.set_opacity(initial(target.opacity))
+
+ if(istype(target, /obj/structure/window))
+ if(color_hex2num(paint_color) < 255)
+ target.set_opacity(255)
+ else
+ target.set_opacity(initial(target.opacity))
+
. = use_charges(user, 2)
var/fraction = min(1, . / reagents.maximum_volume)
reagents.reaction(target, TOUCH, fraction * volume_multiplier)
@@ -692,6 +728,7 @@
if(pre_noise || post_noise)
playsound(user.loc, 'sound/effects/spray.ogg', 5, 1, 5)
+ user.visible_message("[user] coats [target] with spray paint!", "You coat [target] with spray paint.")
return
. = ..()
@@ -709,7 +746,7 @@
desc = "A metallic container containing shiny synthesised paint."
charges = -1
-/obj/item/toy/crayon/spraycan/borg/afterattack(atom/target,mob/user,proximity)
+/obj/item/toy/crayon/spraycan/borg/afterattack(atom/target,mob/user,proximity, params)
var/diff = ..()
if(!iscyborg(user))
to_chat(user, "How did you get this?")
@@ -754,7 +791,9 @@
reagent_contents = list("lube" = 1, "banana" = 1)
volume_multiplier = 5
- validSurfaces = list(/turf/open/floor)
+
+/obj/item/toy/crayon/spraycan/lubecan/isValidSurface(surface)
+ return istype(surface, /turf/open/floor)
/obj/item/toy/crayon/spraycan/mimecan
name = "silent spraycan"
@@ -792,8 +831,16 @@
if(user.mind && user.mind.has_antag_datum(/datum/antagonist/gang) || isobserver(user))
to_chat(user, "This spraycan has been specially modified with a stage 2 nozzle kit, making it faster.")
+/obj/item/toy/crayon/spraycan/infinite
+ name = "infinite spraycan"
+ charges = -1
+ desc = "Now with 30% more bluespace technology."
+
#undef RANDOM_GRAFFITI
#undef RANDOM_LETTER
+#undef RANDOM_PUNCTUATION
+#undef RANDOM_SYMBOL
+#undef RANDOM_DRAWING
#undef RANDOM_NUMBER
#undef RANDOM_ORIENTED
#undef RANDOM_RUNE
diff --git a/code/game/objects/items/defib.dm b/code/game/objects/items/defib.dm
index 55e75b3992..a4f286bf88 100644
--- a/code/game/objects/items/defib.dm
+++ b/code/game/objects/items/defib.dm
@@ -140,12 +140,10 @@
return ..()
/obj/item/defibrillator/emag_act(mob/user)
- if(safety)
- safety = FALSE
- to_chat(user, "You silently disable [src]'s safety protocols with the cryptographic sequencer.")
- else
- safety = TRUE
- to_chat(user, "You silently enable [src]'s safety protocols with the cryptographic sequencer.")
+ . = ..()
+ safety = !safety
+ to_chat(user, "You silently [safety ? "enable" : "disable"] [src]'s safety protocols with the cryptographic sequencer.")
+ return TRUE
/obj/item/defibrillator/emp_act(severity)
. = ..()
@@ -442,9 +440,20 @@
do_help(H, user)
-/obj/item/twohanded/shockpaddles/proc/can_defib(mob/living/carbon/H)
+/obj/item/twohanded/shockpaddles/proc/can_defib(mob/living/carbon/H) //Our code here is different than tg, if it breaks in testing; BUG_PROBABLE_CAUSE
+ var/obj/item/organ/heart = H.getorgan(/obj/item/organ/heart)
+ if(H.suiciding || H.hellbound || HAS_TRAIT(H, TRAIT_HUSK))
+ return
+ if((world.time - H.timeofdeath) > tlimit)
+ return
+ if((H.getBruteLoss() >= MAX_REVIVE_BRUTE_DAMAGE) || (H.getFireLoss() >= MAX_REVIVE_FIRE_DAMAGE))
+ return
+ if(!heart || (heart.organ_flags & ORGAN_FAILING))
+ return
var/obj/item/organ/brain/BR = H.getorgan(/obj/item/organ/brain)
- return (!H.suiciding && !(HAS_TRAIT(H, TRAIT_NOCLONE)) && !H.hellbound && ((world.time - H.timeofdeath) < tlimit) && (H.getBruteLoss() < 180) && (H.getFireLoss() < 180) && H.getorgan(/obj/item/organ/heart) && BR && !BR.damaged_brain)
+ if(QDELETED(BR) || BR.brain_death || (BR.organ_flags & ORGAN_FAILING) || H.suiciding)
+ return
+ return TRUE
/obj/item/twohanded/shockpaddles/proc/shock_touching(dmg, mob/H)
if(req_defib)
@@ -559,14 +568,12 @@
if(do_after(user, primetimer, target = H)) //beginning to place the paddles on patient's chest to allow some time for people to move away to stop the process
user.visible_message("[user] places [src] on [H]'s chest.", "You place [src] on [H]'s chest.")
playsound(src, 'sound/machines/defib_charge.ogg', 75, 0)
- var/tplus = world.time - H.timeofdeath
- // past this much time the patient is unrecoverable
- // (in deciseconds)
- // brain damage starts setting in on the patient after
- // some time left rotting
+ // patients rot when they are killed, and die when they are dead
+ var/tplus = world.time - H.timeofdeath //length of time spent dead
var/tloss = deathtimer
var/total_burn = 0
var/total_brute = 0
+ var/obj/item/organ/heart = H.getorgan(/obj/item/organ/heart)
if(do_after(user, primetimer2, target = H)) //placed on chest and short delay to shock for dramatic effect, revive time is 5sec total
for(var/obj/item/carried_item in H.contents)
if(istype(carried_item, /obj/item/clothing/suit/space))
@@ -591,16 +598,26 @@
failed = "[req_defib ? "[defib]" : "[src]"] buzzes: Resuscitation failed - Patient's soul appears to be on another plane of existence. Further attempts futile."
else if (tplus > tlimit)
failed = "[req_defib ? "[defib]" : "[src]"] buzzes: Resuscitation failed - Body has decayed for too long. Further attempts futile."
- else if (!H.getorgan(/obj/item/organ/heart))
+ else if (!heart)
failed = "[req_defib ? "[defib]" : "[src]"] buzzes: Resuscitation failed - Patient's heart is missing."
+ else if (heart.organ_flags & ORGAN_FAILING)
+ failed = "[req_defib ? "[defib]" : "[src]"] buzzes: Resuscitation failed - Patient's heart too damaged."
else if(total_burn >= 180 || total_brute >= 180)
failed = "[req_defib ? "[defib]" : "[src]"] buzzes: Resuscitation failed - Severe tissue damage makes recovery of patient impossible via defibrillator. Further attempts futile."
else if(H.get_ghost())
failed = "[req_defib ? "[defib]" : "[src]"] buzzes: Resuscitation failed - No activity in patient's brain. Further attempts may be successful."
else
var/obj/item/organ/brain/BR = H.getorgan(/obj/item/organ/brain)
- if(!BR || BR.damaged_brain)
- failed = "[req_defib ? "[defib]" : "[src]"] buzzes: Resuscitation failed - Patient's brain is missing or damaged beyond point of no return. Further attempts futile."
+ if(BR) //BUG_PROBABLE_CAUSE - slight difference between us and tg
+ if(BR.organ_flags & ORGAN_FAILING)
+ failed = "[req_defib ? "[defib]" : "[src]"] buzzes: Resuscitation failed - Patient's brain tissue is damaged making recovery of patient impossible via defibrillator. Further attempts futile."
+ if(BR.brain_death)
+ failed = "[req_defib ? "[defib]" : "[src]"] buzzes: Resuscitation failed - Patient's brain damaged beyond point of no return. Further attempts futile."
+ if(H.suiciding || BR.brainmob?.suiciding)
+ failed = "[req_defib ? "[defib]" : "[src]"] buzzes: Resuscitation failed - No intelligence pattern can be detected in patient's brain. Further attempts futile."
+ else
+ failed = "[req_defib ? "[defib]" : "[src]"] buzzes: Resuscitation failed - Patient's brain is missing. Further attempts futile."
+
if(failed)
user.visible_message(failed)
@@ -625,7 +642,7 @@
H.Jitter(100)
SEND_SIGNAL(H, COMSIG_LIVING_MINOR_SHOCK)
if(tplus > tloss)
- H.adjustBrainLoss( max(0, min(99, ((tlimit - tplus) / tlimit * 100))), 150)
+ H.adjustOrganLoss(ORGAN_SLOT_BRAIN, max(0, min(99, ((tlimit - tplus) / tlimit * 100))), 150)
log_combat(user, H, "revived", defib)
if(req_defib)
if(defib.healdisk)
@@ -643,7 +660,11 @@
playsound(src, 'sound/machines/defib_failed.ogg', 50, 0)
else if(H.undergoing_cardiac_arrest())
H.set_heartattack(FALSE)
- user.visible_message("[req_defib ? "[defib]" : "[src]"] pings: Patient's heart is now beating again.")
+ if(!(heart.organ_flags & ORGAN_FAILING))
+ H.set_heartattack(FALSE)
+ user.visible_message("[req_defib ? "[defib]" : "[src]"] pings: Patient's heart is now beating again.")
+ else
+ user.visible_message("[req_defib ? "[defib]" : "[src]"] buzzes: Resuscitation failed, heart damage detected.")
playsound(src, 'sound/machines/defib_zap.ogg', 50, 1, -1)
diff --git a/code/game/objects/items/devices/PDA/PDA.dm b/code/game/objects/items/devices/PDA/PDA.dm
index 45baa542d4..b0494539ce 100644
--- a/code/game/objects/items/devices/PDA/PDA.dm
+++ b/code/game/objects/items/devices/PDA/PDA.dm
@@ -714,6 +714,7 @@ GLOBAL_LIST_EMPTY(PDAs)
return
if((last_text && world.time < last_text + 10) || (everyone && last_everyone && world.time < last_everyone + PDA_SPAM_DELAY))
return
+ var/emoji_message = emoji_parse(message)
if(prob(1))
message += "\nSent from my PDA"
// Send the signal
@@ -734,7 +735,8 @@ GLOBAL_LIST_EMPTY(PDAs)
"name" = "[owner]",
"job" = "[ownjob]",
"message" = message,
- "targets" = string_targets
+ "targets" = string_targets,
+ "emoji_message" = emoji_message
))
if (picture)
signal.data["photo"] = picture
@@ -751,13 +753,13 @@ GLOBAL_LIST_EMPTY(PDAs)
// Log it in our logs
tnote += "→ To [target_text]: [signal.format_message()] "
// Show it to ghosts
- var/ghost_message = "[owner] PDA Message --> [target_text]: [signal.format_message()]"
+ var/ghost_message = "[owner] PDA Message --> [target_text]: [signal.format_message(TRUE)]"
for(var/mob/M in GLOB.player_list)
if(isobserver(M) && M.client && (M.client.prefs.chat_toggles & CHAT_GHOSTPDA))
to_chat(M, "[FOLLOW_LINK(M, user)] [ghost_message]")
// Log in the talk log
user.log_talk(message, LOG_PDA, tag="PDA: [initial(name)] to [target_text]")
- to_chat(user, "Message sent to [target_text]: \"[message]\"")
+ to_chat(user, "Message sent to [target_text]: \"[emoji_message]\"")
if (!silent)
playsound(src, 'sound/machines/terminal_success.ogg', 15, 1)
// Reset the photo
@@ -787,7 +789,7 @@ GLOBAL_LIST_EMPTY(PDAs)
hrefstart = ""
hrefend = ""
- to_chat(L, "[icon2html(src)] Message from [hrefstart][signal.data["name"]] ([signal.data["job"]])[hrefend], [signal.format_message()] (Reply)")
+ to_chat(L, "[icon2html(src)] Message from [hrefstart][signal.data["name"]] ([signal.data["job"]])[hrefend], [signal.format_message(TRUE)] (Reply)")
update_icon(TRUE)
diff --git a/code/game/objects/items/devices/PDA/cart.dm b/code/game/objects/items/devices/PDA/cart.dm
index bf352e8eb7..3885a1f4d9 100644
--- a/code/game/objects/items/devices/PDA/cart.dm
+++ b/code/game/objects/items/devices/PDA/cart.dm
@@ -498,6 +498,23 @@ Code:
else
menu += "[ldat]"
+ menu += "
Pimpin' Ride:
"
+
+ ldat = null
+ for (var/obj/vehicle/ridden/janicart/M in world)
+ var/turf/ml = get_turf(M)
+
+ if(ml)
+ if (ml.z != cl.z)
+ continue
+ var/direction = get_dir(src, M)
+ ldat += "Ride - \[[ml.x],[ml.y] ([uppertext(dir2text(direction))])\] "
+
+ if (!ldat)
+ menu += "None"
+ else
+ menu += "[ldat]"
+
menu += "
Located Janitorial Cart:
"
ldat = null
diff --git a/code/game/objects/items/devices/dogborg_sleeper.dm b/code/game/objects/items/devices/dogborg_sleeper.dm
index 7f3c7f3bfc..c1a9136f76 100644
--- a/code/game/objects/items/devices/dogborg_sleeper.dm
+++ b/code/game/objects/items/devices/dogborg_sleeper.dm
@@ -211,7 +211,7 @@
data["occupant"]["toxLoss"] = mob_occupant.getToxLoss()
data["occupant"]["fireLoss"] = mob_occupant.getFireLoss()
data["occupant"]["cloneLoss"] = mob_occupant.getCloneLoss()
- data["occupant"]["brainLoss"] = mob_occupant.getBrainLoss()
+ data["occupant"]["brainLoss"] = mob_occupant.getOrganLoss(ORGAN_SLOT_BRAIN)
data["occupant"]["reagents"] = list()
if(mob_occupant.reagents.reagent_list.len)
for(var/datum/reagent/R in mob_occupant.reagents.reagent_list)
@@ -544,4 +544,26 @@
user.visible_message("[hound.name]'s garbage processor groans lightly as [trashman] slips inside.", "Your garbage compactor groans lightly as [trashman] slips inside.")
playsound(hound, 'sound/effects/bin_close.ogg', 80, 1)
return
+ else if(issilicon(target))
+ var/mob/living/silicon/trashbot = target
+ if (!trashbot.devourable)
+ to_chat(user, "[target] registers an error code to your [src]")
+ return
+ if(patient)
+ to_chat(user,"Your [src] is already occupied.")
+ return
+ if(trashbot.buckled)
+ to_chat(user,"[trashbot] is buckled and can not be put into your [src].")
+ return
+ user.visible_message("[hound.name] is ingesting [trashbot] into their [src].", "You start ingesting [trashbot] into your [src.name]...")
+ if(do_after(user, 30, target = trashbot) && !patient && !trashbot.buckled && length(contents) < max_item_count)
+ if(!in_range(src, trashbot)) //Proximity is probably old news by now, do a new check.
+ return //If they moved away, you can't eat them.
+ trashbot.forceMove(src)
+ trashbot.reset_perspective(src)
+ update_gut()
+ user.visible_message("[hound.name]'s garbage processor groans lightly as [trashbot] slips inside.", "Your garbage compactor groans lightly as [trashbot] slips inside.")
+ playsound(hound, 'sound/effects/bin_close.ogg', 80, 1)
+ return
+
return
diff --git a/code/game/objects/items/devices/flashlight.dm b/code/game/objects/items/devices/flashlight.dm
index d8b539ac27..b937f961c0 100644
--- a/code/game/objects/items/devices/flashlight.dm
+++ b/code/game/objects/items/devices/flashlight.dm
@@ -96,7 +96,7 @@
if(BODY_ZONE_PRECISE_MOUTH)
- if((M.head && M.head.flags_cover & HEADCOVERSMOUTH) || (M.wear_mask && M.wear_mask.flags_cover & MASKCOVERSMOUTH))
+ if(M.is_mouth_covered())
to_chat(user, "You're going to need to remove that [(M.head && M.head.flags_cover & HEADCOVERSMOUTH) ? "helmet" : "mask"] first.")
return
@@ -512,17 +512,6 @@
name = "pink glowstick"
color = LIGHT_COLOR_PINK
-/obj/item/flashlight/glowstick/random
- name = "random colored glowstick"
- icon_state = "random_glowstick"
- color = null
-
-/obj/item/flashlight/glowstick/random/Initialize()
- ..()
- var/T = pick(typesof(/obj/item/flashlight/glowstick) - /obj/item/flashlight/glowstick/random)
- new T(loc)
- return INITIALIZE_HINT_QDEL
-
/obj/item/flashlight/spotlight //invisible lighting source
name = "disco light"
desc = "Groovy..."
diff --git a/code/game/objects/items/devices/geiger_counter.dm b/code/game/objects/items/devices/geiger_counter.dm
index 7823e570e0..90cdd0386c 100644
--- a/code/game/objects/items/devices/geiger_counter.dm
+++ b/code/game/objects/items/devices/geiger_counter.dm
@@ -158,7 +158,7 @@
if(!M.radiation)
to_chat(user, "[icon2html(src, user)] Radiation levels within normal boundaries.")
else
- to_chat(user, "[icon2html(src, user)] Subject is irradiated. Radiation levels: [M.radiation].")
+ to_chat(user, "[icon2html(src, user)] Subject is irradiated. Radiation levels: [M.radiation] rad.")
if(rad_strength)
to_chat(user, "[icon2html(src, user)] Target contains radioactive contamination. Radioactive strength: [rad_strength]")
@@ -192,13 +192,15 @@
update_icon()
/obj/item/geiger_counter/emag_act(mob/user)
+ . = ..()
if(obj_flags & EMAGGED)
return
if(scanning)
to_chat(user, "Turn off [src] before you perform this action!")
- return 0
+ return
to_chat(user, "You override [src]'s radiation storing protocols. It will now generate small doses of radiation, and stored rads are now projected into creatures you scan.")
obj_flags |= EMAGGED
+ return TRUE
/obj/item/geiger_counter/cyborg
var/datum/component/mobhook
diff --git a/code/game/objects/items/devices/glue.dm b/code/game/objects/items/devices/glue.dm
index fed582d951..2c57ede706 100644
--- a/code/game/objects/items/devices/glue.dm
+++ b/code/game/objects/items/devices/glue.dm
@@ -19,11 +19,11 @@
return
if(istype(target, /obj/item))
var/obj/item/I = target
- if(I.item_flags & NODROP)
+ if(HAS_TRAIT_FROM(I, TRAIT_NODROP, GLUED_ITEM_TRAIT))
to_chat(user, "[I] is already sticky!")
return
uses -= 1
- I.item_flags |= NODROP
+ ADD_TRAIT(I, TRAIT_NODROP, GLUED_ITEM_TRAIT)
I.desc += " It looks sticky."
to_chat(user, "You smear the [I] with glue, making it incredibly sticky!")
if(uses == 0)
diff --git a/code/game/objects/items/devices/gps.dm b/code/game/objects/items/devices/gps.dm
index c362d9f7f2..9d1b670e71 100644
--- a/code/game/objects/items/devices/gps.dm
+++ b/code/game/objects/items/devices/gps.dm
@@ -156,7 +156,10 @@ GLOBAL_LIST_EMPTY(GPS_list)
icon_state = "gps-b"
gpstag = "BORG0"
desc = "A mining cyborg internal positioning system. Used as a recovery beacon for damaged cyborg assets, or a collaboration tool for mining teams."
- item_flags = NODROP
+
+/obj/item/gps/cyborg/Initialize()
+ . = ..()
+ ADD_TRAIT(src, TRAIT_NODROP, CYBORG_ITEM_TRAIT)
/obj/item/gps/internal
icon_state = null
@@ -164,8 +167,19 @@ GLOBAL_LIST_EMPTY(GPS_list)
gpstag = "Eerie Signal"
desc = "Report to a coder immediately."
invisibility = INVISIBILITY_MAXIMUM
+ var/obj/item/implant/gps/implant
-/obj/item/gps/mining/internal
+/obj/item/gps/internal/Initialize(mapload, obj/item/implant/gps/_implant)
+ . = ..()
+ implant = _implant
+
+/obj/item/gps/internal/Destroy()
+ if(implant?.imp_in)
+ qdel(implant)
+ else
+ return ..()
+
+/obj/item/gps/internal/mining
icon_state = "gps-m"
gpstag = "MINER"
desc = "A positioning system helpful for rescuing trapped or injured miners, keeping one on you at all times while mining might just save your life."
diff --git a/code/game/objects/items/devices/instruments.dm b/code/game/objects/items/devices/instruments.dm
index 661d38ce6b..5894b559ae 100644
--- a/code/game/objects/items/devices/instruments.dm
+++ b/code/game/objects/items/devices/instruments.dm
@@ -234,11 +234,18 @@
w_class = WEIGHT_CLASS_SMALL
actions_types = list(/datum/action/item_action/instrument)
-/obj/item/instrument/harmonica/speechModification(message)
+/obj/item/instrument/harmonica/proc/handle_speech(datum/source, list/speech_args)
if(song.playing && ismob(loc))
to_chat(loc, "You stop playing the harmonica to talk...")
song.playing = FALSE
- return message
+
+/obj/item/instrument/harmonica/equipped(mob/M, slot)
+ . = ..()
+ RegisterSignal(M, COMSIG_MOB_SAY, .proc/handle_speech)
+
+/obj/item/instrument/harmonica/dropped(mob/M)
+ . = ..()
+ UnregisterSignal(M, COMSIG_MOB_SAY)
/obj/item/instrument/bikehorn
name = "gilded bike horn"
diff --git a/code/game/objects/items/devices/lightreplacer.dm b/code/game/objects/items/devices/lightreplacer.dm
index 6615ebe8a8..1e31f14cfd 100644
--- a/code/game/objects/items/devices/lightreplacer.dm
+++ b/code/game/objects/items/devices/lightreplacer.dm
@@ -149,9 +149,11 @@
to_chat(user, "You fill \the [src] with lights from \the [S]. " + status_string() + "")
/obj/item/lightreplacer/emag_act()
+ . = ..()
if(obj_flags & EMAGGED)
return
Emag()
+ return TRUE
/obj/item/lightreplacer/attack_self(mob/user)
to_chat(user, status_string())
diff --git a/code/game/objects/items/devices/megaphone.dm b/code/game/objects/items/devices/megaphone.dm
index 4b244e3002..915fcac504 100644
--- a/code/game/objects/items/devices/megaphone.dm
+++ b/code/game/objects/items/devices/megaphone.dm
@@ -17,20 +17,34 @@
user.say("AAAAAAAAAAAARGHHHHH", forced="megaphone suicide")//he must have died while coding this
return OXYLOSS
-/obj/item/megaphone/get_held_item_speechspans(mob/living/carbon/user)
- if(spamcheck > world.time)
- to_chat(user, "\The [src] needs to recharge!")
+/obj/item/megaphone/equipped(mob/M, slot)
+ . = ..()
+ if (slot == SLOT_HANDS)
+ RegisterSignal(M, COMSIG_MOB_SAY, .proc/handle_speech)
else
- playsound(loc, 'sound/items/megaphone.ogg', 100, 0, 1)
- spamcheck = world.time + 50
- return voicespan
+ UnregisterSignal(M, COMSIG_MOB_SAY)
+
+/obj/item/megaphone/dropped(mob/M)
+ . = ..()
+ UnregisterSignal(M, COMSIG_MOB_SAY)
+
+/obj/item/megaphone/proc/handle_speech(mob/living/carbon/user, list/speech_args)
+ if (user.get_active_held_item() == src)
+ if(spamcheck > world.time)
+ to_chat(user, "\The [src] needs to recharge!")
+ else
+ playsound(loc, 'sound/items/megaphone.ogg', 100, 0, 1)
+ spamcheck = world.time + 50
+ speech_args[SPEECH_SPANS] |= voicespan
/obj/item/megaphone/emag_act(mob/user)
+ . = ..()
if(obj_flags & EMAGGED)
return
to_chat(user, "You overload \the [src]'s voice synthesizer.")
obj_flags |= EMAGGED
voicespan = list(SPAN_REALLYBIG, "userdanger")
+ return TRUE
/obj/item/megaphone/sec
name = "security megaphone"
diff --git a/code/game/objects/items/devices/multitool.dm b/code/game/objects/items/devices/multitool.dm
index f8b1d6e15b..207497922e 100644
--- a/code/game/objects/items/devices/multitool.dm
+++ b/code/game/objects/items/devices/multitool.dm
@@ -32,6 +32,24 @@
var/datum/integrated_io/selected_io = null //functional for integrated circuits.
var/mode = 0
+/obj/item/multitool/chaplain
+ name = "\improper hypertool"
+ desc = "Used for pulsing wires to test which to cut. Also emits microwaves to fry some brains!"
+ damtype = BRAIN
+ force = 18
+ armour_penetration = 35
+ hitsound = 'sound/effects/sparks4.ogg'
+ var/chaplain_spawnable = TRUE
+ total_mass = TOTAL_MASS_MEDIEVAL_WEAPON
+ throw_speed = 3
+ throw_range = 4
+ throwforce = 10
+ obj_flags = UNIQUE_RENAME
+
+/obj/item/multitool/chaplain/Initialize()
+ . = ..()
+ AddComponent(/datum/component/anti_magic, TRUE, TRUE)
+
/obj/item/multitool/examine(mob/user)
..()
if(selected_io)
@@ -237,3 +255,10 @@
icon = 'icons/obj/abductor.dmi'
icon_state = "multitool"
toolspeed = 0.1
+
+/obj/item/multitool/advanced
+ name = "advanced multitool"
+ desc = "The reproduction of an abductor's multitool, this multitool is a classy silver."
+ icon = 'icons/obj/advancedtools.dmi'
+ icon_state = "multitool"
+ toolspeed = 0.2
diff --git a/code/game/objects/items/devices/quantum_keycard.dm b/code/game/objects/items/devices/quantum_keycard.dm
new file mode 100644
index 0000000000..37079722c0
--- /dev/null
+++ b/code/game/objects/items/devices/quantum_keycard.dm
@@ -0,0 +1,32 @@
+/obj/item/quantum_keycard
+ name = "quantum keycard"
+ desc = "A keycard able to link to a quantum pad's particle signature, allowing other quantum pads to travel there instead of their linked pad."
+ icon = 'icons/obj/device.dmi'
+ icon_state = "quantum_keycard"
+ item_state = "card-id"
+ lefthand_file = 'icons/mob/inhands/equipment/idcards_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/equipment/idcards_righthand.dmi'
+ w_class = WEIGHT_CLASS_TINY
+ var/obj/machinery/quantumpad/qpad
+
+/obj/item/quantum_keycard/examine(mob/user)
+ ..()
+ if(qpad)
+ to_chat(user, "It's currently linked to a quantum pad.")
+ to_chat(user, "Alt-click to unlink the keycard.")
+ else
+ to_chat(user, "Insert [src] into an active quantum pad to link it.")
+
+/obj/item/quantum_keycard/AltClick(mob/living/user)
+ if(!istype(user) || !user.canUseTopic(src, BE_CLOSE, ismonkey(user)))
+ return
+ to_chat(user, "You start pressing [src]'s unlink button...")
+ if(do_after(user, 40, target = src))
+ to_chat(user, "The keycard beeps twice and disconnects the quantum link.")
+ qpad = null
+
+/obj/item/quantum_keycard/update_icon()
+ if(qpad)
+ icon_state = "quantum_keycard_on"
+ else
+ icon_state = initial(icon_state)
diff --git a/code/game/objects/items/devices/radio/electropack.dm b/code/game/objects/items/devices/radio/electropack.dm
index 2f8b111b4b..b74b0fa3d4 100644
--- a/code/game/objects/items/devices/radio/electropack.dm
+++ b/code/game/objects/items/devices/radio/electropack.dm
@@ -38,7 +38,7 @@
/obj/item/electropack/attackby(obj/item/W, mob/user, params)
if(istype(W, /obj/item/clothing/head/helmet))
- var/obj/item/assembly/shock_kit/A = new /obj/item/assembly/shock_kit( user )
+ var/obj/item/assembly/shock_kit/A = new /obj/item/assembly/shock_kit(user)
A.icon = 'icons/obj/assemblies.dmi'
if(!user.transferItemToLoc(W, A))
@@ -53,8 +53,6 @@
user.put_in_hands(A)
A.add_fingerprint(user)
- if(item_flags & NODROP)
- A.item_flags |= NODROP
else
return ..()
@@ -106,8 +104,7 @@
if(shock_cooldown != 0)
return
shock_cooldown = 1
- spawn(100)
- shock_cooldown = 0
+ addtimer(VARSET_CALLBACK(src, shock_cooldown, 0), 100)
var/mob/living/L = loc
step(L, pick(GLOB.cardinals))
diff --git a/code/game/objects/items/devices/radio/encryptionkey.dm b/code/game/objects/items/devices/radio/encryptionkey.dm
index dd7489d6b1..9adc0488aa 100644
--- a/code/game/objects/items/devices/radio/encryptionkey.dm
+++ b/code/game/objects/items/devices/radio/encryptionkey.dm
@@ -1,6 +1,6 @@
/obj/item/encryptionkey
name = "standard encryption key"
- desc = "An encryption key for a radio headset. Has no special codes in it. WHY DOES IT EXIST? ASK NANOTRASEN."
+ desc = "An encryption key for a radio headset."
icon = 'icons/obj/radio.dmi'
icon_state = "cypherkey"
w_class = WEIGHT_CLASS_TINY
@@ -9,124 +9,119 @@
var/independent = FALSE
var/list/channels = list()
+/obj/item/encryptionkey/Initialize()
+ . = ..()
+ if(!channels.len)
+ desc = "An encryption key for a radio headset. Has no special codes in it. You should probably tell a coder!"
+
+/obj/item/encryptionkey/examine(mob/user)
+ . = ..()
+ if(LAZYLEN(channels))
+ var/list/examine_text_list = list()
+ for(var/i in channels)
+ examine_text_list += "[GLOB.channel_tokens[i]] - [lowertext(i)]"
+
+ to_chat(user, "It can access the following channels; [jointext(examine_text_list, ", ")].")
+
/obj/item/encryptionkey/syndicate
name = "syndicate encryption key"
- desc = "An encryption key for a radio headset. To access the syndicate channel, use :t."
icon_state = "syn_cypherkey"
- channels = list("Syndicate" = 1)
- syndie = 1//Signifies that it de-crypts Syndicate transmissions
+ channels = list(RADIO_CHANNEL_SYNDICATE = 1)
+ syndie = TRUE //Signifies that it de-crypts Syndicate transmissions
/obj/item/encryptionkey/binary
name = "binary translator key"
- desc = "An encryption key for a radio headset. To access the binary channel, use :b."
icon_state = "bin_cypherkey"
translate_binary = TRUE
/obj/item/encryptionkey/headset_sec
name = "security radio encryption key"
- desc = "An encryption key for a radio headset. To access the security channel, use :s."
icon_state = "sec_cypherkey"
- channels = list("Security" = 1)
+ channels = list(RADIO_CHANNEL_SECURITY = 1)
/obj/item/encryptionkey/headset_eng
name = "engineering radio encryption key"
- desc = "An encryption key for a radio headset. To access the engineering channel, use :e."
icon_state = "eng_cypherkey"
- channels = list("Engineering" = 1)
+ channels = list(RADIO_CHANNEL_ENGINEERING = 1)
/obj/item/encryptionkey/headset_rob
name = "robotics radio encryption key"
- desc = "An encryption key for a radio headset. To access the engineering channel, use :e. For research, use :n."
icon_state = "rob_cypherkey"
- channels = list("Science" = 1, "Engineering" = 1)
+ channels = list(RADIO_CHANNEL_SCIENCE = 1, RADIO_CHANNEL_ENGINEERING = 1)
/obj/item/encryptionkey/headset_med
name = "medical radio encryption key"
- desc = "An encryption key for a radio headset. To access the medical channel, use :m."
icon_state = "med_cypherkey"
- channels = list("Medical" = 1)
+ channels = list(RADIO_CHANNEL_MEDICAL = 1)
/obj/item/encryptionkey/headset_sci
name = "science radio encryption key"
- desc = "An encryption key for a radio headset. To access the science channel, use :n."
icon_state = "sci_cypherkey"
- channels = list("Science" = 1)
+ channels = list(RADIO_CHANNEL_SCIENCE = 1)
/obj/item/encryptionkey/headset_medsci
name = "medical research radio encryption key"
- desc = "An encryption key for a radio headset. To access the medical channel, use :m. For science, use :n."
icon_state = "medsci_cypherkey"
- channels = list("Science" = 1, "Medical" = 1)
+ channels = list(RADIO_CHANNEL_SCIENCE = 1, RADIO_CHANNEL_MEDICAL = 1)
/obj/item/encryptionkey/headset_com
name = "command radio encryption key"
- desc = "An encryption key for a radio headset. To access the command channel, use :c."
icon_state = "com_cypherkey"
- channels = list("Command" = 1)
+ channels = list(RADIO_CHANNEL_COMMAND = 1)
/obj/item/encryptionkey/heads/captain
name = "\proper the captain's encryption key"
- desc = "An encryption key for a radio headset. Channels are as follows: :c - command, :s - security, :e - engineering, :u - supply, :v - service, :m - medical, :n - science."
icon_state = "cap_cypherkey"
- channels = list("Command" = 1, "Security" = 1, "Engineering" = 0, "Science" = 0, "Medical" = 0, "Supply" = 0, "Service" = 0)
+ channels = list(RADIO_CHANNEL_COMMAND = 1, RADIO_CHANNEL_SECURITY = 1, RADIO_CHANNEL_ENGINEERING = 0, RADIO_CHANNEL_SCIENCE = 0, RADIO_CHANNEL_MEDICAL = 0, RADIO_CHANNEL_SUPPLY = 0, RADIO_CHANNEL_SERVICE = 0)
/obj/item/encryptionkey/heads/rd
name = "\proper the research director's encryption key"
- desc = "An encryption key for a radio headset. To access the science channel, use :n. For command, use :c."
icon_state = "rd_cypherkey"
- channels = list("Science" = 1, "Command" = 1)
+ channels = list(RADIO_CHANNEL_SCIENCE = 1, RADIO_CHANNEL_COMMAND = 1)
/obj/item/encryptionkey/heads/hos
name = "\proper the head of security's encryption key"
- desc = "An encryption key for a radio headset. To access the security channel, use :s. For command, use :c."
icon_state = "hos_cypherkey"
- channels = list("Security" = 1, "Command" = 1)
+ channels = list(RADIO_CHANNEL_SECURITY = 1, RADIO_CHANNEL_COMMAND = 1)
/obj/item/encryptionkey/heads/ce
name = "\proper the chief engineer's encryption key"
- desc = "An encryption key for a radio headset. To access the engineering channel, use :e. For command, use :c."
icon_state = "ce_cypherkey"
- channels = list("Engineering" = 1, "Command" = 1)
+ channels = list(RADIO_CHANNEL_ENGINEERING = 1, RADIO_CHANNEL_COMMAND = 1)
/obj/item/encryptionkey/heads/cmo
name = "\proper the chief medical officer's encryption key"
- desc = "An encryption key for a radio headset. To access the medical channel, use :m. For command, use :c."
icon_state = "cmo_cypherkey"
- channels = list("Medical" = 1, "Command" = 1)
+ channels = list(RADIO_CHANNEL_MEDICAL = 1, RADIO_CHANNEL_COMMAND = 1)
/obj/item/encryptionkey/heads/hop
name = "\proper the head of personnel's encryption key"
- desc = "An encryption key for a radio headset. Channels are as follows: :u - supply, :v - service, :c - command."
icon_state = "hop_cypherkey"
- channels = list("Supply" = 1, "Service" = 1, "Command" = 1)
+ channels = list(RADIO_CHANNEL_SUPPLY = 1, RADIO_CHANNEL_SERVICE = 1, RADIO_CHANNEL_COMMAND = 1)
/obj/item/encryptionkey/headset_cargo
name = "supply radio encryption key"
- desc = "An encryption key for a radio headset. To access the supply channel, use :u."
icon_state = "cargo_cypherkey"
- channels = list("Supply" = 1)
+ channels = list(RADIO_CHANNEL_SUPPLY = 1)
/obj/item/encryptionkey/headset_mining
name = "mining radio encryption key"
- desc = "An encryption key for a radio headset. To access the supply channel, use :u. For science, use :n."
icon_state = "cargo_cypherkey"
- channels = list("Supply" = 1, "Science" = 1)
+ channels = list(RADIO_CHANNEL_SUPPLY = 1, RADIO_CHANNEL_SCIENCE = 1)
/obj/item/encryptionkey/headset_service
name = "service radio encryption key"
- desc = "An encryption key for a radio headset. To access the service channel, use :v."
icon_state = "srv_cypherkey"
- channels = list("Service" = 1)
+ channels = list(RADIO_CHANNEL_SERVICE = 1)
/obj/item/encryptionkey/headset_cent
name = "\improper CentCom radio encryption key"
- desc = "An encryption key for a radio headset. To access the CentCom channel, use :y."
icon_state = "cent_cypherkey"
independent = TRUE
- channels = list("CentCom" = 1)
+ channels = list(RADIO_CHANNEL_CENTCOM = 1)
/obj/item/encryptionkey/ai //ported from NT, this goes 'inside' the AI.
- channels = list("Command" = 1, "Security" = 1, "Engineering" = 1, "Science" = 1, "Medical" = 1, "Supply" = 1, "Service" = 1, "AI Private" = 1)
+ channels = list(RADIO_CHANNEL_COMMAND = 1, RADIO_CHANNEL_SECURITY = 1, RADIO_CHANNEL_ENGINEERING = 1, RADIO_CHANNEL_SCIENCE = 1, RADIO_CHANNEL_MEDICAL = 1, RADIO_CHANNEL_SUPPLY = 1, RADIO_CHANNEL_SERVICE = 1, RADIO_CHANNEL_AI_PRIVATE = 1)
/obj/item/encryptionkey/secbot
- channels = list("AI Private"=1,"Security"=1)
+ channels = list(RADIO_CHANNEL_AI_PRIVATE = 1, RADIO_CHANNEL_SECURITY = 1)
diff --git a/code/game/objects/items/devices/radio/headset.dm b/code/game/objects/items/devices/radio/headset.dm
index ea9c6fb99e..cca5eb4217 100644
--- a/code/game/objects/items/devices/radio/headset.dm
+++ b/code/game/objects/items/devices/radio/headset.dm
@@ -1,3 +1,19 @@
+// Used for translating channels to tokens on examination
+GLOBAL_LIST_INIT(channel_tokens, list(
+ RADIO_CHANNEL_COMMON = RADIO_KEY_COMMON,
+ RADIO_CHANNEL_SCIENCE = RADIO_TOKEN_SCIENCE,
+ RADIO_CHANNEL_COMMAND = RADIO_TOKEN_COMMAND,
+ RADIO_CHANNEL_MEDICAL = RADIO_TOKEN_MEDICAL,
+ RADIO_CHANNEL_ENGINEERING = RADIO_TOKEN_ENGINEERING,
+ RADIO_CHANNEL_SECURITY = RADIO_TOKEN_SECURITY,
+ RADIO_CHANNEL_CENTCOM = RADIO_TOKEN_CENTCOM,
+ RADIO_CHANNEL_SYNDICATE = RADIO_TOKEN_SYNDICATE,
+ RADIO_CHANNEL_SUPPLY = RADIO_TOKEN_SUPPLY,
+ RADIO_CHANNEL_SERVICE = RADIO_TOKEN_SERVICE,
+ MODE_BINARY = MODE_TOKEN_BINARY,
+ RADIO_CHANNEL_AI_PRIVATE = RADIO_TOKEN_AI_PRIVATE
+))
+
/obj/item/radio/headset
name = "radio headset"
desc = "An updated, modular intercom that fits over the head. Takes encryption keys."
@@ -17,9 +33,24 @@
/obj/item/radio/headset/examine(mob/user)
..()
- to_chat(user, "To speak on the general radio frequency, use ; before speaking.")
- if (command)
- to_chat(user, "Alt-click to toggle the high-volume mode.")
+
+ if(item_flags & IN_INVENTORY && loc == user)
+ // construction of frequency description
+ var/list/avail_chans = list("Use [RADIO_KEY_COMMON] for the currently tuned frequency")
+ if(translate_binary)
+ avail_chans += "use [MODE_TOKEN_BINARY] for [MODE_BINARY]"
+ if(length(channels))
+ for(var/i in 1 to length(channels))
+ if(i == 1)
+ avail_chans += "use [MODE_TOKEN_DEPARTMENT] or [GLOB.channel_tokens[channels[i]]] for [lowertext(channels[i])]"
+ else
+ avail_chans += "use [GLOB.channel_tokens[channels[i]]] for [lowertext(channels[i])]"
+ to_chat(user, "A small screen on the headset displays the following available frequencies:\n[english_list(avail_chans)].")
+
+ if(command)
+ to_chat(user, "Alt-click to toggle the high-volume mode.")
+ else
+ to_chat(user, "A small screen on the headset flashes, it's too small to read without holding or wearing the headset.")
/obj/item/radio/headset/Initialize()
. = ..()
@@ -47,7 +78,7 @@
/obj/item/radio/headset/syndicate/alt //undisguised bowman with flash protection
name = "syndicate headset"
- desc = "A syndicate headset that can be used to hear all radio frequencies. Protects ears from flashbangs. \nTo access the syndicate channel, use ; before speaking."
+ desc = "A syndicate headset that can be used to hear all radio frequencies. Protects ears from flashbangs."
icon_state = "syndie_headset"
item_state = "syndie_headset"
@@ -72,13 +103,13 @@
/obj/item/radio/headset/headset_sec
name = "security radio headset"
- desc = "This is used by your elite security force.\nTo access the security channel, use :s."
+ desc = "This is used by your elite security force."
icon_state = "sec_headset"
keyslot = new /obj/item/encryptionkey/headset_sec
/obj/item/radio/headset/headset_sec/alt
name = "security bowman headset"
- desc = "This is used by your elite security force. Protects ears from flashbangs.\nTo access the security channel, use :s."
+ desc = "This is used by your elite security force. Protects ears from flashbangs."
icon_state = "sec_headset_alt"
item_state = "sec_headset_alt"
@@ -88,31 +119,31 @@
/obj/item/radio/headset/headset_eng
name = "engineering radio headset"
- desc = "When the engineers wish to chat like girls.\nTo access the engineering channel, use :e."
+ desc = "When the engineers wish to chat like girls."
icon_state = "eng_headset"
keyslot = new /obj/item/encryptionkey/headset_eng
/obj/item/radio/headset/headset_rob
name = "robotics radio headset"
- desc = "Made specifically for the roboticists, who cannot decide between departments.\nTo access the engineering channel, use :e. For research, use :n."
+ desc = "Made specifically for the roboticists, who cannot decide between departments."
icon_state = "rob_headset"
keyslot = new /obj/item/encryptionkey/headset_rob
/obj/item/radio/headset/headset_med
name = "medical radio headset"
- desc = "A headset for the trained staff of the medbay.\nTo access the medical channel, use :m."
+ desc = "A headset for the trained staff of the medbay."
icon_state = "med_headset"
keyslot = new /obj/item/encryptionkey/headset_med
/obj/item/radio/headset/headset_sci
name = "science radio headset"
- desc = "A sciency headset. Like usual.\nTo access the science channel, use :n."
+ desc = "A sciency headset. Like usual."
icon_state = "sci_headset"
keyslot = new /obj/item/encryptionkey/headset_sci
/obj/item/radio/headset/headset_medsci
name = "medical research radio headset"
- desc = "A headset that is a result of the mating between medical and science.\nTo access the medical channel, use :m. For science, use :n."
+ desc = "A headset that is a result of the mating between medical and science."
icon_state = "medsci_headset"
keyslot = new /obj/item/encryptionkey/headset_medsci
@@ -127,13 +158,13 @@
/obj/item/radio/headset/heads/captain
name = "\proper the captain's headset"
- desc = "The headset of the king.\nChannels are as follows: :c - command, :s - security, :e - engineering, :u - supply, :v - service, :m - medical, :n - science."
+ desc = "The headset of the king."
icon_state = "com_headset"
keyslot = new /obj/item/encryptionkey/heads/captain
/obj/item/radio/headset/heads/captain/alt
name = "\proper the captain's bowman headset"
- desc = "The headset of the boss. Protects ears from flashbangs.\nChannels are as follows: :c - command, :s - security, :e - engineering, :u - supply, :v - service, :m - medical, :n - science."
+ desc = "The headset of the boss. Protects ears from flashbangs."
icon_state = "com_headset_alt"
item_state = "com_headset_alt"
@@ -143,19 +174,19 @@
/obj/item/radio/headset/heads/rd
name = "\proper the research director's headset"
- desc = "Headset of the fellow who keeps society marching towards technological singularity.\nTo access the science channel, use :n. For command, use :c."
+ desc = "Headset of the fellow who keeps society marching towards technological singularity."
icon_state = "com_headset"
keyslot = new /obj/item/encryptionkey/heads/rd
/obj/item/radio/headset/heads/hos
name = "\proper the head of security's headset"
- desc = "The headset of the man in charge of keeping order and protecting the station.\nTo access the security channel, use :s. For command, use :c."
+ desc = "The headset of the man in charge of keeping order and protecting the station."
icon_state = "com_headset"
keyslot = new /obj/item/encryptionkey/heads/hos
/obj/item/radio/headset/heads/hos/alt
name = "\proper the head of security's bowman headset"
- desc = "The headset of the man in charge of keeping order and protecting the station. Protects ears from flashbangs.\nTo access the security channel, use :s. For command, use :c."
+ desc = "The headset of the man in charge of keeping order and protecting the station. Protects ears from flashbangs."
icon_state = "com_headset_alt"
item_state = "com_headset_alt"
@@ -165,43 +196,43 @@
/obj/item/radio/headset/heads/ce
name = "\proper the chief engineer's headset"
- desc = "The headset of the guy in charge of keeping the station powered and undamaged.\nTo access the engineering channel, use :e. For command, use :c."
+ desc = "The headset of the guy in charge of keeping the station powered and undamaged."
icon_state = "com_headset"
keyslot = new /obj/item/encryptionkey/heads/ce
/obj/item/radio/headset/heads/cmo
name = "\proper the chief medical officer's headset"
- desc = "The headset of the highly trained medical chief.\nTo access the medical channel, use :m. For command, use :c."
+ desc = "The headset of the highly trained medical chief."
icon_state = "com_headset"
keyslot = new /obj/item/encryptionkey/heads/cmo
/obj/item/radio/headset/heads/hop
name = "\proper the head of personnel's headset"
- desc = "The headset of the guy who will one day be captain.\nChannels are as follows: :u - supply, :v - service, :c - command."
+ desc = "The headset of the guy who will one day be captain."
icon_state = "com_headset"
keyslot = new /obj/item/encryptionkey/heads/hop
/obj/item/radio/headset/headset_cargo
name = "supply radio headset"
- desc = "A headset used by the QM and his slaves.\nTo access the supply channel, use :u."
+ desc = "A headset used by the QM and his slaves."
icon_state = "cargo_headset"
keyslot = new /obj/item/encryptionkey/headset_cargo
/obj/item/radio/headset/headset_cargo/mining
name = "mining radio headset"
- desc = "Headset used by shaft miners.\nTo access the supply channel, use :u. For science, use :n."
+ desc = "Headset used by shaft miners."
icon_state = "mine_headset"
keyslot = new /obj/item/encryptionkey/headset_mining
/obj/item/radio/headset/headset_srv
name = "service radio headset"
- desc = "Headset used by the service staff, tasked with keeping the station full, happy and clean.\nTo access the service channel, use :v."
+ desc = "Headset used by the service staff, tasked with keeping the station full, happy and clean."
icon_state = "srv_headset"
keyslot = new /obj/item/encryptionkey/headset_service
/obj/item/radio/headset/headset_cent
name = "\improper CentCom headset"
- desc = "A headset used by the upper echelons of Nanotrasen.\nTo access the CentCom channel, use :y."
+ desc = "A headset used by the upper echelons of Nanotrasen."
icon_state = "cent_headset"
keyslot = new /obj/item/encryptionkey/headset_com
keyslot2 = new /obj/item/encryptionkey/headset_cent
@@ -215,7 +246,7 @@
/obj/item/radio/headset/headset_cent/alt
name = "\improper CentCom bowman headset"
- desc = "A headset especially for emergency response personnel. Protects ears from flashbangs.\nTo access the CentCom channel, use :y."
+ desc = "A headset especially for emergency response personnel. Protects ears from flashbangs."
icon_state = "cent_headset_alt"
item_state = "cent_headset_alt"
keyslot = null
diff --git a/code/game/objects/items/devices/radio/radio.dm b/code/game/objects/items/devices/radio/radio.dm
index 2ab365d9f7..926810cfbf 100644
--- a/code/game/objects/items/devices/radio/radio.dm
+++ b/code/game/objects/items/devices/radio/radio.dm
@@ -189,7 +189,7 @@
/obj/item/radio/talk_into(atom/movable/M, message, channel, list/spans, datum/language/language)
if(!spans)
- spans = M.get_spans()
+ spans = list(M.speech_span)
if(!language)
language = M.get_default_language()
INVOKE_ASYNC(src, .proc/talk_into_impl, M, message, channel, spans.Copy(), language)
@@ -221,7 +221,7 @@
// From the channel, determine the frequency and get a reference to it.
var/freq
if(channel && channels && channels.len > 0)
- if(channel == "department")
+ if(channel == MODE_DEPARTMENT)
channel = channels[1]
freq = secure_radio_connections[channel]
if (!channels[channel]) // if the channel is turned off, don't broadcast
@@ -413,3 +413,16 @@
/obj/item/radio/off // Station bounced radios, their only difference is spawning with the speakers off, this was made to help the lag.
listening = 0 // And it's nice to have a subtype too for future features.
dog_fashion = /datum/dog_fashion/back
+
+/obj/item/radio/internal
+ var/obj/item/implant/radio/implant
+
+/obj/item/radio/internal/Initialize(mapload, obj/item/implant/radio/_implant)
+ . = ..()
+ implant = _implant
+
+/obj/item/radio/internal/Destroy()
+ if(implant?.imp_in)
+ qdel(implant)
+ else
+ return ..()
diff --git a/code/game/objects/items/devices/reverse_bear_trap.dm b/code/game/objects/items/devices/reverse_bear_trap.dm
index 2f137861aa..20107373d3 100644
--- a/code/game/objects/items/devices/reverse_bear_trap.dm
+++ b/code/game/objects/items/devices/reverse_bear_trap.dm
@@ -49,7 +49,7 @@
if(iscarbon(user))
var/mob/living/carbon/C = user
if(C.get_item_by_slot(SLOT_HEAD) == src)
- if((item_flags & NODROP) && !struggling)
+ if(HAS_TRAIT_FROM(src, TRAIT_NODROP, REVERSE_BEAR_TRAP_TRAIT) && !struggling)
struggling = TRUE
var/fear_string
switch(time_left)
@@ -74,7 +74,7 @@
else
user.visible_message("The lock on [user]'s [name] pops open!", \
"You force open the padlock!", "You hear a single, pronounced click!")
- item_flags &= ~NODROP
+ REMOVE_TRAIT(src, TRAIT_NODROP, REVERSE_BEAR_TRAP_TRAIT)
struggling = FALSE
else
..()
@@ -116,7 +116,7 @@
/obj/item/reverse_bear_trap/proc/reset()
ticking = FALSE
- item_flags &= ~NODROP
+ REMOVE_TRAIT(src, TRAIT_NODROP, REVERSE_BEAR_TRAP_TRAIT)
soundloop.stop()
soundloop2.stop()
STOP_PROCESSING(SSprocessing, src)
@@ -125,7 +125,7 @@
ticking = TRUE
escape_chance = initial(escape_chance) //we keep these vars until re-arm, for tracking purposes
time_left = initial(time_left)
- item_flags |= NODROP
+ ADD_TRAIT(src, TRAIT_NODROP, REVERSE_BEAR_TRAP_TRAIT)
soundloop.start()
soundloop2.mid_length = initial(soundloop2.mid_length)
soundloop2.start()
diff --git a/code/game/objects/items/devices/scanners.dm b/code/game/objects/items/devices/scanners.dm
index 9f52b4c1ac..7655654f78 100644
--- a/code/game/objects/items/devices/scanners.dm
+++ b/code/game/objects/items/devices/scanners.dm
@@ -135,7 +135,7 @@ SLIME SCANNER
var/mob/living/carbon/human/H = M
if(H.undergoing_cardiac_arrest() && H.stat != DEAD)
to_chat(user, "Subject suffering from heart attack: Apply defibrillation or other electric shock immediately!")
- if(H.undergoing_liver_failure() && H.stat != DEAD)
+ if(H.undergoing_liver_failure() && H.stat != DEAD) //might be depreciated BUG_PROBABLE_CAUSE
to_chat(user, "Subject is suffering from liver failure: Apply Corazone and begin a liver transplant immediately!")
var/msg = "*---------*\nAnalyzing results for [M]:\n\tOverall status: [mob_status]\n"
@@ -157,12 +157,46 @@ SLIME SCANNER
msg += "\tSubject appears to have [M.getCloneLoss() > 30 ? "Severe" : "Minor"] cellular damage.\n"
if(advanced)
msg += "\tCellular Damage Level: [M.getCloneLoss()].\n"
- if (M.getBrainLoss() >= 200 || !M.getorgan(/obj/item/organ/brain))
- msg += "\tSubject's brain function is non-existent.\n"
- else if (M.getBrainLoss() >= 120)
- msg += "\tSevere brain damage detected. Subject likely to have mental traumas.\n"
- else if (M.getBrainLoss() >= 45)
- msg += "\tBrain damage detected.\n"
+ if (!M.getorgan(/obj/item/organ/brain))
+ to_chat(user, "\tSubject lacks a brain.") //Unsure how this won't proc for 50% of the cit playerbase (This is a joke everyone on cit a cute.)
+ if(ishuman(M) && advanced) // Should I make this not advanced?
+ var/mob/living/carbon/human/H = M
+ var/obj/item/organ/liver/L = H.getorganslot("liver")
+ if(L)
+ if(L.swelling > 20)
+ msg += "\tSubject is suffering from an enlarged liver.\n" //i.e. shrink their liver or give them a transplant.
+ else
+ msg += "\tSubject's liver is missing.\n"
+ var/obj/item/organ/tongue/T = H.getorganslot("tongue")
+ if(T)
+ if(T.damage > 40)
+ msg += "\tSubject is suffering from severe burn tissue on their tongue.\n" //i.e. their tongue is shot
+ if(T.name == "fluffy tongue")
+ msg += "\tSubject is suffering from a fluffified tongue. Suggested cure: Yamerol or a tongue transplant.\n"
+ else
+ msg += "\tSubject's tongue is missing.\n"
+ var/obj/item/organ/lungs/Lung = H.getorganslot("lungs")
+ if(Lung)
+ if(Lung.damage > 150)
+ msg += "\tSubject is suffering from acute emphysema leading to trouble breathing.\n" //i.e. Their lungs are shot
+ else
+ msg += "\tSubject's lungs have collapsed from trauma!\n"
+ var/obj/item/organ/genital/penis/P = H.getorganslot("penis")
+ if(P)
+ if(P.length>20)
+ msg += "\tSubject has a sizeable gentleman's organ at [P.length] inches.\n"
+ var/obj/item/organ/genital/breasts/Br = H.getorganslot("breasts")
+ if(Br)
+ if(Br.cached_size>5)
+ msg += "\tSubject has a sizeable bosom with a [Br.size] cup.\n"
+
+ if (M.getOrganLoss(ORGAN_SLOT_BRAIN) >= 200 || !M.getorgan(/obj/item/organ/brain))
+ msg += "\tSubject's brain function is non-existent.\n"
+ else if (M.getOrganLoss(ORGAN_SLOT_BRAIN) >= 120)
+ msg += "\tSevere brain damage detected. Subject likely to have mental traumas.\n"
+ else if (M.getOrganLoss(ORGAN_SLOT_BRAIN) >= 45)
+ msg += "\tBrain damage detected.\n"
+
if(iscarbon(M))
var/mob/living/carbon/C = M
if(LAZYLEN(C.get_traumas()))
@@ -182,15 +216,25 @@ SLIME SCANNER
if(C.roundstart_quirks.len)
msg += "\tSubject has the following physiological traits: [C.get_trait_string()].\n"
if(advanced)
- msg += "\tBrain Activity Level: [(200 - M.getBrainLoss())/2]%.\n"
- if (M.radiation)
+ msg += "\tBrain Activity Level: [(200 - M.getOrganLoss(ORGAN_SLOT_BRAIN))/2]%.\n"
+ if(M.radiation)
msg += "\tSubject is irradiated.\n"
- if(advanced)
- msg += "\tRadiation Level: [M.radiation]%.\n"
+ msg += "\tRadiation Level: [M.radiation] rad\n"
if(advanced && M.hallucinating())
msg += "\tSubject is hallucinating.\n"
+ //MKUltra
+ if(advanced && M.has_status_effect(/datum/status_effect/chem/enthrall))
+ msg += "\tSubject has abnormal brain fuctions.\n"
+
+ //Astrogen shenanigans
+ if(advanced && M.reagents.has_reagent("astral"))
+ if(M.mind)
+ msg += "\tWarning: subject may be possesed.\n"
+ else
+ msg += "\tSubject appears to be astrally projecting.\n"
+
//Eyes and ears
if(advanced)
if(iscarbon(M))
@@ -206,11 +250,11 @@ SLIME SCANNER
healthy = FALSE
msg += "\tSubject is deaf.\n"
else
- if(ears.ear_damage)
- msg += "\tSubject has [ears.ear_damage > UNHEALING_EAR_DAMAGE? "permanent ": "temporary "]hearing damage.\n"
+ if(ears.damage)
+ to_chat(user, "\tSubject has [ears.damage > ears.maxHealth ? "permanent ": "temporary "]hearing damage.")
healthy = FALSE
if(ears.deaf)
- msg += "\tSubject is [ears.ear_damage > UNHEALING_EAR_DAMAGE ? "permanently ": "temporarily "] deaf.\n"
+ to_chat(user, "\tSubject is [ears.damage > ears.maxHealth ? "permanently ": "temporarily "] deaf.")
healthy = FALSE
if(healthy)
msg += "\tHealthy.\n"
@@ -226,13 +270,13 @@ SLIME SCANNER
if(HAS_TRAIT(C, TRAIT_NEARSIGHT))
msg += "\tSubject is nearsighted.\n"
healthy = FALSE
- if(eyes.eye_damage > 30)
+ if(eyes.damage > 30)
msg += "\tSubject has severe eye damage.\n"
healthy = FALSE
- else if(eyes.eye_damage > 20)
+ else if(eyes.damage > 20)
msg += "\tSubject has significant eye damage.\n"
healthy = FALSE
- else if(eyes.eye_damage)
+ else if(eyes.damage)
msg += "\tSubject has minor eye damage.\n"
healthy = FALSE
if(healthy)
@@ -256,6 +300,60 @@ SLIME SCANNER
for(var/obj/item/bodypart/org in damaged)
msg += "\t\t[capitalize(org.name)]: [(org.brute_dam > 0) ? "[org.brute_dam]" : "0"]-[(org.burn_dam > 0) ? "[org.burn_dam]" : "0"]\n"
+ //Organ damages report
+ if(ishuman(M))
+ var/mob/living/carbon/human/H = M
+ var/minor_damage
+ var/major_damage
+ var/max_damage
+ var/report_organs = FALSE
+
+ //Piece together the lists to be reported
+ for(var/O in H.internal_organs)
+ var/obj/item/organ/organ = O
+ if(organ.organ_flags & ORGAN_FAILING)
+ report_organs = TRUE //if we report one organ, we report all organs, even if the lists are empty, just for consistency
+ if(max_damage)
+ max_damage += ", " //prelude the organ if we've already reported an organ
+ max_damage += organ.name //this just slaps the organ name into the string of text
+ else
+ max_damage = "\tNon-Functional Organs: " //our initial statement
+ max_damage += organ.name
+ else if(organ.damage > organ.high_threshold)
+ report_organs = TRUE
+ if(major_damage)
+ major_damage += ", "
+ major_damage += organ.name
+ else
+ major_damage = "\tSeverely Damaged Organs: "
+ major_damage += organ.name
+ else if(organ.damage > organ.low_threshold)
+ report_organs = TRUE
+ if(minor_damage)
+ minor_damage += ", "
+ minor_damage += organ.name
+ else
+ minor_damage = "\tMildly Damaged Organs: "
+ minor_damage += organ.name
+
+ if(report_organs) //we either finish the list, or set it to be empty if no organs were reported in that category
+ if(!max_damage)
+ max_damage = "\tNon-Functional Organs: "
+ else
+ max_damage += ""
+ if(!major_damage)
+ major_damage = "\tSeverely Damaged Organs: "
+ else
+ major_damage += ""
+ if(!minor_damage)
+ minor_damage = "\tMildly Damaged Organs: "
+ else
+ minor_damage += ""
+ msg += "[minor_damage]"
+ msg += "[major_damage]"
+ msg += "[max_damage]"
+
+
// Species and body temperature
if(ishuman(M))
var/mob/living/carbon/human/H = M
@@ -310,7 +408,7 @@ SLIME SCANNER
var/mob/living/carbon/human/H = C
if(H.bleed_rate)
msg += "Subject is bleeding!\n"
- var/blood_percent = round((C.blood_volume / BLOOD_VOLUME_NORMAL)*100)
+ var/blood_percent = round((C.blood_volume / (BLOOD_VOLUME_NORMAL * C.blood_ratio))*100)
var/blood_type = C.dna.blood_type
if(blood_id != "blood")//special blood substance
var/datum/reagent/R = GLOB.chemical_reagents_list[blood_id]
@@ -318,9 +416,9 @@ SLIME SCANNER
blood_type = R.name
else
blood_type = blood_id
- if(C.blood_volume <= BLOOD_VOLUME_SAFE && C.blood_volume > BLOOD_VOLUME_OKAY)
+ if(C.blood_volume <= (BLOOD_VOLUME_SAFE*C.blood_ratio) && C.blood_volume > (BLOOD_VOLUME_OKAY*C.blood_ratio))
msg += "LOW blood level [blood_percent] %, [C.blood_volume] cl,type: [blood_type]\n"
- else if(C.blood_volume <= BLOOD_VOLUME_OKAY)
+ else if(C.blood_volume <= (BLOOD_VOLUME_OKAY*C.blood_ratio))
msg += "CRITICAL blood level [blood_percent] %, [C.blood_volume] cl,type: [blood_type]\n"
else
msg += "Blood level [blood_percent] %, [C.blood_volume] cl, type: [blood_type]\n"
@@ -341,9 +439,19 @@ SLIME SCANNER
if(M.reagents)
var/msg = "*---------*\n"
if(M.reagents.reagent_list.len)
- msg += "Subject contains the following reagents:\n"
+ var/list/datum/reagent/reagents = list()
for(var/datum/reagent/R in M.reagents.reagent_list)
- msg += "[R.volume] units of [R.name][R.overdosed == 1 ? " - OVERDOSING" : "."]\n"
+ if(R.chemical_flags & REAGENT_INVISIBLE)
+ continue
+ reagents += R
+
+ if(length(reagents))
+ msg += "Subject contains the following reagents:\n"
+ for(var/datum/reagent/R in reagents)
+ msg += "[R.volume] units of [R.name][R.overdosed == 1 ? " - OVERDOSING" : "."]\n"
+ else
+ msg += "Subject contains no reagents.\n"
+
else
msg += "Subject contains no reagents.\n"
if(M.reagents.addiction_list.len)
@@ -352,6 +460,21 @@ SLIME SCANNER
msg += "[R.name]\n"
else
msg += "Subject is not addicted to any reagents.\n"
+
+ if(M.reagents.has_reagent("fermiTox"))
+ var/datum/reagent/fermiTox = M.reagents.has_reagent("fermiTox")
+ switch(fermiTox.volume)
+ if(5 to 10)
+ msg += "Subject contains a low amount of toxic isomers.\n"
+ if(10 to 25)
+ msg += "Subject contains toxic isomers.\n"
+ if(25 to 50)
+ msg += "Subject contains a substantial amount of toxic isomers.\n"
+ if(50 to 95)
+ msg += "Subject contains a high amount of toxic isomers.\n"
+ if(95 to INFINITY)
+ msg += "Subject contains a extremely dangerous amount of toxic isomers.\n"
+
msg += "*---------*"
to_chat(user, msg)
@@ -621,7 +744,7 @@ SLIME SCANNER
to_chat(user, "Growth progress: [T.amount_grown]/[SLIME_EVOLUTION_THRESHOLD]")
if(T.effectmod)
to_chat(user, "Core mutation in progress: [T.effectmod]")
- to_chat(user, "Progress in core mutation: [T.applied] / [SLIME_EXTRACT_CROSSING_REQUIRED]")
+ to_chat(user, "Progress in core mutation: [T.applied] / [SLIME_EXTRACT_CROSSING_REQUIRED]")
to_chat(user, "========================")
diff --git a/code/game/objects/items/eightball.dm b/code/game/objects/items/eightball.dm
index bdaa716075..9dc3e421fd 100644
--- a/code/game/objects/items/eightball.dm
+++ b/code/game/objects/items/eightball.dm
@@ -206,7 +206,7 @@
switch(action)
if("vote")
var/selected_answer = params["answer"]
- if(!selected_answer in possible_answers)
+ if(!(selected_answer in possible_answers))
return
else
votes[user.ckey] = selected_answer
diff --git a/code/game/objects/items/granters.dm b/code/game/objects/items/granters.dm
index 34ce6bc521..fdb016df38 100644
--- a/code/game/objects/items/granters.dm
+++ b/code/game/objects/items/granters.dm
@@ -1,4 +1,3 @@
-
///books that teach things (intrinsic actions like bar flinging, spells like fireball or smoke, or martial arts)///
/obj/item/book/granter
@@ -13,19 +12,50 @@
/obj/item/book/granter/proc/turn_page(mob/user)
playsound(user, pick('sound/effects/pageturn1.ogg','sound/effects/pageturn2.ogg','sound/effects/pageturn3.ogg'), 30, 1)
if(do_after(user,50, user))
- to_chat(user, "[pick(remarks)]")
+ if(remarks.len)
+ to_chat(user, "[pick(remarks)]")
+ else
+ to_chat(user, "You keep reading...")
return TRUE
return FALSE
/obj/item/book/granter/proc/recoil(mob/user) //nothing so some books can just return
+/obj/item/book/granter/proc/already_known(mob/user)
+ return FALSE
+
+/obj/item/book/granter/proc/on_reading_start(mob/user)
+ to_chat(user, "You start reading [name]...")
+
+/obj/item/book/granter/proc/on_reading_stopped(mob/user)
+ to_chat(user, "You stop reading...")
+
+/obj/item/book/granter/proc/on_reading_finished(mob/user)
+ to_chat(user, "You finish reading [name]!")
+
/obj/item/book/granter/proc/onlearned(mob/user)
used = TRUE
+
/obj/item/book/granter/attack_self(mob/user)
- if(reading == TRUE)
+ if(reading)
to_chat(user, "You're already reading this!")
return FALSE
+ if(already_known(user))
+ return FALSE
+ if(used && oneuse)
+ recoil(user)
+ else
+ on_reading_start(user)
+ reading = TRUE
+ for(var/i=1, i<=pages_to_mastery, i++)
+ if(!turn_page(user))
+ on_reading_stopped()
+ reading = FALSE
+ return
+ if(do_after(user,50, user))
+ on_reading_finished(user)
+ reading = FALSE
return TRUE
///ACTION BUTTONS///
@@ -34,33 +64,23 @@
var/granted_action
var/actionname = "catching bugs" //might not seem needed but this makes it so you can safely name action buttons toggle this or that without it fucking up the granter, also caps
-/obj/item/book/granter/action/attack_self(mob/user)
- . = ..()
- if(!.)
- return
+/obj/item/book/granter/action/already_known(mob/user)
if(!granted_action)
- return
- var/datum/action/G = new granted_action
+ return TRUE
for(var/datum/action/A in user.actions)
- if(A.type == G.type)
+ if(A.type == granted_action)
to_chat(user, "You already know all about [actionname].")
- qdel(G)
- return
- if(used == TRUE && oneuse == TRUE)
- recoil(user)
- else
- to_chat(user, "You start reading about [actionname]...")
- reading = TRUE
- for(var/i=1, i<=pages_to_mastery, i++)
- if(!turn_page(user))
- to_chat(user, "You stop reading...")
- reading = FALSE
- qdel(G)
- return
- if(do_after(user,50, user))
- to_chat(user, "You feel like you've got a good handle on [actionname]!")
- G.Grant(user)
- reading = FALSE
+ return TRUE
+ return FALSE
+
+/obj/item/book/granter/action/on_reading_start(mob/user)
+ to_chat(user, "You start reading about [actionname]...")
+
+/obj/item/book/granter/action/on_reading_finished(mob/user)
+ to_chat(user, "You feel like you've got a good handle on [actionname]!")
+ var/datum/action/G = new granted_action
+ G.Grant(user)
+ onlearned(user)
/obj/item/book/granter/action/drink_fling
granted_action = /datum/action/innate/drink_fling
@@ -120,38 +140,28 @@
var/spell
var/spellname = "conjure bugs"
-/obj/item/book/granter/spell/attack_self(mob/user)
- . = ..()
- if(!.)
- return
+/obj/item/book/granter/spell/already_known(mob/user)
if(!spell)
- return
- var/obj/effect/proc_holder/spell/S = new spell
+ return TRUE
for(var/obj/effect/proc_holder/spell/knownspell in user.mind.spell_list)
- if(knownspell.type == S.type)
+ if(knownspell.type == spell)
if(user.mind)
if(iswizard(user))
- to_chat(user,"You're already far more versed in this spell than this flimsy howto book can provide.")
+ to_chat(user,"You're already far more versed in this spell than this flimsy how-to book can provide.")
else
to_chat(user,"You've already read this one.")
- return
- if(used == TRUE && oneuse == TRUE)
- recoil(user)
- else
- to_chat(user, "You start reading about casting [spellname]...")
- reading = TRUE
- for(var/i=1, i<=pages_to_mastery, i++)
- if(!turn_page(user))
- to_chat(user, "You stop reading...")
- reading = FALSE
- qdel(S)
- return
- if(do_after(user,50, user))
- to_chat(user, "You feel like you've experienced enough to cast [spellname]!")
- user.mind.AddSpell(S)
- user.log_message("learned the spell [spellname] ([S])", LOG_ATTACK, color="orange")
- onlearned(user)
- reading = FALSE
+ return TRUE
+ return FALSE
+
+/obj/item/book/granter/spell/on_reading_start(mob/user)
+ to_chat(user, "You start reading about casting [spellname]...")
+
+/obj/item/book/granter/spell/on_reading_finished(mob/user)
+ to_chat(user, "You feel like you've experienced enough to cast [spellname]!")
+ var/obj/effect/proc_holder/spell/S = new spell
+ user.mind.AddSpell(S)
+ user.log_message("learned the spell [spellname] ([S])", LOG_ATTACK, color="orange")
+ onlearned(user)
/obj/item/book/granter/spell/recoil(mob/user)
user.visible_message("[src] glows in a black light!")
@@ -279,10 +289,7 @@
/obj/item/book/granter/spell/barnyard/recoil(mob/living/carbon/user)
if(ishuman(user))
to_chat(user,"HORSIE HAS RISEN")
- var/obj/item/clothing/mask/horsehead/magichead = new /obj/item/clothing/mask/horsehead
- magichead.item_flags |= NODROP //curses!
- magichead.flags_inv &= ~HIDEFACE //so you can still see their face
- magichead.voicechange = TRUE //NEEEEIIGHH
+ var/obj/item/clothing/magichead = new /obj/item/clothing/mask/horsehead/cursed(user.drop_location())
if(!user.dropItemToGround(user.wear_mask))
qdel(user.wear_mask)
user.equip_to_slot_if_possible(magichead, SLOT_WEAR_MASK, TRUE, TRUE)
@@ -295,7 +302,7 @@
spellname = "charge"
icon_state ="bookcharge"
desc = "This book is made of 100% postconsumer wizard."
- remarks = list("I feel ALIVE!", "I CAN TASTE THE MANA!", "What a RUSH!", "I'm FLYING through these pages!", "THIS GENIUS IS MAKING IT!", "This book is ACTION PAcKED!", "HE'S DONE IT", "LETS GOOOOOOOOOOOO")
+ remarks = list("I feel ALIVE!", "I CAN TASTE THE MANA!", "What a RUSH!", "I'm FLYING through these pages!", "THIS GENIUS IS MAKING IT!", "This book is ACTION PAcKED!", "HE'S DONE IT", "LETS GOOOOOOOOOOOO", "Just wait faster is all...")
/obj/item/book/granter/spell/charge/recoil(mob/user)
..()
@@ -331,35 +338,24 @@
var/martialname = "bug jitsu"
var/greet = "You feel like you have mastered the art in breaking code. Nice work, jackass."
-/obj/item/book/granter/martial/attack_self(mob/user)
- . = ..()
- if(!.)
- return
+/obj/item/book/granter/martial/already_known(mob/user)
if(!martial)
- return
+ return TRUE
+ var/datum/martial_art/MA = martial
+ if(user.mind.has_martialart(initial(MA.id)))
+ to_chat(user,"You already know [martialname]!")
+ return TRUE
+ return FALSE
+
+/obj/item/book/granter/martial/on_reading_start(mob/user)
+ to_chat(user, "You start reading about [martialname]...")
+
+/obj/item/book/granter/martial/on_reading_finished(mob/user)
+ to_chat(user, "[greet]")
var/datum/martial_art/MA = new martial
- if(user.mind.martial_art)
- for(var/datum/martial_art/knownmartial in user.mind.martial_art)
- if(knownmartial.type == MA.type)
- to_chat(user,"You already know [martialname]!")
- return
- if(used == TRUE && oneuse == TRUE)
- recoil(user)
- else
- to_chat(user, "You start reading about [martialname]...")
- reading = TRUE
- for(var/i=1, i<=pages_to_mastery, i++)
- if(!turn_page(user))
- to_chat(user, "You stop reading...")
- reading = FALSE
- qdel(MA)
- return
- if(do_after(user,50, user))
- to_chat(user, "[greet]")
- MA.teach(user)
- user.log_message("learned the martial art [martialname] ([MA])", LOG_ATTACK, color="orange")
- onlearned(user)
- reading = FALSE
+ MA.teach(user)
+ user.log_message("learned the martial art [martialname] ([MA])", LOG_ATTACK, color="orange")
+ onlearned(user)
/obj/item/book/granter/martial/cqc
martial = /datum/martial_art/cqc
@@ -420,3 +416,44 @@
icon_state = "blankscroll"
// I did not include mushpunch's grant, it is not a book and the item does it just fine.
+
+
+//Crafting Recipe books
+
+/obj/item/book/granter/crafting_recipe
+ var/list/crafting_recipe_types = list() //Use full /datum/crafting_recipe/what_you_craft
+
+/obj/item/book/granter/crafting_recipe/on_reading_finished(mob/user)
+ . = ..()
+ if(!user.mind)
+ return
+ for(var/crafting_recipe_type in crafting_recipe_types)
+ var/datum/crafting_recipe/R = crafting_recipe_type
+ user.mind.teach_crafting_recipe(crafting_recipe_type)
+ to_chat(user,"You learned how to make [initial(R.name)].")
+
+/obj/item/book/granter/crafting_recipe/cooking_sweets_101 //We start at 101 for 103 and 105
+ name = "Cooking Desserts 101"
+ desc = "A cook book that teaches you some more of the newest desserts. AI approved, and a best seller on Honkplanet."
+ crafting_recipe_types = list(/datum/crafting_recipe/food/mimetart, /datum/crafting_recipe/food/berrytart, /datum/crafting_recipe/food/cocolavatart, /datum/crafting_recipe/food/clowncake, /datum/crafting_recipe/food/vanillacake)
+ icon_state = "cooking_learing_sweets"
+ oneuse = FALSE
+ remarks = list("So that is how icing is made!", "Placing fruit on top? How simple...", "Huh layering cake seems harder then this...", "This book smells like candy", "A clown must have made this page, or they forgot to spell check it before printing...", "Wait, a way to cook slime to be safe?")
+
+/obj/item/book/granter/crafting_recipe/coldcooking //IceCream
+ name = "Cooking with Ice"
+ desc = "A cook book that teaches you many old icecream treats."
+ crafting_recipe_types = list(/datum/crafting_recipe/food/banana_split, /datum/crafting_recipe/food/root_float, /datum/crafting_recipe/food/bluecharrie_float, /datum/crafting_recipe/food/charrie_float)
+ icon_state = "cooking_learing_ice"
+ oneuse = FALSE
+ remarks = list("Looks like these would sell much better in a plasma fire...", "Using glass bowls rather then cones?", "Mixing soda and ice-cream?", "Tall glasses with of liquids and solids...", "Just add a bit of icecream and cherry on top?")
+
+//Later content when I have free time - Trilby Date:24-Aug-2019
+
+/obj/item/book/granter/crafting_recipe/under_the_oven //Illegal cook book
+ name = "Under The Oven"
+ desc = "A cook book that teaches you many illegal and fun candys. MALF AI approved, and a best seller on the blackmarket."
+ crafting_recipe_types = list()
+ icon_state = "cooking_learing_illegal"
+ oneuse = FALSE
+ remarks = list()
diff --git a/code/game/objects/items/handcuffs.dm b/code/game/objects/items/handcuffs.dm
index 5e19577b46..246dd77684 100644
--- a/code/game/objects/items/handcuffs.dm
+++ b/code/game/objects/items/handcuffs.dm
@@ -1,5 +1,6 @@
/obj/item/restraints
breakouttime = 600
+ var/demoralize_criminals = TRUE // checked on carbon/carbon.dm to decide wheter to apply the handcuffed negative moodlet or not.
/obj/item/restraints/suicide_act(mob/living/carbon/user)
user.visible_message("[user] is strangling [user.p_them()]self with [src]! It looks like [user.p_theyre()] trying to commit suicide!")
@@ -26,6 +27,7 @@
gender = PLURAL
icon = 'icons/obj/items_and_weapons.dmi'
icon_state = "handcuff"
+ item_state = "handcuff"
lefthand_file = 'icons/mob/inhands/equipment/security_lefthand.dmi'
righthand_file = 'icons/mob/inhands/equipment/security_righthand.dmi'
flags_1 = CONDUCT_1
@@ -103,7 +105,6 @@
desc = "A pair of restraints fashioned from long strands of flesh."
icon = 'icons/obj/mining.dmi'
icon_state = "sinewcuff"
- item_state = "sinewcuff"
breakouttime = 300 //Deciseconds = 30s
cuffsound = 'sound/weapons/cablecuff.ogg'
@@ -164,14 +165,6 @@
/obj/item/restraints/handcuffs/cable/white
item_color = "white"
-/obj/item/restraints/handcuffs/alien
- icon_state = "handcuffAlien"
-
-/obj/item/restraints/handcuffs/fake
- name = "fake handcuffs"
- desc = "Fake handcuffs meant for gag purposes."
- breakouttime = 10 //Deciseconds = 1s
-
/obj/item/restraints/handcuffs/cable/attackby(obj/item/I, mob/user, params)
..()
if(istype(I, /obj/item/stack/rods))
@@ -206,7 +199,7 @@
/obj/item/restraints/handcuffs/cable/zipties
name = "zipties"
desc = "Plastic, disposable zipties that can be used to restrain temporarily but are destroyed after use."
- icon_state = "cuff"
+ item_state = "zipties"
lefthand_file = 'icons/mob/inhands/equipment/security_lefthand.dmi'
righthand_file = 'icons/mob/inhands/equipment/security_righthand.dmi'
materials = list()
@@ -217,11 +210,26 @@
/obj/item/restraints/handcuffs/cable/zipties/used
desc = "A pair of broken zipties."
icon_state = "cuff_used"
- item_state = "cuff"
/obj/item/restraints/handcuffs/cable/zipties/used/attack()
return
+/obj/item/restraints/handcuffs/alien
+ icon_state = "handcuffAlien"
+
+/obj/item/restraints/handcuffs/fake
+ name = "fake handcuffs"
+ desc = "Fake handcuffs meant for gag purposes."
+ breakouttime = 10 //Deciseconds = 1s
+ demoralize_criminals = FALSE
+
+/obj/item/restraints/handcuffs/fake/kinky
+ name = "kinky handcuffs"
+ desc = "Fake handcuffs meant for erotic roleplay."
+ icon = 'modular_citadel/icons/obj/items_and_weapons.dmi'
+ icon_state = "handcuffgag"
+ item_state = "kinkycuff"
+
//Legcuffs
/obj/item/restraints/legcuffs
@@ -230,6 +238,7 @@
gender = PLURAL
icon = 'icons/obj/items_and_weapons.dmi'
icon_state = "handcuff"
+ item_state = "legcuff"
lefthand_file = 'icons/mob/inhands/equipment/security_lefthand.dmi'
righthand_file = 'icons/mob/inhands/equipment/security_righthand.dmi'
flags_1 = CONDUCT_1
diff --git a/code/game/objects/items/his_grace.dm b/code/game/objects/items/his_grace.dm
index 49a5cfaf35..3be57d23f1 100644
--- a/code/game/objects/items/his_grace.dm
+++ b/code/game/objects/items/his_grace.dm
@@ -8,12 +8,13 @@
name = "artistic toolbox"
desc = "A toolbox painted bright green. Looking at it makes you feel uneasy."
icon_state = "his_grace"
- item_state = "artistic_toolbox"
+ item_state = "toolbox_green"
lefthand_file = 'icons/mob/inhands/equipment/toolbox_lefthand.dmi'
righthand_file = 'icons/mob/inhands/equipment/toolbox_righthand.dmi'
icon = 'icons/obj/items_and_weapons.dmi'
- w_class = WEIGHT_CLASS_GIGANTIC
+ w_class = WEIGHT_CLASS_BULKY
force = 12
+ total_mass = TOTAL_MASS_NORMAL_ITEM // average toolbox
attack_verb = list("robusted")
hitsound = 'sound/weapons/smash.ogg'
var/awakened = FALSE
@@ -90,7 +91,7 @@
do_attack_animation(master, null, src)
master.emote("scream")
master.remove_status_effect(STATUS_EFFECT_HISGRACE)
- item_flags &= ~NODROP
+ REMOVE_TRAIT(src, TRAIT_NODROP, HIS_GRACE_TRAIT)
master.Knockdown(60)
master.adjustBruteLoss(master.maxHealth)
playsound(master, 'sound/effects/splat.ogg', 100, 0)
@@ -174,6 +175,7 @@
/obj/item/his_grace/proc/consume(mob/living/meal) //Here's your dinner, Mr. Grace.
if(!meal)
return
+ var/victims = 0
meal.visible_message("[src] swings open and devours [meal]!", "[src] consumes you!")
meal.adjustBruteLoss(200)
playsound(meal, 'sound/misc/desceration-02.ogg', 75, 1)
@@ -185,7 +187,10 @@
bloodthirst = max(LAZYLEN(contents), 1) //Never fully sated, and His hunger will only grow.
else
bloodthirst = HIS_GRACE_CONSUME_OWNER
- if(LAZYLEN(contents) >= victims_needed)
+ for(var/mob/living/C in contents)
+ if(C.mind)
+ victims++
+ if(victims >= victims_needed)
ascend()
update_stats()
@@ -198,20 +203,20 @@
update_stats()
/obj/item/his_grace/proc/update_stats()
- item_flags &= ~NODROP
+ REMOVE_TRAIT(src, TRAIT_NODROP, HIS_GRACE_TRAIT)
var/mob/living/master = get_atom_on_turf(src, /mob/living)
switch(bloodthirst)
if(HIS_GRACE_CONSUME_OWNER to HIS_GRACE_FALL_ASLEEP)
if(HIS_GRACE_CONSUME_OWNER > prev_bloodthirst)
master.visible_message("[src] enters a frenzy!")
if(HIS_GRACE_STARVING to HIS_GRACE_CONSUME_OWNER)
- item_flags |= NODROP
+ ADD_TRAIT(src, TRAIT_NODROP, HIS_GRACE_TRAIT)
if(HIS_GRACE_STARVING > prev_bloodthirst)
master.visible_message("[src] is starving!", "[src]'s bloodlust overcomes you. [src] must be fed, or you will become His meal.\
[force_bonus < 15 ? " And still, His power grows.":""]")
force_bonus = max(force_bonus, 15)
if(HIS_GRACE_FAMISHED to HIS_GRACE_STARVING)
- item_flags |= NODROP
+ ADD_TRAIT(src, TRAIT_NODROP, HIS_GRACE_TRAIT)
if(HIS_GRACE_FAMISHED > prev_bloodthirst)
master.visible_message("[src] is very hungry!", "Spines sink into your hand. [src] must feed immediately.\
[force_bonus < 10 ? " His power grows.":""]")
diff --git a/code/game/objects/items/holy_weapons.dm b/code/game/objects/items/holy_weapons.dm
index 06aef0a22b..ccb82f7029 100644
--- a/code/game/objects/items/holy_weapons.dm
+++ b/code/game/objects/items/holy_weapons.dm
@@ -34,6 +34,7 @@
desc = "God wills it!"
icon_state = "knight_templar"
item_state = "knight_templar"
+ allowed = list(/obj/item/storage/book/bible, HOLY_WEAPONS, /obj/item/reagent_containers/food/drinks/bottle/holywater, /obj/item/storage/fancy/candle_box, /obj/item/candle, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman)
// CITADEL CHANGES: More variants
/obj/item/clothing/suit/armor/riot/chaplain/teutonic
@@ -122,7 +123,6 @@
icon_state = "studentuni"
item_state = "studentuni"
body_parts_covered = ARMS|CHEST
- allowed = list(/obj/item/storage/book/bible, /obj/item/nullrod, /obj/item/reagent_containers/food/drinks/bottle/holywater, /obj/item/storage/fancy/candle_box, /obj/item/candle, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman)
/obj/item/clothing/head/helmet/chaplain/cage
name = "cage"
@@ -166,7 +166,6 @@
icon_state = "witchhunter"
item_state = "witchhunter"
body_parts_covered = CHEST|GROIN|LEGS|ARMS
- allowed = list(/obj/item/storage/book/bible, /obj/item/nullrod, /obj/item/reagent_containers/food/drinks/bottle/holywater, /obj/item/storage/fancy/candle_box, /obj/item/candle, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman)
/obj/item/clothing/head/helmet/chaplain/witchunter_hat
name = "witchunter hat"
@@ -191,7 +190,7 @@
icon_state = "chaplain_hoodie"
item_state = "chaplain_hoodie"
body_parts_covered = CHEST|GROIN|LEGS|ARMS
- allowed = list(/obj/item/storage/book/bible, /obj/item/nullrod, /obj/item/reagent_containers/food/drinks/bottle/holywater, /obj/item/storage/fancy/candle_box, /obj/item/candle, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman)
+ allowed = list(/obj/item/storage/book/bible, HOLY_WEAPONS, /obj/item/reagent_containers/food/drinks/bottle/holywater, /obj/item/storage/fancy/candle_box, /obj/item/candle, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman)
hoodtype = /obj/item/clothing/head/hooded/chaplain_hood
/obj/item/clothing/head/hooded/chaplain_hood
@@ -229,8 +228,8 @@
throwforce = 10
w_class = WEIGHT_CLASS_TINY
obj_flags = UNIQUE_RENAME
- var/reskinned = FALSE
var/chaplain_spawnable = TRUE
+ total_mass = TOTAL_MASS_MEDIEVAL_WEAPON
/obj/item/nullrod/Initialize()
. = ..()
@@ -247,10 +246,8 @@
/obj/item/nullrod/proc/reskin_holy_weapon(mob/M)
if(GLOB.holy_weapon_type)
return
- var/obj/item/nullrod/holy_weapon
- var/list/holy_weapons_list = typesof(/obj/item/nullrod) + list(
- /obj/item/melee/transforming/energy/sword/cx/chaplain
- )
+ var/obj/item/holy_weapon
+ var/list/holy_weapons_list = subtypesof(/obj/item/nullrod) + list(HOLY_WEAPONS)
var/list/display_names = list()
for(var/V in holy_weapons_list)
var/obj/item/nullrod/rodtype = V
@@ -273,6 +270,13 @@
qdel(src)
M.put_in_active_hand(holy_weapon)
+/obj/item/nullrod/proc/jedi_spin(mob/living/user)
+ for(var/i in list(NORTH,SOUTH,EAST,WEST,EAST,SOUTH,NORTH,SOUTH,EAST,WEST,EAST,SOUTH))
+ user.setDir(i)
+ if(i == WEST)
+ user.emote("flip")
+ sleep(1)
+
/obj/item/nullrod/godhand
icon_state = "disintegrate"
item_state = "disintegrate"
@@ -280,11 +284,16 @@
righthand_file = 'icons/mob/inhands/items_righthand.dmi'
name = "god hand"
desc = "This hand of yours glows with an awesome power!"
- item_flags = ABSTRACT | NODROP | DROPDEL
+ item_flags = ABSTRACT | DROPDEL
w_class = WEIGHT_CLASS_HUGE
hitsound = 'sound/weapons/sear.ogg'
damtype = BURN
attack_verb = list("punched", "cross countered", "pummeled")
+ total_mass = TOTAL_MASS_HAND_REPLACEMENT
+
+/obj/item/nullrod/godhand/Initialize()
+ . = ..()
+ ADD_TRAIT(src, TRAIT_NODROP, HAND_REPLACEMENT_TRAIT)
/obj/item/nullrod/staff
icon_state = "godstaff-red"
@@ -349,6 +358,8 @@
slot_flags = ITEM_SLOT_BELT
attack_verb = list("sawed", "torn", "cut", "chopped", "diced")
hitsound = 'sound/weapons/chainsawhit.ogg'
+ tool_behaviour = TOOL_SAW
+ toolspeed = 1.5 //slower than a real saw
/obj/item/nullrod/claymore/glowing
icon_state = "swordon"
@@ -504,7 +515,8 @@
slot_flags = ITEM_SLOT_BELT
attack_verb = list("sawed", "torn", "cut", "chopped", "diced")
hitsound = 'sound/weapons/chainsawhit.ogg'
-
+ tool_behaviour = TOOL_SAW
+ toolspeed = 0.5
/obj/item/nullrod/hammmer
icon_state = "hammeron"
@@ -525,13 +537,17 @@
lefthand_file = 'icons/mob/inhands/weapons/chainsaw_lefthand.dmi'
righthand_file = 'icons/mob/inhands/weapons/chainsaw_righthand.dmi'
w_class = WEIGHT_CLASS_HUGE
- item_flags = NODROP | ABSTRACT
+ item_flags = ABSTRACT
sharpness = IS_SHARP
attack_verb = list("sawed", "torn", "cut", "chopped", "diced")
hitsound = 'sound/weapons/chainsawhit.ogg'
+ total_mass = TOTAL_MASS_HAND_REPLACEMENT
+ tool_behaviour = TOOL_SAW
+ toolspeed = 2
/obj/item/nullrod/chainsaw/Initialize()
. = ..()
+ ADD_TRAIT(src, TRAIT_NODROP, HAND_REPLACEMENT_TRAIT)
AddComponent(/datum/component/butchering, 30, 100, 0, hitsound)
/obj/item/nullrod/clown
@@ -576,6 +592,7 @@
lefthand_file = 'icons/mob/inhands/weapons/melee_lefthand.dmi'
righthand_file = 'icons/mob/inhands/weapons/melee_righthand.dmi'
slot_flags = ITEM_SLOT_BELT
+ force = 12
reach = 2
attack_verb = list("whipped", "lashed")
hitsound = 'sound/weapons/chainhit.ogg'
@@ -601,12 +618,14 @@
item_state = "arm_blade"
lefthand_file = 'icons/mob/inhands/antag/changeling_lefthand.dmi'
righthand_file = 'icons/mob/inhands/antag/changeling_righthand.dmi'
- item_flags = ABSTRACT | NODROP
+ item_flags = ABSTRACT
w_class = WEIGHT_CLASS_HUGE
sharpness = IS_SHARP
+ total_mass = TOTAL_MASS_HAND_REPLACEMENT
/obj/item/nullrod/armblade/Initialize()
. = ..()
+ ADD_TRAIT(src, TRAIT_NODROP, HAND_REPLACEMENT_TRAIT)
AddComponent(/datum/component/butchering, 80, 70)
/obj/item/nullrod/armblade/tentacle
@@ -650,6 +669,44 @@
lefthand_file = 'icons/mob/inhands/weapons/staves_lefthand.dmi'
righthand_file = 'icons/mob/inhands/weapons/staves_righthand.dmi'
+/obj/item/nullrod/claymore/bostaff/attack(mob/target, mob/living/user)
+ add_fingerprint(user)
+ if((HAS_TRAIT(user, TRAIT_CLUMSY)) && prob(50))
+ to_chat(user, "You club yourself over the head with [src].")
+ user.Knockdown(60)
+ if(ishuman(user))
+ var/mob/living/carbon/human/H = user
+ H.apply_damage(2*force, BRUTE, BODY_ZONE_HEAD)
+ else
+ user.take_bodypart_damage(2*force)
+ return
+ if(iscyborg(target))
+ return ..()
+ if(!isliving(target))
+ return ..()
+ var/mob/living/carbon/C = target
+ if(C.stat || C.health < 0 || C.staminaloss > 130 )
+ to_chat(user, "It would be dishonorable to attack a foe while they cannot retaliate.")
+ return
+ if(user.a_intent == INTENT_DISARM)
+ if(!ishuman(target))
+ return ..()
+ var/mob/living/carbon/human/H = target
+ var/list/fluffmessages = list("[user] clubs [H] with [src]!", \
+ "[user] smacks [H] with the butt of [src]!", \
+ "[user] broadsides [H] with [src]!", \
+ "[user] smashes [H]'s head with [src]!", \
+ "[user] beats [H] with front of [src]!", \
+ "[user] twirls and slams [H] with [src]!")
+ H.visible_message("[pick(fluffmessages)]", \
+ "[pick(fluffmessages)]")
+ playsound(get_turf(user), 'sound/effects/woodhit.ogg', 75, 1, -1)
+ H.adjustStaminaLoss(rand(12,18))
+ if(prob(25))
+ (INVOKE_ASYNC(src, .proc/jedi_spin, user))
+ else
+ return ..()
+
/obj/item/nullrod/tribal_knife
icon_state = "crysknife"
item_state = "crysknife"
@@ -692,8 +749,8 @@
name = "egyptian staff"
desc = "A tutorial in mummification is carved into the staff. You could probably craft the wraps if you had some cloth."
icon = 'icons/obj/guns/magic.dmi'
- icon_state = "pharoah_sceptre"
- item_state = "pharoah_sceptre"
+ icon_state = "pharaoh_sceptre"
+ item_state = "pharaoh_sceptre"
lefthand_file = 'icons/mob/inhands/weapons/staves_lefthand.dmi'
righthand_file = 'icons/mob/inhands/weapons/staves_righthand.dmi'
w_class = WEIGHT_CLASS_NORMAL
diff --git a/code/game/objects/items/hot_potato.dm b/code/game/objects/items/hot_potato.dm
index db7f38ddf3..e7b05d7a01 100644
--- a/code/game/objects/items/hot_potato.dm
+++ b/code/game/objects/items/hot_potato.dm
@@ -134,7 +134,7 @@
return
update_icon()
if(sticky)
- item_flags |= NODROP
+ ADD_TRAIT(src, TRAIT_NODROP, HOT_POTATO_TRAIT)
name = "primed [name]"
activation_time = timer + world.time
detonation_timerid = addtimer(CALLBACK(src, .proc/detonate), delay, TIMER_STOPPABLE)
@@ -147,7 +147,7 @@
/obj/item/hot_potato/proc/deactivate()
update_icon()
name = initial(name)
- item_flags &= ~NODROP
+ REMOVE_TRAIT(src, TRAIT_NODROP, HOT_POTATO_TRAIT)
deltimer(detonation_timerid)
STOP_PROCESSING(SSfastprocess, src)
detonation_timerid = null
diff --git a/code/game/objects/items/implants/implant.dm b/code/game/objects/items/implants/implant.dm
index 482cfb0d8e..0786172f25 100644
--- a/code/game/objects/items/implants/implant.dm
+++ b/code/game/objects/items/implants/implant.dm
@@ -72,7 +72,7 @@
else
return FALSE
- forceMove(target)
+ moveToNullspace()
imp_in = target
target.implants += src
if(activated)
@@ -89,12 +89,12 @@
return TRUE
/obj/item/implant/proc/removed(mob/living/source, silent = FALSE, special = 0)
- moveToNullspace()
+ SEND_SIGNAL(src, COMSIG_IMPLANT_REMOVING, args)
imp_in = null
source.implants -= src
for(var/X in actions)
var/datum/action/A = X
- A.Grant(source)
+ A.Remove(source)
if(ishuman(source))
var/mob/living/carbon/human/H = source
H.sec_hud_set_implants()
diff --git a/code/game/objects/items/implants/implant_chem.dm b/code/game/objects/items/implants/implant_chem.dm
index c6c8be1a83..8da1d1e472 100644
--- a/code/game/objects/items/implants/implant_chem.dm
+++ b/code/game/objects/items/implants/implant_chem.dm
@@ -3,6 +3,7 @@
desc = "Injects things."
icon_state = "reagents"
activated = FALSE
+ var/obj/item/imp_chemholder/chemholder
/obj/item/implant/chem/get_data()
var/dat = {"Implant Specifications:
@@ -29,6 +30,26 @@
GLOB.tracked_chem_implants -= src
return ..()
+/obj/item/implant/chem/implant(mob/living/target, mob/user, silent = FALSE)
+ . = ..()
+ if(!.)
+ return
+ if(chemholder)
+ chemholder.forceMove(target)
+ return
+ chemholder = new(imp_in, src)
+ reagents.trans_to(chemholder, reagents.total_volume)
+
+/obj/item/implant/chem/removed(mob/target, silent = FALSE, special = 0)
+ . = ..()
+ if(!.)
+ return
+ if(!special)
+ chemholder.reagents.trans_to(src, chemholder.reagents.total_volume)
+ QDEL_NULL(chemholder)
+ else
+ chemholder?.moveToNullspace()
+
/obj/item/implant/chem/trigger(emote, mob/living/source)
if(emote == "deathgasp")
if(istype(source) && !(source.stat == DEAD))
@@ -45,13 +66,12 @@
injectamount = reagents.total_volume
else
injectamount = cause
- reagents.trans_to(R, injectamount)
+ chemholder.reagents.trans_to(R, injectamount)
to_chat(R, "You hear a faint beep.")
- if(!reagents.total_volume)
+ if(!chemholder.reagents.total_volume)
to_chat(R, "You hear a faint click from your chest.")
qdel(src)
-
/obj/item/implantcase/chem
name = "implant case - 'Remote Chemical'"
desc = "A glass case containing a remote chemical implant."
@@ -63,3 +83,17 @@
return TRUE
else
return ..()
+
+/obj/item/imp_chemholder
+ var/obj/item/implant/chem/implant
+
+/obj/item/imp_chemholder/Initialize(mapload, obj/item/implant/chem/_implant)
+ . = ..()
+ create_reagents(50)
+ implant = _implant
+
+/obj/item/imp_chemholder/Destroy()
+ if(implant?.imp_in)
+ qdel(implant)
+ else
+ return ..()
\ No newline at end of file
diff --git a/code/game/objects/items/implants/implant_clown.dm b/code/game/objects/items/implants/implant_clown.dm
index ae1fef109d..bb98e72b30 100644
--- a/code/game/objects/items/implants/implant_clown.dm
+++ b/code/game/objects/items/implants/implant_clown.dm
@@ -11,7 +11,7 @@
/obj/item/implant/sad_trombone/trigger(emote, mob/source)
if(emote == "deathgasp")
- playsound(loc, 'sound/misc/sadtrombone.ogg', 50, 0)
+ playsound(source.loc, 'sound/misc/sadtrombone.ogg', 50, 0)
/obj/item/implanter/sad_trombone
name = "implanter (sad trombone)"
diff --git a/code/game/objects/items/implants/implant_explosive.dm b/code/game/objects/items/implants/implant_explosive.dm
index e749a6c4be..b93c9419a3 100644
--- a/code/game/objects/items/implants/implant_explosive.dm
+++ b/code/game/objects/items/implants/implant_explosive.dm
@@ -11,8 +11,9 @@
var/popup = FALSE // is the DOUWANNABLOWUP window open?
var/active = FALSE
-/obj/item/implant/explosive/on_mob_death(mob/living/L, gibbed)
- activate("death")
+/obj/item/implant/explosive/trigger(emote, mob/source)
+ if(emote == "deathgasp")
+ activate("death")
/obj/item/implant/explosive/get_data()
var/dat = {"Implant Specifications:
@@ -29,32 +30,18 @@
/obj/item/implant/explosive/activate(cause)
. = ..()
if(!cause || !imp_in || active)
- return 0
+ return FALSE
if(cause == "action_button" && !popup)
popup = TRUE
var/response = alert(imp_in, "Are you sure you want to activate your [name]? This will cause you to explode!", "[name] Confirmation", "Yes", "No")
popup = FALSE
if(response == "No")
- return 0
- heavy = round(heavy)
- medium = round(medium)
- weak = round(weak)
- to_chat(imp_in, "You activate your [name].")
- active = TRUE
- var/turf/boomturf = get_turf(imp_in)
- message_admins("[ADMIN_LOOKUPFLW(imp_in)] has activated their [name] at [ADMIN_VERBOSEJMP(boomturf)], with cause of [cause].")
-//If the delay is short, just blow up already jeez
- if(delay <= 7)
- explosion(src,heavy,medium,weak,weak, flame_range = weak)
- if(imp_in)
- imp_in.gib(1)
- qdel(src)
- return
- timed_explosion()
+ return FALSE
+ addtimer(CALLBACK(src, .proc/timed_explosion, cause), 1)
/obj/item/implant/explosive/implant(mob/living/target)
for(var/X in target.implants)
- if(istype(X, type))
+ if(istype(X, /obj/item/implant/explosive))
var/obj/item/implant/explosive/imp_e = X
imp_e.heavy += heavy
imp_e.medium += medium
@@ -65,22 +52,37 @@
return ..()
-/obj/item/implant/explosive/proc/timed_explosion()
- imp_in.visible_message("[imp_in] starts beeping ominously!")
- playsound(loc, 'sound/items/timer.ogg', 30, 0)
- sleep(delay*0.25)
- if(imp_in && !imp_in.stat)
+/obj/item/implant/explosive/proc/timed_explosion(cause)
+ if(cause == "death" && imp_in.stat != DEAD)
+ return FALSE
+ heavy = round(heavy)
+ medium = round(medium)
+ weak = round(weak)
+ to_chat(imp_in, "You activate your [name].")
+ active = TRUE
+ var/turf/boomturf = get_turf(imp_in)
+ message_admins("[ADMIN_LOOKUPFLW(imp_in)] has activated their [name] at [ADMIN_VERBOSEJMP(boomturf)], with cause of [cause].")
+ if(delay > 7)
+ imp_in?.visible_message("[imp_in] starts beeping ominously!")
+ playsound(get_turf(imp_in ? imp_in : src), 'sound/items/timer.ogg', 30, 0)
+ addtimer(CALLBACK(src, .proc/double_pain, TRUE), delay * 0.25)
+ addtimer(CALLBACK(src, .proc/double_pain), delay * 0.5)
+ addtimer(CALLBACK(src, .proc/double_pain), delay * 0.75)
+ addtimer(CALLBACK(src, .proc/boom_goes_the_weasel), delay)
+ else //If the delay is short, just blow up already jeez
+ boom_goes_the_weasel()
+
+/obj/item/implant/explosive/proc/double_pain(message = FALSE)
+ playsound(get_turf(imp_in ? imp_in : src), 'sound/items/timer.ogg', 30, 0)
+ if(!imp_in)
+ return
+ if(message && imp_in.stat == CONSCIOUS)
imp_in.visible_message("[imp_in] doubles over in pain!")
- imp_in.Knockdown(140)
- playsound(loc, 'sound/items/timer.ogg', 30, 0)
- sleep(delay*0.25)
- playsound(loc, 'sound/items/timer.ogg', 30, 0)
- sleep(delay*0.25)
- playsound(loc, 'sound/items/timer.ogg', 30, 0)
- sleep(delay*0.25)
- explosion(src,heavy,medium,weak,weak, flame_range = weak)
- if(imp_in)
- imp_in.gib(1)
+ imp_in.Knockdown(140)
+
+/obj/item/implant/explosive/proc/boom_goes_the_weasel()
+ explosion(get_turf(imp_in ? imp_in : src), heavy, medium, weak, weak, flame_range = weak)
+ imp_in?.gib(TRUE)
qdel(src)
/obj/item/implant/explosive/macro
@@ -95,17 +97,7 @@
/obj/item/implant/explosive/macro/implant(mob/living/target)
for(var/X in target.implants)
if(istype(X, type))
- return 0
-
- for(var/Y in target.implants)
- if(istype(Y, /obj/item/implant/explosive))
- var/obj/item/implant/explosive/imp_e = Y
- heavy += imp_e.heavy
- medium += imp_e.medium
- weak += imp_e.weak
- delay += imp_e.delay
- qdel(imp_e)
- break
+ return FALSE
return ..()
diff --git a/code/game/objects/items/implants/implant_krav_maga.dm b/code/game/objects/items/implants/implant_krav_maga.dm
index 3a751ecd0e..373658b386 100644
--- a/code/game/objects/items/implants/implant_krav_maga.dm
+++ b/code/game/objects/items/implants/implant_krav_maga.dm
@@ -21,7 +21,7 @@
return
if(!H.mind)
return
- if(istype(H.mind.martial_art, /datum/martial_art/krav_maga))
+ if(H.mind.has_martialart(MARTIALART_KRAVMAGA))
style.remove(H)
else
style.teach(H,1)
diff --git a/code/game/objects/items/implants/implant_mindshield.dm b/code/game/objects/items/implants/implant_mindshield.dm
index b9907cbfca..2a99c6e1ec 100644
--- a/code/game/objects/items/implants/implant_mindshield.dm
+++ b/code/game/objects/items/implants/implant_mindshield.dm
@@ -1,7 +1,6 @@
/obj/item/implant/mindshield
name = "mindshield implant"
desc = "Protects against brainwashing."
- resistance_flags = INDESTRUCTIBLE
activated = 0
/obj/item/implant/mindshield/get_data()
diff --git a/code/game/objects/items/implants/implant_misc.dm b/code/game/objects/items/implants/implant_misc.dm
index 3a4295c61e..75b0c67798 100644
--- a/code/game/objects/items/implants/implant_misc.dm
+++ b/code/game/objects/items/implants/implant_misc.dm
@@ -69,62 +69,4 @@
healthstring = "Oxygen Deprivation Damage => [round(L.getOxyLoss())] Fire Damage => [round(L.getFireLoss())] Toxin Damage => [round(L.getToxLoss())] Brute Force Damage => [round(L.getBruteLoss())]"
if (!healthstring)
healthstring = "ERROR"
- return healthstring
-
-/obj/item/implant/radio
- name = "internal radio implant"
- activated = TRUE
- var/obj/item/radio/radio
- var/radio_key
- var/subspace_transmission = FALSE
- icon = 'icons/obj/radio.dmi'
- icon_state = "walkietalkie"
-
-/obj/item/implant/radio/activate()
- . = ..()
- // needs to be GLOB.deep_inventory_state otherwise it won't open
- radio.ui_interact(usr, "main", null, FALSE, null, GLOB.deep_inventory_state)
-
-/obj/item/implant/radio/Initialize(mapload)
- . = ..()
-
- radio = new(src)
- // almost like an internal headset, but without the
- // "must be in ears to hear" restriction.
- radio.name = "internal radio"
- radio.subspace_transmission = subspace_transmission
- radio.canhear_range = 0
- if(radio_key)
- radio.keyslot = new radio_key
- radio.recalculateChannels()
-
-/obj/item/implant/radio/mining
- radio_key = /obj/item/encryptionkey/headset_cargo
-
-/obj/item/implant/radio/syndicate
- desc = "Are you there God? It's me, Syndicate Comms Agent."
- radio_key = /obj/item/encryptionkey/syndicate
- subspace_transmission = TRUE
-
-/obj/item/implant/radio/slime
- name = "slime radio"
- icon = 'icons/obj/surgery.dmi'
- icon_state = "adamantine_resonator"
- radio_key = /obj/item/encryptionkey/headset_sci
- subspace_transmission = TRUE
-
-/obj/item/implant/radio/get_data()
- var/dat = {"Implant Specifications:
- Name: Internal Radio Implant
- Life: 24 hours
- Implant Details: Allows user to use an internal radio, useful if user expects equipment loss, or cannot equip conventional radios."}
- return dat
-
-/obj/item/implanter/radio
- name = "implanter (internal radio)"
- imp_type = /obj/item/implant/radio
-
-/obj/item/implanter/radio/syndicate
- name = "implanter (internal syndicate radio)"
- imp_type = /obj/item/implant/radio/syndicate
-
+ return healthstring
\ No newline at end of file
diff --git a/code/game/objects/items/implants/implant_radio.dm b/code/game/objects/items/implants/implant_radio.dm
new file mode 100644
index 0000000000..5d3d579a4e
--- /dev/null
+++ b/code/game/objects/items/implants/implant_radio.dm
@@ -0,0 +1,69 @@
+/obj/item/implant/radio
+ name = "internal radio implant"
+ activated = TRUE
+ var/obj/item/radio/internal/radio
+ var/radio_key
+ var/subspace_transmission = FALSE
+ icon = 'icons/obj/radio.dmi'
+ icon_state = "walkietalkie"
+
+/obj/item/implant/radio/activate()
+ . = ..()
+ // needs to be GLOB.deep_inventory_state otherwise it won't open
+ radio.ui_interact(usr, "main", null, FALSE, null, GLOB.deep_inventory_state)
+
+/obj/item/implant/radio/implant(mob/living/target, mob/user, silent = FALSE)
+ . = ..()
+ if(!.)
+ return
+ if(radio)
+ radio.forceMove(target)
+ return
+ radio = new(target)
+ // almost like an internal headset, but without the
+ // "must be in ears to hear" restriction.
+ radio.name = "internal radio"
+ radio.subspace_transmission = subspace_transmission
+ radio.canhear_range = 0
+ if(radio_key)
+ radio.keyslot = new radio_key
+ radio.recalculateChannels()
+
+/obj/item/implant/radio/removed(mob/target, silent = FALSE, special = 0)
+ . = ..()
+ if(!.)
+ return
+ if(!special)
+ qdel(radio)
+ else
+ radio?.moveToNullspace()
+
+/obj/item/implant/radio/mining
+ radio_key = /obj/item/encryptionkey/headset_cargo
+
+/obj/item/implant/radio/syndicate
+ desc = "Are you there God? It's me, Syndicate Comms Agent."
+ radio_key = /obj/item/encryptionkey/syndicate
+ subspace_transmission = TRUE
+
+/obj/item/implant/radio/slime
+ name = "slime radio"
+ icon = 'icons/obj/surgery.dmi'
+ icon_state = "adamantine_resonator"
+ radio_key = /obj/item/encryptionkey/headset_sci
+ subspace_transmission = TRUE
+
+/obj/item/implant/radio/get_data()
+ var/dat = {"Implant Specifications:
+ Name: Internal Radio Implant
+ Life: 24 hours
+ Implant Details: Allows user to use an internal radio, useful if user expects equipment loss, or cannot equip conventional radios."}
+ return dat
+
+/obj/item/implanter/radio
+ name = "implanter (internal radio)"
+ imp_type = /obj/item/implant/radio
+
+/obj/item/implanter/radio/syndicate
+ name = "implanter (internal syndicate radio)"
+ imp_type = /obj/item/implant/radio/syndicate
\ No newline at end of file
diff --git a/code/game/objects/items/implants/implant_stealth.dm b/code/game/objects/items/implants/implant_stealth.dm
index 84f9f5f454..eb58d76d1b 100644
--- a/code/game/objects/items/implants/implant_stealth.dm
+++ b/code/game/objects/items/implants/implant_stealth.dm
@@ -9,6 +9,7 @@
name = "inconspicious box"
desc = "It's so normal that you didn't notice it before."
icon_state = "agentbox"
+ max_integrity = 1
use_mob_movespeed = TRUE
/obj/structure/closet/cardboard/agent/proc/go_invisible()
diff --git a/code/game/objects/items/implants/implant_storage.dm b/code/game/objects/items/implants/implant_storage.dm
index ec6b4d64c3..739873af00 100644
--- a/code/game/objects/items/implants/implant_storage.dm
+++ b/code/game/objects/items/implants/implant_storage.dm
@@ -4,30 +4,44 @@
icon_state = "storage"
item_color = "r"
var/max_slot_stacking = 4
+ var/obj/item/storage/bluespace_pocket/pocket
/obj/item/implant/storage/activate()
. = ..()
- SEND_SIGNAL(src, COMSIG_TRY_STORAGE_SHOW, imp_in, TRUE)
+ SEND_SIGNAL(pocket, COMSIG_TRY_STORAGE_SHOW, imp_in, TRUE)
/obj/item/implant/storage/removed(source, silent = FALSE, special = 0)
- . = ..()
- if(.)
- if(!special)
- qdel(GetComponent(/datum/component/storage/concrete/implant))
+ if(!special)
+ qdel(pocket)
+ else
+ pocket?.moveToNullspace()
+ return ..()
/obj/item/implant/storage/implant(mob/living/target, mob/user, silent = FALSE)
for(var/X in target.implants)
if(istype(X, type))
var/obj/item/implant/storage/imp_e = X
- GET_COMPONENT_FROM(STR, /datum/component/storage, imp_e)
+ GET_COMPONENT_FROM(STR, /datum/component/storage, imp_e.pocket)
if(!STR || (STR && STR.max_items < max_slot_stacking))
- imp_e.AddComponent(/datum/component/storage/concrete/implant)
+ imp_e.pocket.AddComponent(/datum/component/storage/concrete/implant)
qdel(src)
return TRUE
return FALSE
- AddComponent(/datum/component/storage/concrete/implant)
+ . = ..()
+ if(.)
+ if(pocket)
+ pocket.forceMove(target)
+ else
+ pocket = new(target)
- return ..()
+/obj/item/storage/bluespace_pocket
+ name = "internal bluespace pocket"
+ icon_state = "pillbox"
+ w_class = WEIGHT_CLASS_TINY
+ desc = "A tiny yet spacious pocket, usually found implanted inside sneaky syndicate agents and nowhere else."
+ component_type = /datum/component/storage/concrete/implant
+ resistance_flags = INDESTRUCTIBLE //A bomb!
+ item_flags = DROPDEL
/obj/item/implanter/storage
name = "implanter (storage)"
diff --git a/code/game/objects/items/implants/implant_track.dm b/code/game/objects/items/implants/implant_track.dm
index 913c577f2c..4b7ae3bbac 100644
--- a/code/game/objects/items/implants/implant_track.dm
+++ b/code/game/objects/items/implants/implant_track.dm
@@ -3,8 +3,8 @@
desc = "Track with this."
activated = 0
-/obj/item/implant/tracking/New()
- ..()
+/obj/item/implant/tracking/Initialize()
+ . = ..()
GLOB.tracked_implants += src
/obj/item/implant/tracking/Destroy()
@@ -15,7 +15,31 @@
imp_type = /obj/item/implant/tracking
/obj/item/implanter/tracking/gps
- imp_type = /obj/item/gps/mining/internal
+ imp_type = /obj/item/implant/gps
+
+/obj/item/implant/gps
+ name = "\improper GPS implant"
+ desc = "Track with this and a GPS."
+ activated = FALSE
+ var/obj/item/gps/internal/mining/real_gps
+
+/obj/item/implant/gps/implant(mob/living/target, mob/user, silent = FALSE)
+ . = ..()
+ if(!.)
+ return
+ if(real_gps)
+ real_gps.forceMove(target)
+ else
+ real_gps = new(target)
+
+/obj/item/implant/gps/removed(mob/living/source, silent = FALSE, special = 0)
+ . = ..()
+ if(!.)
+ return
+ if(!special)
+ qdel(real_gps)
+ else
+ real_gps?.moveToNullspace()
/obj/item/implant/tracking/get_data()
var/dat = {"Implant Specifications:
diff --git a/code/game/objects/items/implants/implantuplink.dm b/code/game/objects/items/implants/implant_uplink.dm
similarity index 93%
rename from code/game/objects/items/implants/implantuplink.dm
rename to code/game/objects/items/implants/implant_uplink.dm
index 9000fbbe34..9895c1e34c 100644
--- a/code/game/objects/items/implants/implantuplink.dm
+++ b/code/game/objects/items/implants/implant_uplink.dm
@@ -1,23 +1,23 @@
-/obj/item/implant/uplink
- name = "uplink implant"
- desc = "Sneeki breeki."
- icon = 'icons/obj/radio.dmi'
- icon_state = "radio"
- lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi'
- righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi'
- var/starting_tc = 0
-
-/obj/item/implant/uplink/Initialize(mapload, _owner)
- . = ..()
- AddComponent(/datum/component/uplink, _owner, TRUE, FALSE, null, starting_tc)
-
-/obj/item/implanter/uplink
- name = "implanter (uplink)"
- imp_type = /obj/item/implant/uplink
-
-/obj/item/implanter/uplink/precharged
- name = "implanter (precharged uplink)"
- imp_type = /obj/item/implant/uplink/precharged
-
-/obj/item/implant/uplink/precharged
- starting_tc = 10
+/obj/item/implant/uplink
+ name = "uplink implant"
+ desc = "Sneeki breeki."
+ icon = 'icons/obj/radio.dmi'
+ icon_state = "radio"
+ lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi'
+ var/starting_tc = 0
+
+/obj/item/implant/uplink/Initialize(mapload, _owner)
+ . = ..()
+ AddComponent(/datum/component/uplink, _owner, TRUE, FALSE, null, starting_tc, GLOB.not_incapacitated_state)
+
+/obj/item/implanter/uplink
+ name = "implanter (uplink)"
+ imp_type = /obj/item/implant/uplink
+
+/obj/item/implanter/uplink/precharged
+ name = "implanter (precharged uplink)"
+ imp_type = /obj/item/implant/uplink/precharged
+
+/obj/item/implant/uplink/precharged
+ starting_tc = 10
diff --git a/code/game/objects/items/manuals.dm b/code/game/objects/items/manuals.dm
index 02d0a1c36a..6ae34e9ca2 100644
--- a/code/game/objects/items/manuals.dm
+++ b/code/game/objects/items/manuals.dm
@@ -244,7 +244,7 @@
..()
/obj/item/book/manual/wiki/proc/initialize_wikibook()
- var/wikiurl = CONFIG_GET(string/wikiurl)
+ var/wikiurl = CONFIG_GET(string/wikiurltg)
if(wikiurl)
dat = {"
@@ -270,13 +270,67 @@
"}
-/obj/item/book/manual/wiki/chemistry
+/obj/item/book/manual/wiki/cit
+ name = "Citadel infobook"
+ icon_state ="book8"
+ author = "Nanotrasen"
+ title = "Citadel infobook"
+ page_link = ""
+ window_size = "1500x800" //Too squashed otherwise
+
+/obj/item/book/manual/wiki/cit/initialize_wikibook()
+ var/wikiurl = CONFIG_GET(string/wikiurl)
+ if(wikiurl)
+ dat = {"
+
+
+
+
+
+
+
You start skimming through the manual...
+
+
+
+
+
+ "}
+
+/obj/item/book/manual/wiki/cit/chemistry
name = "Chemistry Textbook"
icon_state ="chemistrybook"
author = "Nanotrasen"
title = "Chemistry Textbook"
+ page_link = "main/guides/guide_chemistry"
+
+/obj/item/book/manual/wiki/cit/chem_recipies
+ name = "Chemistry Recipies"
+ icon_state ="chemrecibook"
+ author = "Chemcat"
+ title = "Chemistry Recipies"
+ page_link = "main/guides/chem_recipies"
+
+/obj/item/book/manual/wiki/chemistry
+ name = "Outdated Chemistry Textbook"
+ icon_state ="chemistrybook_old"
+ author = "Nanotrasen"
+ title = "Outdated Chemistry Textbook"
page_link = "Guide_to_chemistry"
+/obj/item/book/manual/wiki/chemistry/Initialize()
+ ..()
+ new /obj/item/book/manual/wiki/cit/chemistry(loc)
+ new /obj/item/book/manual/wiki/cit/chem_recipies(loc)
+
/obj/item/book/manual/wiki/engineering_construction
name = "Station Repairs and Construction"
icon_state ="bookEngineering"
diff --git a/code/game/objects/items/melee/energy.dm b/code/game/objects/items/melee/energy.dm
index 935d2a007e..57b9973aa3 100644
--- a/code/game/objects/items/melee/energy.dm
+++ b/code/game/objects/items/melee/energy.dm
@@ -5,9 +5,12 @@
armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 30)
resistance_flags = FIRE_PROOF
var/brightness_on = 3
+ total_mass = 0.4 //Survival flashlights typically weigh around 5 ounces.
+
/obj/item/melee/transforming/energy/Initialize()
. = ..()
+ total_mass_on = (total_mass_on ? total_mass_on : (w_class_on * 0.75))
if(active)
set_light(brightness_on)
START_PROCESSING(SSobj, src)
@@ -79,6 +82,7 @@
attack_verb_off = list("attacked", "chopped", "cleaved", "torn", "cut")
attack_verb_on = list()
light_color = "#40ceff"
+ total_mass = null
/obj/item/melee/transforming/energy/axe/suicide_act(mob/user)
user.visible_message("[user] swings [src] towards [user.p_their()] head! It looks like [user.p_theyre()] trying to commit suicide!")
@@ -138,6 +142,8 @@
w_class = WEIGHT_CLASS_NORMAL
sharpness = IS_SHARP
light_color = "#40ceff"
+ tool_behaviour = TOOL_SAW
+ toolspeed = 0.7
/obj/item/melee/transforming/energy/sword/cyborg/saw/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK)
return 0
diff --git a/code/game/objects/items/melee/misc.dm b/code/game/objects/items/melee/misc.dm
index 33a48c17bb..10b84917bb 100644
--- a/code/game/objects/items/melee/misc.dm
+++ b/code/game/objects/items/melee/misc.dm
@@ -42,8 +42,9 @@
force = 20
throwforce = 10
hitsound = 'sound/weapons/bladeslice.ogg'
- attack_verb = list("attacked", "slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut")
+ attack_verb = list("attacked", "impaled", "stabbed", "sliced", "torn", "ripped", "diced", "cut")
sharpness = IS_SHARP
+ total_mass = TOTAL_MASS_HAND_REPLACEMENT
/obj/item/melee/synthetic_arm_blade/Initialize()
. = ..()
@@ -67,6 +68,7 @@
attack_verb = list("slashed", "cut")
hitsound = 'sound/weapons/rapierhit.ogg'
materials = list(MAT_METAL = 1000)
+ total_mass = 3.4
/obj/item/melee/sabre/Initialize()
. = ..()
@@ -89,11 +91,16 @@
if(istype(B))
playsound(B, 'sound/items/sheath.ogg', 25, 1)
+/obj/item/melee/sabre/get_belt_overlay()
+ return mutable_appearance('icons/obj/clothing/belt_overlays.dmi', "sabre")
+
+/obj/item/melee/sabre/get_worn_belt_overlay(icon_file)
+ return mutable_appearance(icon_file, "-sabre")
+
/obj/item/melee/sabre/suicide_act(mob/living/user)
user.visible_message("[user] is trying to cut off all [user.p_their()] limbs with [src]! it looks like [user.p_theyre()] trying to commit suicide!")
var/i = 0
- var/originally_nodropped = item_flags & NODROP
- item_flags |= NODROP
+ ADD_TRAIT(src, TRAIT_NODROP, SABRE_SUICIDE_TRAIT)
if(iscarbon(user))
var/mob/living/carbon/Cuser = user
var/obj/item/bodypart/holding_bodypart = Cuser.get_holding_bodypart_of_item(src)
@@ -118,7 +125,7 @@
for(bodypart in limbs_to_dismember)
i++
addtimer(CALLBACK(src, .proc/suicide_dismember, user, bodypart), speedbase * i)
- addtimer(CALLBACK(src, .proc/manual_suicide, user, originally_nodropped), (5 SECONDS) * i)
+ addtimer(CALLBACK(src, .proc/manual_suicide, user), (5 SECONDS) * i)
return MANUAL_SUICIDE
/obj/item/melee/sabre/proc/suicide_dismember(mob/living/user, obj/item/bodypart/affecting)
@@ -131,8 +138,36 @@
if(!QDELETED(user))
user.adjustBruteLoss(200)
user.death(FALSE)
- if(!originally_nodropped)
- item_flags &= ~NODROP
+ REMOVE_TRAIT(src, TRAIT_NODROP, SABRE_SUICIDE_TRAIT)
+
+/obj/item/melee/rapier
+ name = "plastitanium rapier"
+ desc = "A impossibly thin blade made of plastitanium with a tip made of diamond. It looks to be able to cut through any armor."
+ icon = 'icons/obj/items_and_weapons.dmi'
+ icon_state = "rapier"
+ item_state = "rapier"
+ lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi'
+ force = 25
+ throwforce = 35
+ block_chance = 0
+ armour_penetration = 100
+ flags_1 = CONDUCT_1
+ obj_flags = UNIQUE_RENAME
+ w_class = WEIGHT_CLASS_BULKY
+ sharpness = IS_SHARP_ACCURATE //It cant be sharpend cook -_-
+ attack_verb = list("slashed", "cut", "pierces", "pokes")
+ total_mass = 3.4
+
+/obj/item/melee/rapier/Initialize()
+ . = ..()
+ AddComponent(/datum/component/butchering, 20, 65, 0)
+
+/obj/item/melee/rapier/get_belt_overlay()
+ return mutable_appearance('icons/obj/clothing/belt_overlays.dmi', "rapier")
+
+/obj/item/melee/rapier/get_worn_belt_overlay(icon_file)
+ return mutable_appearance(icon_file, "-rapier")
/obj/item/melee/classic_baton
name = "police baton"
@@ -145,8 +180,13 @@
slot_flags = ITEM_SLOT_BELT
force = 12 //9 hit crit
w_class = WEIGHT_CLASS_NORMAL
- var/cooldown = 0
+ var/cooldown = 13
var/on = TRUE
+ var/last_hit = 0
+ var/stun_stam_cost_coeff = 1.25
+ var/hardstun_ds = 1
+ var/softstun_ds = 0
+ var/stam_dmg = 30
/obj/item/melee/classic_baton/attack(mob/living/target, mob/living/user)
if(!on)
@@ -172,12 +212,10 @@
if(!isliving(target))
return
if (user.a_intent == INTENT_HARM)
- if(!..())
- return
- if(!iscyborg(target))
+ if(!..() || !iscyborg(target))
return
else
- if(cooldown <= world.time)
+ if(last_hit < world.time)
if(ishuman(target))
var/mob/living/carbon/human/H = target
if (H.check_shields(src, 0, "[user]'s [name]", MELEE_ATTACK))
@@ -185,7 +223,7 @@
if(check_martial_counter(H, user))
return
playsound(get_turf(src), 'sound/effects/woodhit.ogg', 75, 1, -1)
- target.Knockdown(60)
+ target.Knockdown(softstun_ds, TRUE, FALSE, hardstun_ds, stam_dmg)
log_combat(user, target, "stunned", src)
src.add_fingerprint(user)
target.visible_message("[user] has knocked down [target] with [src]!", \
@@ -194,7 +232,7 @@
target.LAssailant = null
else
target.LAssailant = user
- cooldown = world.time + 40
+ last_hit = world.time + cooldown
user.adjustStaminaLossBuffered(getweight())//CIT CHANGE - makes swinging batons cost stamina
/obj/item/melee/classic_baton/telescopic
@@ -210,6 +248,7 @@
item_flags = NONE
force = 0
on = FALSE
+ total_mass = TOTAL_MASS_NORMAL_ITEM
/obj/item/melee/classic_baton/telescopic/suicide_act(mob/user)
var/mob/living/carbon/human/H = user
@@ -370,6 +409,7 @@
var/static/list/ovens
var/on = FALSE
var/datum/beam/beam
+ total_mass = 2.5
/obj/item/melee/roastingstick/Initialize()
. = ..()
diff --git a/code/game/objects/items/melee/transforming.dm b/code/game/objects/items/melee/transforming.dm
index 0d39e6c847..aabb930bb2 100644
--- a/code/game/objects/items/melee/transforming.dm
+++ b/code/game/objects/items/melee/transforming.dm
@@ -13,6 +13,7 @@
var/list/nemesis_factions //Any mob with a faction that exists in this list will take bonus damage/effects
var/w_class_on = WEIGHT_CLASS_BULKY
var/clumsy_check = TRUE
+ var/total_mass_on //Total mass in ounces when transformed. Primarily for balance purposes. Don't think about it too hard.
/obj/item/melee/transforming/Initialize()
. = ..()
@@ -46,6 +47,7 @@
active = !active
if(active)
force = force_on
+ total_mass = total_mass_on
throwforce = throwforce_on
hitsound = hitsound_on
throw_speed = 4
@@ -62,6 +64,7 @@
attack_verb = attack_verb_off
icon_state = initial(icon_state)
w_class = initial(w_class)
+ total_mass = initial(total_mass)
if(is_sharp())
var/datum/component/butchering/BT = LoadComponent(/datum/component/butchering)
BT.butchering_enabled = TRUE
@@ -84,4 +87,4 @@
/obj/item/melee/transforming/proc/clumsy_transform_effect(mob/living/user)
if(clumsy_check && HAS_TRAIT(user, TRAIT_CLUMSY) && prob(50))
to_chat(user, "You accidentally cut yourself with [src], like a doofus!")
- user.take_bodypart_damage(5,5)
+ user.take_bodypart_damage(5,5)
\ No newline at end of file
diff --git a/code/game/objects/items/mop.dm b/code/game/objects/items/mop.dm
index 44ccd7aad5..7524fc9007 100644
--- a/code/game/objects/items/mop.dm
+++ b/code/game/objects/items/mop.dm
@@ -15,7 +15,7 @@
var/mopping = 0
var/mopcount = 0
var/mopcap = 5
- var/stamusage = 5
+ var/stamusage = 2
force_string = "robust... against germs"
var/insertable = TRUE
@@ -94,7 +94,7 @@
force = 6
throwforce = 8
throw_range = 4
- stamusage = 2
+ stamusage = 1
var/refill_enabled = TRUE //Self-refill toggle for when a janitor decides to mop with something other than water.
var/refill_rate = 1 //Rate per process() tick mop refills itself
var/refill_reagent = "water" //Determins what reagent to use for refilling, just in case someone wanted to make a HOLY MOP OF PURGING
diff --git a/code/game/objects/items/paint.dm b/code/game/objects/items/paint.dm
index a6f5830dd4..cc2f5e9be7 100644
--- a/code/game/objects/items/paint.dm
+++ b/code/game/objects/items/paint.dm
@@ -90,14 +90,14 @@
add_fingerprint(user)
-/obj/item/paint/afterattack(turf/target, mob/user, proximity)
+/obj/item/paint/afterattack(atom/target, mob/user, proximity)
. = ..()
if(!proximity)
return
if(paintleft <= 0)
icon_state = "paint_empty"
return
- if(!istype(target) || isspaceturf(target))
+ if(!isturf(target) || isspaceturf(target))
return
var/newcolor = "#" + item_color
target.add_atom_colour(newcolor, WASHABLE_COLOUR_PRIORITY)
@@ -105,12 +105,14 @@
/obj/item/paint/paint_remover
gender = PLURAL
name = "paint remover"
- desc = "Used to remove color from floors and walls."
+ desc = "Used to remove color from anything."
icon_state = "paint_neutral"
-/obj/item/paint/paint_remover/afterattack(turf/target, mob/user, proximity)
+/obj/item/paint/paint_remover/afterattack(atom/target, mob/user, proximity)
. = ..()
if(!proximity)
return
- if(istype(target) && target.color != initial(target.color))
+ if(!isturf(target) || !isobj(target))
+ return
+ if(target.color != initial(target.color))
target.remove_atom_colour(WASHABLE_COLOUR_PRIORITY)
diff --git a/code/game/objects/items/pet_carrier.dm b/code/game/objects/items/pet_carrier.dm
index a73be6e12e..30442d7304 100644
--- a/code/game/objects/items/pet_carrier.dm
+++ b/code/game/objects/items/pet_carrier.dm
@@ -122,7 +122,7 @@
if(user.mob_size <= MOB_SIZE_SMALL)
to_chat(user, "You poke a limb through [src]'s bars and start fumbling for the lock switch... (This will take some time.)")
to_chat(loc, "You see [user] reach through the bars and fumble for the lock switch!")
- if(!do_after(user, rand(300, 400), target = user) || open || !locked || !user in occupants)
+ if(!do_after(user, rand(300, 400), target = user) || open || !locked || !(user in occupants))
return
loc.visible_message("[user] flips the lock switch on [src] by reaching through!", null, null, null, user)
to_chat(user, "Bingo! The lock pops open!")
@@ -132,7 +132,7 @@
else
loc.visible_message("[src] starts rattling as something pushes against the door!", null, null, null, user)
to_chat(user, "You start pushing out of [src]... (This will take about 20 seconds.)")
- if(!do_after(user, 200, target = user) || open || !locked || !user in occupants)
+ if(!do_after(user, 200, target = user) || open || !locked || !(user in occupants))
return
loc.visible_message("[user] shoves out of [src]!", null, null, null, user)
to_chat(user, "You shove open [src]'s door against the lock's resistance and fall out!")
@@ -185,7 +185,7 @@
occupant_weight += occupant.mob_size
/obj/item/pet_carrier/proc/remove_occupant(mob/living/occupant, turf/new_turf)
- if(!occupant in occupants || !istype(occupant))
+ if(!(occupant in occupants) || !istype(occupant))
return
occupant.forceMove(new_turf ? new_turf : drop_location())
occupants -= occupant
diff --git a/code/game/objects/items/plushes.dm b/code/game/objects/items/plushes.dm
index c93c79a25d..48588cf9f3 100644
--- a/code/game/objects/items/plushes.dm
+++ b/code/game/objects/items/plushes.dm
@@ -578,10 +578,6 @@
icon_state = "vulken"
item_state = "vulken"
-/obj/item/toy/plush/snakeplushie/jecca
- icon_state = "jecca"
- item_state = "jecca"
-
/obj/item/toy/plush/nukeplushie
name = "operative plushie"
desc = "A stuffed toy that resembles a syndicate nuclear operative. The tag claims operatives to be purely fictitious."
@@ -633,14 +629,10 @@
/obj/item/toy/plush/mothplushie
name = "insect plushie"
- desc = "An adorable stuffed toy that resembles some kind of insect"
- icon_state = "cydia"
- item_state = "cydia"
- squeak_override = list('modular_citadel/sound/voice/mothsqueak.ogg' = 1)
-
-/obj/item/toy/plush/mothplushie/bumble
+ desc = "An adorable stuffed toy that resembles some kind of insect."
icon_state = "bumble"
item_state = "bumble"
+ squeak_override = list('modular_citadel/sound/voice/mothsqueak.ogg' = 1)
/obj/item/toy/plush/mothplushie/nameko
icon_state = "nameko"
@@ -672,6 +664,27 @@
item_state = "box"
attack_verb = list("open", "closed", "packed", "hidden", "rigged", "bombed", "sent", "gave")
+/obj/item/toy/plush/slaggy
+ name = "slag plushie"
+ desc = "A piece of slag with some googly eyes and a drawn on mouth."
+ icon_state = "slaggy"
+ item_state = "slaggy"
+ attack_verb = list("melted", "refined", "stared")
+
+/obj/item/toy/plush/mr_buckety
+ name = "bucket plushie"
+ desc = "A bucket that is missing its handle with some googly eyes and a drawn on mouth."
+ icon_state = "mr_buckety"
+ item_state = "mr_buckety"
+ attack_verb = list("filled", "dumped", "stared")
+
+/obj/item/toy/plush/dr_scanny
+ name = "scanner plushie"
+ desc = "A old outdated scanner that has been modified to have googly eyes, a dawn on mouth and, heart."
+ icon_state = "dr_scanny"
+ item_state = "dr_scanny"
+ attack_verb = list("scanned", "beeped", "stared")
+
/obj/item/toy/plush/borgplushie
name = "robot plushie"
desc = "An adorable stuffed toy of a robot."
@@ -760,8 +773,10 @@
item_state = "blep"
/obj/item/toy/plush/mammal/circe
+ desc = "A luxuriously soft toy that resembles a nine-tailed kitsune."
icon_state = "circe"
item_state = "circe"
+ attack_verb = list("medicated", "tailhugged", "kissed")
/obj/item/toy/plush/mammal/robin
icon_state = "robin"
@@ -826,8 +841,10 @@
item_state = "rae"
/obj/item/toy/plush/mammal/zed
+ desc = "A masked stuffed toy that resembles a fierce miner. He even comes with his own little crusher!"
icon_state = "zed"
item_state = "zed"
+ attack_verb = list("ENDED", "CRUSHED", "GNOMED")
/obj/item/toy/plush/mammal/justin
icon_state = "justin"
@@ -839,6 +856,12 @@
item_state = "reece"
attack_verb = list("healed", "cured", "demoted")
+/obj/item/toy/plush/mammal/redwood
+ desc = "An adorable stuffed toy resembling a Nanotrasen Captain. That just happens to be a bunny."
+ icon_state = "redwood"
+ item_state = "redwood"
+ attack_verb = list("ordered", "bapped", "reprimanded")
+
/obj/item/toy/plush/mammal/dog
desc = "An adorable stuffed toy that resembles a canine."
icon_state = "katlin"
@@ -885,6 +908,12 @@
obj_flags = UNIQUE_RENAME
unique_reskin = list("Goodboye" = "fritz", "Badboye" = "fritz_bad")
+/obj/item/toy/plush/mammal/dog/jesse
+ desc = "An adorable wolf toy that resembles a cream-colored wolf. He has a little pride flag!"
+ icon_state = "jesse"
+ item_state = "jesse"
+ attack_verb = list("greeted", "merc'd", "howdy'd")
+
/obj/item/toy/plush/catgirl
name = "feline plushie"
desc = "An adorable stuffed toy that resembles a feline."
@@ -922,3 +951,15 @@
item_state = "fermis"
attack_verb = list("cuddled", "petpatted", "wigglepurred")
squeak_override = list('modular_citadel/sound/voice/merowr.ogg' = 1)
+
+/obj/item/toy/plush/catgirl/mariaf
+ desc = "An adorable stuffed toy that resembles a very tall cat girl."
+ icon_state = "mariaf"
+ item_state = "mariaf"
+ attack_verb = list("hugged", "stabbed", "licked")
+
+/obj/item/toy/plush/catgirl/maya
+ desc = "An adorable stuffed toy that resembles an angry cat girl. She has her own tiny nuke disk!"
+ icon_state = "maya"
+ item_state = "maya"
+ attack_verb = list("nuked", "arrested", "harmbatonned")
diff --git a/code/game/objects/items/robot/robot_items.dm b/code/game/objects/items/robot/robot_items.dm
index 6d7c3036f9..5454b0fdb8 100644
--- a/code/game/objects/items/robot/robot_items.dm
+++ b/code/game/objects/items/robot/robot_items.dm
@@ -282,11 +282,13 @@
var/cooldown = 0
/obj/item/harmalarm/emag_act(mob/user)
+ . = ..()
obj_flags ^= EMAGGED
if(obj_flags & EMAGGED)
to_chat(user, "You short out the safeties on [src]!")
else
to_chat(user, "You reset the safeties on [src]!")
+ return TRUE
/obj/item/harmalarm/attack_self(mob/user)
var/safety = !(obj_flags & EMAGGED)
diff --git a/code/game/objects/items/robot/robot_parts.dm b/code/game/objects/items/robot/robot_parts.dm
index cd2ccff170..95110692c9 100644
--- a/code/game/objects/items/robot/robot_parts.dm
+++ b/code/game/objects/items/robot/robot_parts.dm
@@ -254,7 +254,7 @@
to_chat(user, "The MMI indicates that their mind is currently inactive; it might change!")
return
- if(BM.stat == DEAD || (M.brain && M.brain.damaged_brain))
+ if(BM.stat == DEAD || (M.brain && M.brain.organ_flags & ORGAN_FAILING))
to_chat(user, "Sticking a dead brain into the frame would sort of defeat the purpose!")
return
diff --git a/code/game/objects/items/robot/robot_upgrades.dm b/code/game/objects/items/robot/robot_upgrades.dm
index d5806494e6..9c929a6ebf 100644
--- a/code/game/objects/items/robot/robot_upgrades.dm
+++ b/code/game/objects/items/robot/robot_upgrades.dm
@@ -9,7 +9,7 @@
var/locked = FALSE
var/installed = 0
var/require_module = 0
- var/module_type = null
+ var/list/module_type
// if true, is not stored in the robot to be ejected
// if module is reset
var/one_use = FALSE
@@ -18,7 +18,7 @@
if(R.stat == DEAD)
to_chat(user, "[src] will not function on a deceased cyborg.")
return FALSE
- if(module_type && !istype(R.module, module_type))
+ if(module_type && !is_type_in_list(R.module, module_type))
to_chat(R, "Upgrade mounting error! No suitable hardpoint detected!")
to_chat(user, "There's no mounting point for the module!")
return FALSE
@@ -93,7 +93,6 @@
desc = "Used to cool a mounted disabler, increasing the potential current in it and thus its recharge rate."
icon_state = "cyborg_upgrade3"
require_module = 1
- //module_type = /obj/item/robot_module/security
/obj/item/borg/upgrade/disablercooler/action(mob/living/silicon/robot/R, user = usr)
. = ..()
@@ -141,7 +140,7 @@
desc = "A diamond drill replacement for the mining module's standard drill."
icon_state = "cyborg_upgrade3"
require_module = 1
- module_type = /obj/item/robot_module/miner
+ module_type = list(/obj/item/robot_module/miner)
/obj/item/borg/upgrade/ddrill/action(mob/living/silicon/robot/R, user = usr)
. = ..()
@@ -173,7 +172,7 @@
desc = "A satchel of holding replacement for mining cyborg's ore satchel module."
icon_state = "cyborg_upgrade3"
require_module = 1
- module_type = /obj/item/robot_module/miner
+ module_type = list(/obj/item/robot_module/miner)
/obj/item/borg/upgrade/soh/action(mob/living/silicon/robot/R)
. = ..()
@@ -200,7 +199,7 @@
desc = "A trash bag of holding replacement for the janiborg's standard trash bag."
icon_state = "cyborg_upgrade3"
require_module = 1
- module_type = /obj/item/robot_module/janitor
+ module_type = list(/obj/item/robot_module/janitor, /obj/item/robot_module/scrubpup)
/obj/item/borg/upgrade/tboh/action(mob/living/silicon/robot/R)
. = ..()
@@ -227,7 +226,7 @@
desc = "An advanced mop replacement for the janiborg's standard mop."
icon_state = "cyborg_upgrade3"
require_module = 1
- module_type = /obj/item/robot_module/janitor
+ module_type = list(/obj/item/robot_module/janitor, /obj/item/robot_module/scrubpup)
/obj/item/borg/upgrade/amop/action(mob/living/silicon/robot/R)
. = ..()
@@ -276,7 +275,7 @@
icon_state = "ash_plating"
resistance_flags = LAVA_PROOF | FIRE_PROOF
require_module = 1
- module_type = /obj/item/robot_module/miner
+ module_type = list(/obj/item/robot_module/miner)
/obj/item/borg/upgrade/lavaproof/action(mob/living/silicon/robot/R, user = usr)
. = ..()
@@ -405,7 +404,9 @@
to produce more advanced and complex medical reagents."
icon_state = "cyborg_upgrade3"
require_module = 1
- module_type = /obj/item/robot_module/medical
+ module_type = list(/obj/item/robot_module/medical,
+ /obj/item/robot_module/syndicate_medical,
+ /obj/item/robot_module/medihound)
var/list/additional_reagents = list()
/obj/item/borg/upgrade/hypospray/action(mob/living/silicon/robot/R, user = usr)
@@ -467,7 +468,9 @@
defibrillator, for on the scene revival."
icon_state = "cyborg_upgrade3"
require_module = 1
- module_type = /obj/item/robot_module/medical
+ module_type = list(/obj/item/robot_module/medical,
+ /obj/item/robot_module/syndicate_medical,
+ /obj/item/robot_module/medihound)
/obj/item/borg/upgrade/defib/action(mob/living/silicon/robot/R, user = usr)
. = ..()
@@ -489,7 +492,9 @@
out procedures"
icon_state = "cyborg_upgrade3"
require_module = 1
- module_type = /obj/item/robot_module/medical
+ module_type = list(/obj/item/robot_module/medical,
+ /obj/item/robot_module/syndicate_medical,
+ /obj/item/robot_module/medihound)
/obj/item/borg/upgrade/processor/action(mob/living/silicon/robot/R, user = usr)
. = ..()
@@ -514,7 +519,7 @@
/obj/item/robot_module/medical,
/obj/item/robot_module/syndicate_medical,
/obj/item/robot_module/medihound,
- /obj/item/robot_module/borgi)
+ /obj/item/robot_module/borgi)
/obj/item/borg/upgrade/advhealth/action(mob/living/silicon/robot/R, user = usr)
. = ..()
@@ -598,7 +603,7 @@
icon = 'icons/obj/storage.dmi'
icon_state = "borgrped"
require_module = TRUE
- module_type = /obj/item/robot_module/engineering
+ module_type = list(/obj/item/robot_module/engineering)
/obj/item/borg/upgrade/rped/action(mob/living/silicon/robot/R, user = usr)
. = ..()
@@ -626,7 +631,9 @@
icon = 'icons/obj/device.dmi'
icon_state = "pinpointer_crew"
require_module = TRUE
- module_type = /obj/item/robot_module/medical
+ module_type = list(/obj/item/robot_module/medical,
+ /obj/item/robot_module/syndicate_medical,
+ /obj/item/robot_module/medihound)
/obj/item/borg/upgrade/pinpointer/action(mob/living/silicon/robot/R, user = usr)
. = ..()
@@ -664,3 +671,33 @@
desc = "Allows you to to turn a cyborg into a clown, honk."
icon_state = "cyborg_upgrade3"
new_module = /obj/item/robot_module/clown
+
+// Citadel's Vtech Controller
+/obj/effect/proc_holder/silicon/cyborg/vtecControl
+ name = "vTec Control"
+ desc = "Allows finer-grained control of the vTec speed boost."
+ action_icon = 'icons/mob/actions.dmi'
+ action_icon_state = "Chevron_State_0"
+
+ var/currentState = 0
+ var/maxReduction = 2
+
+
+/obj/effect/proc_holder/silicon/cyborg/vtecControl/Click(mob/living/silicon/robot/user)
+ var/mob/living/silicon/robot/self = usr
+
+ currentState = (currentState + 1) % 3
+
+ if(usr)
+ switch(currentState)
+ if (0)
+ self.speed = maxReduction
+ if (1)
+ self.speed -= maxReduction*0.5
+ if (2)
+ self.speed -= maxReduction*1.25
+
+ action.button_icon_state = "Chevron_State_[currentState]"
+ action.UpdateButtonIcon()
+
+ return
diff --git a/code/game/objects/items/scrolls.dm b/code/game/objects/items/scrolls.dm
index 07f6edb828..28a4664a24 100644
--- a/code/game/objects/items/scrolls.dm
+++ b/code/game/objects/items/scrolls.dm
@@ -66,7 +66,8 @@
to_chat(user, "The spell matrix was unable to locate a suitable teleport destination for an unknown reason. Sorry.")
return
- user.forceMove(pick(L))
-
- smoke.start()
- uses--
+ if(do_teleport(user, pick(L), forceMove = TRUE, channel = TELEPORT_CHANNEL_MAGIC, forced = TRUE))
+ smoke.start()
+ uses--
+ else
+ to_chat(user, "The spell matrix was disrupted by something near the destination.")
diff --git a/code/game/objects/items/shields.dm b/code/game/objects/items/shields.dm
index a9f0e038df..15127c2e0e 100644
--- a/code/game/objects/items/shields.dm
+++ b/code/game/objects/items/shields.dm
@@ -159,11 +159,13 @@
armor = list("melee" = 25, "bullet" = 25, "laser" = 5, "energy" = 0, "bomb" = 30, "bio" = 0, "rad" = 0, "fire" = 70, "acid" = 80)
lefthand_file = 'icons/mob/inhands/equipment/shields_lefthand.dmi'
righthand_file = 'icons/mob/inhands/equipment/shields_righthand.dmi'
- item_state = "makeshift_shield"
+ icon = 'icons/obj/items_and_weapons.dmi'
+ item_state = "metal"
+ icon_state = "makeshift_shield"
materials = list(MAT_METAL = 18000)
slot_flags = null
- block_chance = 25
- force = 5
+ block_chance = 35
+ force = 10
throwforce = 7
/obj/item/shield/riot/tower
@@ -171,8 +173,11 @@
desc = "A massive shield that can block a lot of attacks, can take a lot of abuse before braking."
armor = list("melee" = 95, "bullet" = 95, "laser" = 75, "energy" = 60, "bomb" = 90, "bio" = 90, "rad" = 0, "fire" = 90, "acid" = 10) //Armor for the item, dosnt transfer to user
item_state = "metal"
+ icon_state = "metal"
+ icon = 'icons/obj/items_and_weapons.dmi'
block_chance = 75 //1/4 shots will hit*
- force = 10
+ force = 16
slowdown = 2
throwforce = 15 //Massive pice of metal
w_class = WEIGHT_CLASS_HUGE
+ item_flags = SLOWS_WHILE_IN_HAND
diff --git a/code/game/objects/items/singularityhammer.dm b/code/game/objects/items/singularityhammer.dm
index 1fe57d151f..b6559c9091 100644
--- a/code/game/objects/items/singularityhammer.dm
+++ b/code/game/objects/items/singularityhammer.dm
@@ -16,6 +16,7 @@
armor = list("melee" = 50, "bullet" = 50, "laser" = 50, "energy" = 0, "bomb" = 50, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100)
resistance_flags = FIRE_PROOF | ACID_PROOF
force_string = "LORD SINGULOTH HIMSELF"
+ total_mass = TOTAL_MASS_MEDIEVAL_WEAPON
/obj/item/twohanded/singularityhammer/New()
..()
@@ -84,6 +85,7 @@
throwforce = 30
throw_range = 7
w_class = WEIGHT_CLASS_HUGE
+ total_mass = TOTAL_MASS_MEDIEVAL_WEAPON
/obj/item/twohanded/mjollnir/proc/shock(mob/living/target)
target.Stun(60)
diff --git a/code/game/objects/items/stacks/bscrystal.dm b/code/game/objects/items/stacks/bscrystal.dm
index 522e1a1153..49a735af9c 100644
--- a/code/game/objects/items/stacks/bscrystal.dm
+++ b/code/game/objects/items/stacks/bscrystal.dm
@@ -33,7 +33,7 @@
use(1)
/obj/item/stack/ore/bluespace_crystal/proc/blink_mob(mob/living/L)
- do_teleport(L, get_turf(L), blink_range, asoundin = 'sound/effects/phasein.ogg')
+ do_teleport(L, get_turf(L), blink_range, asoundin = 'sound/effects/phasein.ogg', channel = TELEPORT_CHANNEL_BLUESPACE)
/obj/item/stack/ore/bluespace_crystal/throw_impact(atom/hit_atom)
if(!..()) // not caught in mid-air
diff --git a/code/game/objects/items/stacks/medical.dm b/code/game/objects/items/stacks/medical.dm
index 4bb3359c2f..5610cbad9a 100644
--- a/code/game/objects/items/stacks/medical.dm
+++ b/code/game/objects/items/stacks/medical.dm
@@ -18,7 +18,7 @@
/obj/item/stack/medical/attack(mob/living/M, mob/user)
- if(M.stat == DEAD)
+ if(M.stat == DEAD && !stop_bleeding)
var/t_him = "it"
if(M.gender == MALE)
t_him = "him"
diff --git a/code/game/objects/items/stacks/sheets/glass.dm b/code/game/objects/items/stacks/sheets/glass.dm
index cf967e25ba..c9fc59a3fe 100644
--- a/code/game/objects/items/stacks/sheets/glass.dm
+++ b/code/game/objects/items/stacks/sheets/glass.dm
@@ -23,8 +23,9 @@ GLOBAL_LIST_INIT(glass_recipes, list ( \
armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 100)
resistance_flags = ACID_PROOF
merge_type = /obj/item/stack/sheet/glass
- grind_results = list("silicon" = 20)
+ grind_results = list(/datum/reagent/silicon = 20)
point_value = 1
+ tableVariant = /obj/structure/table/glass
/obj/item/stack/sheet/glass/suicide_act(mob/living/carbon/user)
user.visible_message("[user] begins to slice [user.p_their()] neck with \the [src]! It looks like [user.p_theyre()] trying to commit suicide!")
@@ -87,7 +88,8 @@ GLOBAL_LIST_INIT(pglass_recipes, list ( \
armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 75, "acid" = 100)
resistance_flags = ACID_PROOF
merge_type = /obj/item/stack/sheet/plasmaglass
- grind_results = list("silicon" = 20, "plasma" = 10)
+ grind_results = list(/datum/reagent/silicon = 20, /datum/reagent/toxin/plasma = 10)
+ tableVariant = /obj/structure/table/plasmaglass
/obj/item/stack/sheet/plasmaglass/fifty
amount = 50
@@ -138,7 +140,7 @@ GLOBAL_LIST_INIT(reinforced_glass_recipes, list ( \
armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 70, "acid" = 100)
resistance_flags = ACID_PROOF
merge_type = /obj/item/stack/sheet/rglass
- grind_results = list("silicon" = 20, "iron" = 10)
+ grind_results = list(/datum/reagent/silicon = 20, /datum/reagent/iron = 10)
point_value = 4
/obj/item/stack/sheet/rglass/attackby(obj/item/W, mob/user, params)
@@ -177,11 +179,11 @@ GLOBAL_LIST_INIT(prglass_recipes, list ( \
singular_name = "reinforced plasma glass sheet"
icon_state = "sheet-prglass"
item_state = "sheet-prglass"
- materials = list(MAT_PLASMA=MINERAL_MATERIAL_AMOUNT * 0.5, MAT_GLASS=MINERAL_MATERIAL_AMOUNT, MAT_METAL = MINERAL_MATERIAL_AMOUNT * 0.5,)
+ materials = list(MAT_PLASMA=MINERAL_MATERIAL_AMOUNT * 0.5, MAT_GLASS=MINERAL_MATERIAL_AMOUNT, MAT_METAL=MINERAL_MATERIAL_AMOUNT * 0.5,)
armor = list("melee" = 20, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 100)
resistance_flags = ACID_PROOF
merge_type = /obj/item/stack/sheet/plasmarglass
- grind_results = list("silicon" = 20, "plasma" = 10, "iron" = 10)
+ grind_results = list(/datum/reagent/silicon = 20, /datum/reagent/toxin/plasma = 10, /datum/reagent/iron = 10)
point_value = 23
/obj/item/stack/sheet/plasmarglass/Initialize(mapload, new_amount, merge = TRUE)
@@ -243,8 +245,9 @@ GLOBAL_LIST_INIT(plastitaniumglass_recipes, list(
resistance_flags = ACID_PROOF
armor = list("melee" = 100, "bullet" = 0, "laser" = 0, "energy" = 100, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 100)
max_integrity = 40
- var/cooldown = 0
sharpness = IS_SHARP
+ var/icon_prefix
+
/obj/item/shard/suicide_act(mob/user)
user.visible_message("[user] is slitting [user.p_their()] [pick("wrists", "throat")] with the shard of glass! It looks like [user.p_theyre()] trying to commit suicide.")
@@ -266,9 +269,19 @@ GLOBAL_LIST_INIT(plastitaniumglass_recipes, list(
if("large")
pixel_x = rand(-5, 5)
pixel_y = rand(-5, 5)
- var/matrix/M = matrix(transform)
- M.Turn(rand(-170, 170))
- transform = M
+ if (icon_prefix)
+ icon_state = "[icon_prefix][icon_state]"
+
+ var/turf/T = get_turf(src)
+ if(T && is_station_level(T.z))
+ SSblackbox.record_feedback("tally", "station_mess_created", 1, name)
+
+/obj/item/shard/Destroy()
+ . = ..()
+
+ var/turf/T = get_turf(src)
+ if(T && is_station_level(T.z))
+ SSblackbox.record_feedback("tally", "station_mess_destroyed", 1, name)
/obj/item/shard/afterattack(atom/A as mob|obj, mob/user, proximity)
. = ..()
@@ -298,6 +311,7 @@ GLOBAL_LIST_INIT(plastitaniumglass_recipes, list(
return ..()
/obj/item/shard/welder_act(mob/living/user, obj/item/I)
+ ..()
if(I.use_tool(src, user, 0, volume=50))
var/obj/item/stack/sheet/glass/NG = new (user.loc)
for(var/obj/item/stack/sheet/glass/G in user.loc)
@@ -316,4 +330,13 @@ GLOBAL_LIST_INIT(plastitaniumglass_recipes, list(
playsound(loc, 'sound/effects/glass_step.ogg', 30, 1)
else
playsound(loc, 'sound/effects/glass_step.ogg', 50, 1)
- . = ..()
+ return ..()
+
+/obj/item/shard/plasma
+ name = "purple shard"
+ desc = "A nasty looking shard of plasma glass."
+ force = 6
+ throwforce = 11
+ icon_state = "plasmalarge"
+ materials = list(MAT_PLASMA=MINERAL_MATERIAL_AMOUNT * 0.5, MAT_GLASS=MINERAL_MATERIAL_AMOUNT)
+ icon_prefix = "plasma"
\ No newline at end of file
diff --git a/code/game/objects/items/stacks/sheets/leather.dm b/code/game/objects/items/stacks/sheets/leather.dm
index f9d4878fab..01351ab2e2 100644
--- a/code/game/objects/items/stacks/sheets/leather.dm
+++ b/code/game/objects/items/stacks/sheets/leather.dm
@@ -36,6 +36,7 @@ GLOBAL_LIST_INIT(human_recipes, list( \
GLOBAL_LIST_INIT(gondola_recipes, list ( \
new/datum/stack_recipe("gondola mask", /obj/item/clothing/mask/gondola, 1), \
new/datum/stack_recipe("gondola suit", /obj/item/clothing/under/gondola, 2), \
+ new/datum/stack_recipe("gondola bedsheet", /obj/item/bedsheet/gondola, 1), \
))
/obj/item/stack/sheet/animalhide/gondola
diff --git a/code/game/objects/items/stacks/sheets/mineral.dm b/code/game/objects/items/stacks/sheets/mineral.dm
index 7df165461b..46c0d47388 100644
--- a/code/game/objects/items/stacks/sheets/mineral.dm
+++ b/code/game/objects/items/stacks/sheets/mineral.dm
@@ -232,6 +232,7 @@ GLOBAL_LIST_INIT(gold_recipes, list ( \
grind_results = list("silver" = 20)
point_value = 20
merge_type = /obj/item/stack/sheet/mineral/silver
+ tableVariant = /obj/structure/table/optable
GLOBAL_LIST_INIT(silver_recipes, list ( \
new/datum/stack_recipe("silver door", /obj/structure/mineral_door/silver, 10, one_per_turf = 1, on_floor = 1), \
diff --git a/code/game/objects/items/stacks/sheets/sheet_types.dm b/code/game/objects/items/stacks/sheets/sheet_types.dm
index b17bc87ffd..c085306892 100644
--- a/code/game/objects/items/stacks/sheets/sheet_types.dm
+++ b/code/game/objects/items/stacks/sheets/sheet_types.dm
@@ -107,6 +107,7 @@ GLOBAL_LIST_INIT(metal_recipes, list ( \
merge_type = /obj/item/stack/sheet/metal
grind_results = list("iron" = 20)
point_value = 2
+ tableVariant = /obj/structure/table
/obj/item/stack/sheet/metal/ratvar_act()
new /obj/item/stack/tile/brass(loc, amount)
@@ -168,6 +169,7 @@ GLOBAL_LIST_INIT(plasteel_recipes, list ( \
merge_type = /obj/item/stack/sheet/plasteel
grind_results = list("iron" = 20, "plasma" = 20)
point_value = 23
+ tableVariant = /obj/structure/table/reinforced
/obj/item/stack/sheet/plasteel/Initialize(mapload, new_amount, merge = TRUE)
recipes = GLOB.plasteel_recipes
@@ -201,6 +203,7 @@ GLOBAL_LIST_INIT(wood_recipes, list ( \
new/datum/stack_recipe("dresser", /obj/structure/dresser, 10, time = 15, one_per_turf = TRUE, on_floor = TRUE), \
new/datum/stack_recipe("picture frame", /obj/item/wallframe/picture, 1, time = 10),\
new/datum/stack_recipe("display case chassis", /obj/structure/displaycase_chassis, 5, one_per_turf = TRUE, on_floor = TRUE), \
+ new/datum/stack_recipe("loom", /obj/structure/loom, 10, time = 15, one_per_turf = TRUE, on_floor = TRUE), \
new/datum/stack_recipe("wooden buckler", /obj/item/shield/riot/buckler, 20, time = 40), \
new/datum/stack_recipe("apiary", /obj/structure/beebox, 40, time = 50),\
new/datum/stack_recipe("tiki mask", /obj/item/clothing/mask/gas/tiki_mask, 2), \
@@ -248,7 +251,8 @@ GLOBAL_LIST_INIT(cloth_recipes, list ( \
new/datum/stack_recipe("bio bag", /obj/item/storage/bag/bio, 4), \
null, \
new/datum/stack_recipe("improvised gauze", /obj/item/stack/medical/gauze/improvised, 1, 2, 6), \
- new/datum/stack_recipe("rag", /obj/item/reagent_containers/glass/rag, 1), \
+ new/datum/stack_recipe("rag", /obj/item/reagent_containers/rag, 1), \
+ new/datum/stack_recipe("towel", /obj/item/reagent_containers/rag/towel, 3), \
new/datum/stack_recipe("bedsheet", /obj/item/bedsheet, 3), \
new/datum/stack_recipe("empty sandbag", /obj/item/emptysandbag, 4), \
null, \
@@ -277,6 +281,31 @@ GLOBAL_LIST_INIT(cloth_recipes, list ( \
/obj/item/stack/sheet/cloth/ten
amount = 10
+//Durathread fuck slash-asterisk comments
+ GLOBAL_LIST_INIT(durathread_recipes, list ( \
+ new/datum/stack_recipe("durathread jumpsuit", /obj/item/clothing/under/durathread, 4, time = 40),
+ new/datum/stack_recipe("durathread beret", /obj/item/clothing/head/beret/durathread, 2, time = 40), \
+ new/datum/stack_recipe("durathread beanie", /obj/item/clothing/head/beanie/durathread, 2, time = 40), \
+ new/datum/stack_recipe("durathread bandana", /obj/item/clothing/mask/bandana/durathread, 1, time = 25), \
+ ))
+
+/obj/item/stack/sheet/durathread
+ name = "durathread"
+ desc = "A fabric sown from incredibly durable threads, known for its usefulness in armor production."
+ singular_name = "durathread roll"
+ icon_state = "sheet-durathread"
+ item_state = "sheet-cloth"
+ resistance_flags = FLAMMABLE
+ force = 0
+ throwforce = 0
+ merge_type = /obj/item/stack/sheet/durathread
+
+/obj/item/stack/sheet/durathread/Initialize(mapload, new_amount, merge = TRUE)
+ recipes = GLOB.durathread_recipes
+ return ..()
+
+
+
/*
* Cardboard
*/
@@ -414,6 +443,8 @@ GLOBAL_LIST_INIT(brass_recipes, list ( \
new/datum/stack_recipe("brass window - directional", /obj/structure/window/reinforced/clockwork/unanchored, time = 0, on_floor = TRUE, window_checks = TRUE), \
new/datum/stack_recipe("brass window - fulltile", /obj/structure/window/reinforced/clockwork/fulltile/unanchored, 2, time = 0, on_floor = TRUE, window_checks = TRUE), \
new/datum/stack_recipe("brass chair", /obj/structure/chair/brass, 1, time = 0, one_per_turf = TRUE, on_floor = TRUE), \
+ new/datum/stack_recipe("brass bar stool", /obj/structure/chair/stool/bar/brass, 1, time = 0, one_per_turf = TRUE, on_floor = TRUE), \
+ new/datum/stack_recipe("brass stool", /obj/structure/chair/stool/brass, 1, time = 0, one_per_turf = TRUE, on_floor = TRUE), \
new/datum/stack_recipe("brass table frame", /obj/structure/table_frame/brass, 1, time = 5, one_per_turf = TRUE, on_floor = TRUE), \
null,
new/datum/stack_recipe("sender - pressure sensor", /obj/structure/destructible/clockwork/trap/trigger/pressure_sensor, 2, time = 20, one_per_turf = TRUE, on_floor = TRUE), \
@@ -424,6 +455,8 @@ GLOBAL_LIST_INIT(brass_recipes, list ( \
new/datum/stack_recipe("receiver - brass skewer", /obj/structure/destructible/clockwork/trap/brass_skewer, 2, time = 20, one_per_turf = TRUE, on_floor = TRUE, placement_checks = STACK_CHECK_ADJACENT), \
new/datum/stack_recipe("receiver - steam vent", /obj/structure/destructible/clockwork/trap/steam_vent, 3, time = 30, one_per_turf = TRUE, on_floor = TRUE, placement_checks = STACK_CHECK_CARDINALS), \
new/datum/stack_recipe("receiver - power nullifier", /obj/structure/destructible/clockwork/trap/power_nullifier, 5, time = 20, one_per_turf = TRUE, on_floor = TRUE, placement_checks = STACK_CHECK_CARDINALS), \
+ null,
+ new/datum/stack_recipe("brass flask", /obj/item/reagent_containers/food/drinks/bottle/holyoil/empty), \
))
@@ -441,8 +474,9 @@ GLOBAL_LIST_INIT(brass_recipes, list ( \
throw_range = 3
turf_type = /turf/open/floor/clockwork
novariants = FALSE
- grind_results = list("iron" = 5, "teslium" = 15)
+ grind_results = list("iron" = 5, "teslium" = 15, "holyoil" = 1)
merge_type = /obj/item/stack/tile/brass
+ tableVariant = /obj/structure/table/reinforced/brass
/obj/item/stack/tile/brass/narsie_act()
new /obj/item/stack/sheet/runed_metal(loc, amount)
@@ -475,6 +509,8 @@ GLOBAL_LIST_INIT(bronze_recipes, list ( \
new/datum/stack_recipe("bronze boots", /obj/item/clothing/shoes/bronze), \
null,
new/datum/stack_recipe("bronze chair", /obj/structure/chair/bronze, 1, time = 0, one_per_turf = TRUE, on_floor = TRUE), \
+ new/datum/stack_recipe("bronze bar stool", /obj/structure/chair/stool/bar/bronze, 1, time = 0, one_per_turf = TRUE, on_floor = TRUE), \
+ new/datum/stack_recipe("bronze stool", /obj/structure/chair/stool/bronze, 1, time = 0, one_per_turf = TRUE, on_floor = TRUE), \
))
/obj/item/stack/tile/bronze
@@ -493,6 +529,7 @@ GLOBAL_LIST_INIT(bronze_recipes, list ( \
novariants = FALSE
grind_results = list("iron" = 5, "copper" = 3) //we have no "tin" reagent so this is the closest thing
merge_type = /obj/item/stack/tile/bronze
+ tableVariant = /obj/structure/table/bronze
/obj/item/stack/tile/bronze/attack_self(mob/living/user)
if(is_servant_of_ratvar(user)) //still lets them build with it, just gives a message
@@ -596,3 +633,29 @@ new /datum/stack_recipe("paper frame door", /obj/structure/mineral_door/paperfra
amount = 20
/obj/item/stack/sheet/paperframes/fifty
amount = 50
+
+
+//durathread and cotton raw
+/obj/item/stack/sheet/cotton
+ name = "raw cotton bundle"
+ desc = "A bundle of raw cotton ready to be spun on the loom."
+ singular_name = "raw cotton ball"
+ icon_state = "sheet-cotton"
+ is_fabric = TRUE
+ resistance_flags = FLAMMABLE
+ force = 0
+ throwforce = 0
+ merge_type = /obj/item/stack/sheet/cotton
+ pull_effort = 30
+ loom_result = /obj/item/stack/sheet/cloth
+
+/obj/item/stack/sheet/cotton/durathread
+ name = "raw durathread bundle"
+ desc = "A bundle of raw durathread ready to be spun on the loom."
+ singular_name = "raw durathread ball"
+ icon_state = "sheet-durathreadraw"
+ merge_type = /obj/item/stack/sheet/cotton/durathread
+ pull_effort = 70
+ loom_result = /obj/item/stack/sheet/durathread
+
+
diff --git a/code/game/objects/items/stacks/sheets/sheets.dm b/code/game/objects/items/stacks/sheets/sheets.dm
index 21b43eba20..908fc88383 100644
--- a/code/game/objects/items/stacks/sheets/sheets.dm
+++ b/code/game/objects/items/stacks/sheets/sheets.dm
@@ -12,4 +12,7 @@
novariants = FALSE
var/perunit = MINERAL_MATERIAL_AMOUNT
var/sheettype = null //this is used for girders in the creation of walls/false walls
- var/point_value = 0 //turn-in value for the gulag stacker - loosely relative to its rarity.
\ No newline at end of file
+ var/point_value = 0 //turn-in value for the gulag stacker - loosely relative to its rarity
+ var/is_fabric = FALSE //is this a valid material for the loom?
+ var/loom_result //result from pulling on the loom
+ var/pull_effort = 0 //amount of delay when pulling on the loom
\ No newline at end of file
diff --git a/code/game/objects/items/stacks/stack.dm b/code/game/objects/items/stacks/stack.dm
index 4217a58f81..155d1f1643 100644
--- a/code/game/objects/items/stacks/stack.dm
+++ b/code/game/objects/items/stacks/stack.dm
@@ -22,6 +22,7 @@
var/full_w_class = WEIGHT_CLASS_NORMAL //The weight class the stack should have at amount > 2/3rds max_amount
var/novariants = TRUE //Determines whether the item should update it's sprites based on amount.
//NOTE: When adding grind_results, the amounts should be for an INDIVIDUAL ITEM - these amounts will be multiplied by the stack size in on_grind()
+ var/obj/structure/table/tableVariant // we tables now (stores table variant to be built from this stack)
/obj/item/stack/on_grind()
for(var/i in 1 to grind_results.len) //This should only call if it's ground, so no need to check if grind_results exists
diff --git a/code/game/objects/items/stacks/telecrystal.dm b/code/game/objects/items/stacks/telecrystal.dm
index c1fb396529..7c34ae87bf 100644
--- a/code/game/objects/items/stacks/telecrystal.dm
+++ b/code/game/objects/items/stacks/telecrystal.dm
@@ -9,9 +9,10 @@
item_flags = NOBLUDGEON
/obj/item/stack/telecrystal/attack(mob/target, mob/user)
- if(target == user) //You can't go around smacking people with crystals to find out if they have an uplink or not.
- for(var/obj/item/implant/uplink/I in target)
- if(I && I.imp_in)
+ if(target == user && isliving(user)) //You can't go around smacking people with crystals to find out if they have an uplink or not.
+ var/mob/living/L = user
+ for(var/obj/item/implant/uplink/I in L.implants)
+ if(I?.imp_in)
GET_COMPONENT_FROM(hidden_uplink, /datum/component/uplink, I)
if(hidden_uplink)
hidden_uplink.telecrystals += amount
diff --git a/code/game/objects/items/stacks/tiles/tile_types.dm b/code/game/objects/items/stacks/tiles/tile_types.dm
index 3b28c30402..847e1b521e 100644
--- a/code/game/objects/items/stacks/tiles/tile_types.dm
+++ b/code/game/objects/items/stacks/tiles/tile_types.dm
@@ -99,33 +99,117 @@
icon_state = "tile-carpet"
turf_type = /turf/open/floor/carpet
resistance_flags = FLAMMABLE
-
-/obj/item/stack/tile/carpet/fifty
- amount = 50
+ tableVariant = /obj/structure/table/wood/fancy
/obj/item/stack/tile/carpet/black
name = "black carpet"
icon_state = "tile-carpet-black"
turf_type = /turf/open/floor/carpet/black
+ tableVariant = /obj/structure/table/wood/fancy/black
+
+/obj/item/stack/tile/carpet/blackred
+ name = "red carpet"
+ icon_state = "tile-carpet-blackred"
+ turf_type = /turf/open/floor/carpet/blackred
+ tableVariant = /obj/structure/table/wood/fancy/blackred
+
+/obj/item/stack/tile/carpet/monochrome
+ name = "monochrome carpet"
+ icon_state = "tile-carpet-monochrome"
+ turf_type = /turf/open/floor/carpet/monochrome
+ tableVariant = /obj/structure/table/wood/fancy/monochrome
+
+/obj/item/stack/tile/carpet/blue
+ name = "blue carpet"
+ icon_state = "tile-carpet-blue"
+ item_state = "tile-carpet-blue"
+ turf_type = /turf/open/floor/carpet/blue
+ tableVariant = /obj/structure/table/wood/fancy/blue
+
+/obj/item/stack/tile/carpet/cyan
+ name = "cyan carpet"
+ icon_state = "tile-carpet-cyan"
+ item_state = "tile-carpet-cyan"
+ turf_type = /turf/open/floor/carpet/cyan
+ tableVariant = /obj/structure/table/wood/fancy/cyan
+
+/obj/item/stack/tile/carpet/green
+ name = "green carpet"
+ icon_state = "tile-carpet-green"
+ item_state = "tile-carpet-green"
+ turf_type = /turf/open/floor/carpet/green
+ tableVariant = /obj/structure/table/wood/fancy/green
+
+/obj/item/stack/tile/carpet/orange
+ name = "orange carpet"
+ icon_state = "tile-carpet-orange"
+ item_state = "tile-carpet-orange"
+ turf_type = /turf/open/floor/carpet/orange
+ tableVariant = /obj/structure/table/wood/fancy/orange
+
+/obj/item/stack/tile/carpet/purple
+ name = "purple carpet"
+ icon_state = "tile-carpet-purple"
+ item_state = "tile-carpet-purple"
+ turf_type = /turf/open/floor/carpet/purple
+ tableVariant = /obj/structure/table/wood/fancy/purple
+
+/obj/item/stack/tile/carpet/red
+ name = "red carpet"
+ icon_state = "tile-carpet-red"
+ item_state = "tile-carpet-red"
+ turf_type = /turf/open/floor/carpet/red
+ tableVariant = /obj/structure/table/wood/fancy/red
+
+/obj/item/stack/tile/carpet/royalblack
+ name = "royal black carpet"
+ icon_state = "tile-carpet-royalblack"
+ item_state = "tile-carpet-royalblack"
+ turf_type = /turf/open/floor/carpet/royalblack
+ tableVariant = /obj/structure/table/wood/fancy/royalblack
+
+/obj/item/stack/tile/carpet/royalblue
+ name = "royal blue carpet"
+ icon_state = "tile-carpet-royalblue"
+ item_state = "tile-carpet-royalblue"
+ turf_type = /turf/open/floor/carpet/royalblue
+ tableVariant = /obj/structure/table/wood/fancy/royalblue
+
+/obj/item/stack/tile/carpet/fifty
+ amount = 50
/obj/item/stack/tile/carpet/black/fifty
amount = 50
-/obj/item/stack/tile/carpet/blackred
- name = "red carpet"
- icon_state = "tile-carpet-blackred"
- turf_type = /turf/open/floor/carpet/blackred
-
/obj/item/stack/tile/carpet/blackred/fifty
- amount = 50
-
-/obj/item/stack/tile/carpet/monochrome
- name = "monochrome carpet"
- icon_state = "tile-carpet-monochrome"
- turf_type = /turf/open/floor/carpet/monochrome
+ amount = 50
/obj/item/stack/tile/carpet/monochrome/fifty
- amount = 50
+ amount = 50
+
+/obj/item/stack/tile/carpet/blue/fifty
+ amount = 50
+
+/obj/item/stack/tile/carpet/cyan/fifty
+ amount = 50
+
+/obj/item/stack/tile/carpet/green/fifty
+ amount = 50
+
+/obj/item/stack/tile/carpet/orange/fifty
+ amount = 50
+
+/obj/item/stack/tile/carpet/purple/fifty
+ amount = 50
+
+/obj/item/stack/tile/carpet/red/fifty
+ amount = 50
+
+/obj/item/stack/tile/carpet/royalblack/fifty
+ amount = 50
+
+/obj/item/stack/tile/carpet/royalblue/fifty
+ amount = 50
/obj/item/stack/tile/fakespace
name = "astral carpet"
diff --git a/code/game/objects/items/storage/backpack.dm b/code/game/objects/items/storage/backpack.dm
index eb71311c96..ad8c4306a5 100644
--- a/code/game/objects/items/storage/backpack.dm
+++ b/code/game/objects/items/storage/backpack.dm
@@ -46,6 +46,7 @@
item_flags = NO_MAT_REDEMPTION
armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 60, "acid" = 50)
component_type = /datum/component/storage/concrete/bluespace/bag_of_holding
+ rad_flags = RAD_PROTECT_CONTENTS | RAD_NO_CONTAMINATE
/obj/item/storage/backpack/holding/satchel
name = "satchel of holding"
@@ -53,6 +54,7 @@
icon_state = "holdingsat"
item_state = "holdingsat"
species_exception = list(/datum/species/angel)
+ rad_flags = RAD_PROTECT_CONTENTS | RAD_NO_CONTAMINATE
/obj/item/storage/backpack/holding/ComponentInitialize()
. = ..()
@@ -81,6 +83,7 @@
icon_state = "giftbag0"
item_state = "giftbag"
w_class = WEIGHT_CLASS_BULKY
+ rad_flags = RAD_PROTECT_CONTENTS | RAD_NO_CONTAMINATE
/obj/item/storage/backpack/santabag/ComponentInitialize()
. = ..()
@@ -133,6 +136,8 @@
desc = "It's a special backpack made exclusively for Nanotrasen officers."
icon_state = "captainpack"
item_state = "captainpack"
+ resistance_flags = FIRE_PROOF
+ rad_flags = RAD_PROTECT_CONTENTS | RAD_NO_CONTAMINATE
/obj/item/storage/backpack/industrial
name = "industrial backpack"
@@ -140,6 +145,7 @@
icon_state = "engiepack"
item_state = "engiepack"
resistance_flags = FIRE_PROOF
+ rad_flags = RAD_PROTECT_CONTENTS | RAD_NO_CONTAMINATE
/obj/item/storage/backpack/botany
name = "botany backpack"
@@ -194,6 +200,8 @@
desc = "A tough satchel with extra pockets."
icon_state = "satchel-eng"
item_state = "engiepack"
+ resistance_flags = FIRE_PROOF
+ rad_flags = RAD_PROTECT_CONTENTS | RAD_NO_CONTAMINATE
/obj/item/storage/backpack/satchel/med
name = "medical satchel"
@@ -248,18 +256,21 @@
desc = "A bone satchel fashend with watcher wings and large bones from goliath. Can be worn on the belt."
icon = 'icons/obj/mining.dmi'
icon_state = "goliath_saddle"
- slot_flags = ITEM_SLOT_BACK | ITEM_SLOT_BELT
+ slot_flags = ITEM_SLOT_BACK
/obj/item/storage/backpack/satchel/bone/ComponentInitialize()
. = ..()
GET_COMPONENT(STR, /datum/component/storage)
- STR.max_combined_w_class = 10
+ STR.max_combined_w_class = 20
+ STR.max_items = 15
/obj/item/storage/backpack/satchel/cap
name = "captain's satchel"
desc = "An exclusive satchel for Nanotrasen officers."
icon_state = "satchel-cap"
item_state = "captainpack"
+ resistance_flags = FIRE_PROOF
+ rad_flags = RAD_PROTECT_CONTENTS | RAD_NO_CONTAMINATE
/obj/item/storage/backpack/satchel/flat
name = "smuggler's satchel"
@@ -355,6 +366,7 @@
new /obj/item/cautery(src)
new /obj/item/surgical_drapes(src)
new /obj/item/clothing/mask/surgical(src)
+ new /obj/item/reagent_containers/medspray/sterilizine(src)
new /obj/item/razor(src)
/obj/item/storage/backpack/duffelbag/sec
@@ -376,12 +388,23 @@
new /obj/item/cautery(src)
new /obj/item/surgical_drapes(src)
new /obj/item/clothing/mask/surgical(src)
+ new /obj/item/reagent_containers/medspray/sterilizine(src)
/obj/item/storage/backpack/duffelbag/engineering
name = "industrial duffel bag"
desc = "A large duffel bag for holding extra tools and supplies."
icon_state = "duffel-eng"
item_state = "duffel-eng"
+ resistance_flags = FIRE_PROOF
+ rad_flags = RAD_PROTECT_CONTENTS | RAD_NO_CONTAMINATE
+
+/obj/item/storage/backpack/duffelbag/durathread
+ name = "durathread duffel bag"
+ desc = "A lightweight duffel bag made out of durathread."
+ icon_state = "duffel-durathread"
+ item_state = "duffel-durathread"
+ resistance_flags = FIRE_PROOF
+ slowdown = 0
/obj/item/storage/backpack/duffelbag/drone
name = "drone duffel bag"
@@ -389,6 +412,7 @@
icon_state = "duffel-drone"
item_state = "duffel-drone"
resistance_flags = FIRE_PROOF
+ rad_flags = RAD_PROTECT_CONTENTS | RAD_NO_CONTAMINATE
/obj/item/storage/backpack/duffelbag/drone/PopulateContents()
new /obj/item/screwdriver(src)
@@ -398,6 +422,7 @@
new /obj/item/stack/cable_coil/random(src)
new /obj/item/wirecutters(src)
new /obj/item/multitool(src)
+ new /obj/item/pipe_dispenser(src)
/obj/item/storage/backpack/duffelbag/clown
name = "clown's duffel bag"
@@ -415,6 +440,7 @@
icon_state = "duffel-syndie"
item_state = "duffel-syndieammo"
slowdown = 0
+ rad_flags = RAD_PROTECT_CONTENTS | RAD_NO_CONTAMINATE
/obj/item/storage/backpack/duffelbag/syndie/ComponentInitialize()
. = ..()
@@ -460,18 +486,16 @@
new /obj/item/mmi/syndie(src)
new /obj/item/implantcase(src)
new /obj/item/implanter(src)
+ new /obj/item/reagent_containers/medspray/sterilizine(src)
/obj/item/storage/backpack/duffelbag/syndie/surgery_adv
name = "advanced surgery duffel bag"
desc = "A large duffel bag for holding surgical tools. Bears the logo of an advanced med-tech firm."
/obj/item/storage/backpack/duffelbag/syndie/surgery_adv/PopulateContents()
- new /obj/item/hemostat/adv(src)
- new /obj/item/circular_saw/adv(src)
- new /obj/item/scalpel/adv(src)
- new /obj/item/retractor/adv(src)
- new /obj/item/cautery/adv(src)
- new /obj/item/surgicaldrill/adv(src)
+ new /obj/item/scalpel/advanced(src)
+ new /obj/item/retractor/advanced(src)
+ new /obj/item/surgicaldrill/advanced(src)
new /obj/item/surgical_drapes(src)
new /obj/item/storage/firstaid/tactical(src)
new /obj/item/clothing/suit/straight_jacket(src)
@@ -479,6 +503,7 @@
new /obj/item/mmi/syndie(src)
new /obj/item/implantcase(src)
new /obj/item/implanter(src)
+ new /obj/item/reagent_containers/medspray/sterilizine(src)
/obj/item/storage/backpack/duffelbag/syndie/ammo
name = "ammunition duffel bag"
diff --git a/code/game/objects/items/storage/bags.dm b/code/game/objects/items/storage/bags.dm
index e4debeff49..0a475b2af1 100644
--- a/code/game/objects/items/storage/bags.dm
+++ b/code/game/objects/items/storage/bags.dm
@@ -81,6 +81,7 @@
desc = "The latest and greatest in custodial convenience, a trashbag that is capable of holding vast quantities of garbage."
icon_state = "bluetrashbag"
item_flags = NO_MAT_REDEMPTION
+ rad_flags = RAD_PROTECT_CONTENTS | RAD_NO_CONTAMINATE
/obj/item/storage/bag/trash/bluespace/ComponentInitialize()
. = ..()
@@ -105,6 +106,7 @@
component_type = /datum/component/storage/concrete/stack
var/spam_protection = FALSE //If this is TRUE, the holder won't receive any messages when they fail to pick up ore through crossing it
var/datum/component/mobhook
+ rad_flags = RAD_PROTECT_CONTENTS | RAD_NO_CONTAMINATE
/obj/item/storage/bag/ore/ComponentInitialize()
. = ..()
@@ -364,7 +366,7 @@
STR.max_combined_w_class = 200
STR.max_items = 50
STR.insert_preposition = "in"
- STR.can_hold = typecacheof(list(/obj/item/reagent_containers/pill, /obj/item/reagent_containers/glass/beaker, /obj/item/reagent_containers/glass/bottle))
+ STR.can_hold = typecacheof(list(/obj/item/reagent_containers/pill, /obj/item/reagent_containers/glass/beaker, /obj/item/reagent_containers/glass/bottle, /obj/item/reagent_containers/syringe/dart))
/*
* Biowaste bag (mostly for xenobiologists)
@@ -391,6 +393,7 @@
icon = 'icons/obj/chemical.dmi'
icon_state = "bspace_biobag"
desc = "A bag for the safe transportation and disposal of biowaste and other biological materials."
+ rad_flags = RAD_PROTECT_CONTENTS | RAD_NO_CONTAMINATE
/obj/item/storage/bag/bio/holding/ComponentInitialize()
. = ..()
diff --git a/code/game/objects/items/storage/belt.dm b/code/game/objects/items/storage/belt.dm
index e2d7fdf9d7..e354123ab9 100755
--- a/code/game/objects/items/storage/belt.dm
+++ b/code/game/objects/items/storage/belt.dm
@@ -10,6 +10,7 @@
attack_verb = list("whipped", "lashed", "disciplined")
max_integrity = 300
var/content_overlays = FALSE //If this is true, the belt will gain overlays based on what it's holding
+ var/worn_overlays = FALSE //worn counterpart of the above.
/obj/item/storage/belt/suicide_act(mob/living/carbon/user)
user.visible_message("[user] begins belting [user.p_them()]self with \the [src]! It looks like [user.p_theyre()] trying to commit suicide!")
@@ -23,6 +24,12 @@
add_overlay(M)
..()
+/obj/item/storage/belt/worn_overlays(isinhands, icon_file)
+ . = ..()
+ if(!isinhands && worn_overlays)
+ for(var/obj/item/I in contents)
+ . += I.get_worn_belt_overlay(icon_file)
+
/obj/item/storage/belt/Initialize()
. = ..()
update_icon()
@@ -33,6 +40,7 @@
icon_state = "utilitybelt"
item_state = "utility"
content_overlays = TRUE
+ rad_flags = RAD_PROTECT_CONTENTS | RAD_NO_CONTAMINATE //because this is easier than trying to have showers wash all contents.
/obj/item/storage/belt/utility/ComponentInitialize()
. = ..()
@@ -177,12 +185,9 @@
content_overlays = FALSE
/obj/item/storage/belt/medical/surgery_belt_adv/PopulateContents()
- new /obj/item/hemostat/adv(src)
- new /obj/item/circular_saw/adv(src)
- new /obj/item/scalpel/adv(src)
- new /obj/item/retractor/adv(src)
- new /obj/item/cautery/adv(src)
- new /obj/item/surgicaldrill/adv(src)
+ new /obj/item/scalpel/advanced(src)
+ new /obj/item/retractor/advanced(src)
+ new /obj/item/surgicaldrill/advanced(src)
new /obj/item/surgical_drapes(src)
/obj/item/storage/belt/security
@@ -337,6 +342,7 @@
desc = "A set of tactical webbing worn by Syndicate boarding parties."
icon_state = "militarywebbing"
item_state = "militarywebbing"
+ rad_flags = RAD_PROTECT_CONTENTS | RAD_NO_CONTAMINATE
/obj/item/storage/belt/military/ComponentInitialize()
. = ..()
@@ -424,6 +430,48 @@
GET_COMPONENT(STR, /datum/component/storage)
STR.max_items = 6
+/obj/item/storage/belt/durathread
+ name = "durathread toolbelt"
+ desc = "A toolbelt made out of durathread, it seems resistant enough to hold even big tools like an RCD, it also has higher capacity."
+ icon_state = "webbing-durathread"
+ item_state = "webbing-durathread"
+ resistance_flags = FIRE_PROOF
+
+/obj/item/storage/belt/durathread/ComponentInitialize()
+ . = ..()
+ var/datum/component/storage/STR = GetComponent(/datum/component/storage)
+ STR.max_items = 14
+ STR.max_combined_w_class = 32
+ STR.max_w_class = WEIGHT_CLASS_NORMAL
+ STR.can_hold = typecacheof(list(
+ /obj/item/crowbar,
+ /obj/item/screwdriver,
+ /obj/item/weldingtool,
+ /obj/item/wirecutters,
+ /obj/item/wrench,
+ /obj/item/multitool,
+ /obj/item/flashlight,
+ /obj/item/stack/cable_coil,
+ /obj/item/t_scanner,
+ /obj/item/analyzer,
+ /obj/item/geiger_counter,
+ /obj/item/extinguisher/mini,
+ /obj/item/radio,
+ /obj/item/clothing/gloves,
+ /obj/item/holosign_creator/atmos,
+ /obj/item/holosign_creator/engineering,
+ /obj/item/forcefield_projector,
+ /obj/item/assembly/signaler,
+ /obj/item/lightreplacer,
+ /obj/item/rcd_ammo,
+ /obj/item/construction/rcd,
+ /obj/item/pipe_dispenser,
+ /obj/item/stack/rods,
+ /obj/item/stack/tile/plasteel,
+ /obj/item/grenade/chem_grenade/metalfoam,
+ /obj/item/grenade/chem_grenade/smart_metal_foam
+ ))
+
/obj/item/storage/belt/grenade
name = "grenadier belt"
desc = "A belt for holding grenades."
@@ -481,6 +529,7 @@
desc = "A belt designed to hold various rods of power. A veritable fanny pack of exotic magic."
icon_state = "soulstonebelt"
item_state = "soulstonebelt"
+ rad_flags = RAD_PROTECT_CONTENTS | RAD_NO_CONTAMINATE
/obj/item/storage/belt/wands/ComponentInitialize()
. = ..()
@@ -517,12 +566,15 @@
/obj/item/grenade/chem_grenade,
/obj/item/lightreplacer,
/obj/item/flashlight,
+ /obj/item/reagent_containers/glass/beaker,
+ /obj/item/reagent_containers/glass/bottle,
/obj/item/reagent_containers/spray,
/obj/item/soap,
/obj/item/holosign_creator,
/obj/item/key/janitor,
/obj/item/clothing/gloves,
/obj/item/melee/flyswatter,
+ /obj/item/paint/paint_remover,
/obj/item/assembly/mousetrap
))
@@ -541,6 +593,22 @@
/obj/item/ammo_casing/shotgun
))
+/obj/item/storage/belt/bandolier/durathread
+ name = "durathread bandolier"
+ desc = "An double stacked bandolier made out of durathread."
+ icon_state = "bandolier-durathread"
+ item_state = "bandolier-durathread"
+ resistance_flags = FIRE_PROOF
+
+/obj/item/storage/belt/bandolier/durathread/ComponentInitialize()
+ . = ..()
+ GET_COMPONENT(STR, /datum/component/storage)
+ STR.max_items = 32
+ STR.display_numerical_stacking = TRUE
+ STR.can_hold = typecacheof(list(
+ /obj/item/ammo_casing
+ ))
+
/obj/item/storage/belt/medolier
name = "medolier"
desc = "A medical bandolier for holding smartdarts."
@@ -552,6 +620,9 @@
GET_COMPONENT(STR, /datum/component/storage)
STR.max_items = 15
STR.display_numerical_stacking = FALSE
+ STR.allow_quick_gather = TRUE
+ STR.allow_quick_empty = TRUE
+ STR.click_gather = TRUE
STR.can_hold = typecacheof(list(
/obj/item/reagent_containers/syringe/dart
))
@@ -560,6 +631,25 @@
for(var/i in 1 to 16)
new /obj/item/reagent_containers/syringe/dart/(src)
+/obj/item/storage/belt/medolier/afterattack(obj/target, mob/user , proximity)
+ if(!(istype(target, /obj/item/reagent_containers/glass/beaker)))
+ return
+ if(!proximity)
+ return
+ if(!target.reagents)
+ return
+
+ for(var/obj/item/reagent_containers/syringe/dart/D in contents)
+ if(round(target.reagents.total_volume, 1) <= 0)
+ to_chat(user, "You soak as many of the darts as you can with the contents from [target].")
+ return
+ if(D.mode == SYRINGE_INJECT)
+ continue
+
+ D.afterattack(target, user, proximity)
+
+ ..()
+
/obj/item/storage/belt/holster
name = "shoulder holster"
desc = "A holster to carry a handgun and ammo. WARNING: Badasses only."
@@ -653,6 +743,10 @@
icon_state = "sheath"
item_state = "sheath"
w_class = WEIGHT_CLASS_BULKY
+ content_overlays = TRUE
+ worn_overlays = TRUE
+ var/list/fitting_swords = list(/obj/item/melee/sabre, /obj/item/melee/baton/stunsword)
+ var/starting_sword = /obj/item/melee/sabre
/obj/item/storage/belt/sabre/ComponentInitialize()
. = ..()
@@ -660,9 +754,7 @@
STR.max_items = 1
STR.rustle_sound = FALSE
STR.max_w_class = WEIGHT_CLASS_BULKY
- STR.can_hold = typecacheof(list(
- /obj/item/melee/sabre
- ))
+ STR.can_hold = typecacheof(fitting_swords)
/obj/item/storage/belt/sabre/examine(mob/user)
..()
@@ -681,16 +773,28 @@
to_chat(user, "[src] is empty.")
/obj/item/storage/belt/sabre/update_icon()
- icon_state = "sheath"
- item_state = "sheath"
- if(contents.len)
- icon_state += "-sabre"
- item_state += "-sabre"
- if(loc && isliving(loc))
+ . = ..()
+ if(isliving(loc))
var/mob/living/L = loc
L.regenerate_icons()
- ..()
/obj/item/storage/belt/sabre/PopulateContents()
- new /obj/item/melee/sabre(src)
- update_icon()
+ new starting_sword(src)
+
+/obj/item/storage/belt/sabre/rapier
+ name = "rapier sheath"
+ desc = "A black, thin sheath that looks to house only a long thin blade. Feels like its made of metal."
+ icon_state = "rsheath"
+ item_state = "rsheath"
+ force = 5
+ throwforce = 15
+ block_chance = 30
+ w_class = WEIGHT_CLASS_BULKY
+ attack_verb = list("bashed", "slashes", "prods", "pokes")
+ fitting_swords = list(/obj/item/melee/rapier)
+ starting_sword = /obj/item/melee/rapier
+
+/obj/item/storage/belt/sabre/rapier/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK)
+ if(attack_type == PROJECTILE_ATTACK)
+ final_block_chance = 0 //To thin to block bullets
+ return ..()
diff --git a/code/game/objects/items/storage/book.dm b/code/game/objects/items/storage/book.dm
index 830c3aea96..85a06f0c1e 100644
--- a/code/game/objects/items/storage/book.dm
+++ b/code/game/objects/items/storage/book.dm
@@ -74,6 +74,7 @@ GLOBAL_LIST_INIT(bibleitemstates, list("bible", "koran", "scrapbook", "bible",
if(B.icon_state == "honk1" || B.icon_state == "honk2")
var/mob/living/carbon/human/H = usr
H.dna.add_mutation(CLOWNMUT)
+ H.dna.add_mutation(SMILE)
H.equip_to_slot_or_del(new /obj/item/clothing/mask/gas/clown_hat(H), SLOT_WEAR_MASK)
GLOB.bible_icon_state = B.icon_state
@@ -138,8 +139,8 @@ GLOBAL_LIST_INIT(bibleitemstates, list("bible", "koran", "scrapbook", "bible",
smack = 0
else if(iscarbon(M))
var/mob/living/carbon/C = M
- if(!istype(C.head, /obj/item/clothing/head))
- C.adjustBrainLoss(10, 80)
+ if(!istype(C.head, /obj/item/clothing/head))
+ C.adjustOrganLoss(ORGAN_SLOT_BRAIN, 10, 80)
to_chat(C, "You feel dumber.")
if(smack)
diff --git a/code/game/objects/items/storage/boxes.dm b/code/game/objects/items/storage/boxes.dm
index 4ff63ceeac..dd6a6b8453 100644
--- a/code/game/objects/items/storage/boxes.dm
+++ b/code/game/objects/items/storage/boxes.dm
@@ -33,6 +33,7 @@
resistance_flags = FLAMMABLE
var/foldable = /obj/item/stack/sheet/cardboard
var/illustration = "writing"
+ rad_flags = RAD_PROTECT_CONTENTS | RAD_NO_CONTAMINATE //exploits ahoy
/obj/item/storage/box/Initialize(mapload)
. = ..()
@@ -1131,6 +1132,7 @@
name = "Nanotrasen MRE Ration Kit Menu 0"
desc = "A package containing food suspended in an outdated bluespace pocket which lasts for centuries. If you're lucky you may even be able to enjoy the meal without getting food poisoning."
icon_state = "mre"
+ illustration = null
var/can_expire = TRUE
var/spawner_chance = 2
var/expiration_date
@@ -1184,7 +1186,7 @@
/obj/item/storage/box/mre/menu3
name = "\improper Nanotrasen MRE Ration Kit Menu 3"
- desc = "The holy grail of MREs. This item contains the fabled MRE pizza and a sample of coffee instant type 2. Any NT employee lucky enough to get their hands on one of these is truly blessed."
+ desc = "The holy grail of MREs. This item contains the fabled MRE pizza, spicy nachos and a sample of coffee instant type 2. Any NT employee lucky enough to get their hands on one of these is truly blessed."
icon_state = "menu3"
can_expire = FALSE //always fresh, never expired.
spawner_chance = 1
@@ -1192,7 +1194,42 @@
/obj/item/storage/box/mre/menu3/PopulateContents()
new /obj/item/reagent_containers/food/snacks/pizzaslice/pepperoni(src)
new /obj/item/reagent_containers/food/snacks/breadslice/plain(src)
- new /obj/item/reagent_containers/food/snacks/cheesewedge(src)
+ new /obj/item/reagent_containers/food/snacks/cubannachos(src)
new /obj/item/reagent_containers/food/snacks/grown/chili(src)
new /obj/item/reagent_containers/food/drinks/coffee/type2(src)
new /obj/item/tank/internals/emergency_oxygen(src)
+
+/obj/item/storage/box/mre/menu4
+ name = "\improper Nanotrasen MRE Ration Kit Menu 4"
+
+/obj/item/storage/box/mre/menu4/safe
+ spawner_chance = 0
+ desc = "A package containing food suspended in a bluespace pocket capable of lasting till the end of time."
+ can_expire = FALSE
+
+/obj/item/storage/box/mre/menu4/PopulateContents()
+ if(prob(66))
+ new /obj/item/reagent_containers/food/snacks/salad/boiledrice(src)
+ else
+ new /obj/item/reagent_containers/food/snacks/salad/ricebowl(src)
+ new /obj/item/reagent_containers/food/snacks/burger/tofu(src)
+ new /obj/item/reagent_containers/food/snacks/salad/fruit(src)
+ new /obj/item/reagent_containers/food/snacks/cracker(src)
+ new /obj/item/tank/internals/emergency_oxygen(src)
+
+//Where do I put this?
+/obj/item/secbat
+ name = "Secbat box"
+ desc = "Contained inside is a secbat for use with law enforcement."
+ icon = 'icons/obj/storage.dmi'
+ icon_state = "box"
+ item_state = "syringe_kit"
+ lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi'
+
+/obj/item/secbat/attack_self(mob/user)
+ new /mob/living/simple_animal/hostile/retaliate/bat/secbat(user.loc)
+ to_chat(user, "You open the box, releasing the secbat!")
+ var/obj/item/stack/sheet/cardboard/I = new(user.drop_location())
+ qdel(src)
+ user.put_in_hands(I)
diff --git a/code/game/objects/items/storage/briefcase.dm b/code/game/objects/items/storage/briefcase.dm
index bca13f2a45..ed547bc17d 100644
--- a/code/game/objects/items/storage/briefcase.dm
+++ b/code/game/objects/items/storage/briefcase.dm
@@ -40,9 +40,18 @@
/obj/item/storage/briefcase/lawyer/family
name = "battered briefcase"
- desc = "An old briefcase, this one has seen better days in its time. It's clear they don't make them nowadays as good as they used to. Comes with an added belt clip!"
+ icon_state = "gbriefcase"
+ lefthand_file = 'icons/mob/inhands/equipment/briefcase_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/equipment/briefcase_righthand.dmi'
+ desc = "An old briefcase with a golden trim. It's clear they don't make them as good as they used to. Comes with an added belt clip!"
slot_flags = ITEM_SLOT_BELT
+/obj/item/storage/briefcase/lawyer/family/ComponentInitialize()
+ . = ..()
+ GET_COMPONENT(STR, /datum/component/storage)
+ STR.max_w_class = WEIGHT_CLASS_NORMAL
+ STR.max_combined_w_class = 14
+
/obj/item/storage/briefcase/lawyer/family/PopulateContents()
new /obj/item/stamp/law(src)
new /obj/item/pen/fountain(src)
diff --git a/code/game/objects/items/storage/firstaid.dm b/code/game/objects/items/storage/firstaid.dm
index e9b074d40c..312ef35430 100644
--- a/code/game/objects/items/storage/firstaid.dm
+++ b/code/game/objects/items/storage/firstaid.dm
@@ -1,365 +1,391 @@
-/* First aid storage
- * Contains:
- * First Aid Kits
- * Pill Bottles
- * Dice Pack (in a pill bottle)
- */
-
-/*
- * First Aid Kits
- */
-/obj/item/storage/firstaid
- name = "first-aid kit"
- desc = "It's an emergency medical kit for those serious boo-boos."
- icon_state = "firstaid"
- lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi'
- righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi'
- throw_speed = 3
- throw_range = 7
- var/empty = FALSE
-
-/obj/item/storage/firstaid/regular
- icon_state = "firstaid"
- desc = "A first aid kit with the ability to heal common types of injuries."
-
-/obj/item/storage/firstaid/regular/suicide_act(mob/living/carbon/user)
- user.visible_message("[user] begins giving [user.p_them()]self aids with \the [src]! It looks like [user.p_theyre()] trying to commit suicide!")
- return BRUTELOSS
-
-/obj/item/storage/firstaid/regular/PopulateContents()
- if(empty)
- return
- new /obj/item/stack/medical/gauze(src)
- new /obj/item/stack/medical/bruise_pack(src)
- new /obj/item/stack/medical/bruise_pack(src)
- new /obj/item/stack/medical/ointment(src)
- new /obj/item/stack/medical/ointment(src)
- new /obj/item/reagent_containers/hypospray/medipen(src)
- new /obj/item/healthanalyzer(src)
-
-/obj/item/storage/firstaid/ancient
- icon_state = "firstaid"
- desc = "A first aid kit with the ability to heal common types of injuries."
-
-/obj/item/storage/firstaid/ancient/PopulateContents()
- if(empty)
- return
- new /obj/item/stack/medical/gauze(src)
- new /obj/item/stack/medical/bruise_pack(src)
- new /obj/item/stack/medical/bruise_pack(src)
- new /obj/item/stack/medical/bruise_pack(src)
- new /obj/item/stack/medical/ointment(src)
- new /obj/item/stack/medical/ointment(src)
- new /obj/item/stack/medical/ointment(src)
-
-/obj/item/storage/firstaid/fire
- name = "burn treatment kit"
- desc = "A specialized medical kit for when the toxins lab -spontaneously- burns down."
- icon_state = "ointment"
- item_state = "firstaid-ointment"
-
-/obj/item/storage/firstaid/fire/suicide_act(mob/living/carbon/user)
- user.visible_message("[user] begins rubbing \the [src] against [user.p_them()]self! It looks like [user.p_theyre()] trying to start a fire!")
- return FIRELOSS
-
-/obj/item/storage/firstaid/fire/Initialize(mapload)
- . = ..()
- icon_state = pick("ointment","firefirstaid")
-
-/obj/item/storage/firstaid/fire/PopulateContents()
- if(empty)
- return
- for(var/i in 1 to 3)
- new /obj/item/reagent_containers/pill/patch/silver_sulf(src)
- new /obj/item/reagent_containers/pill/oxandrolone(src)
- new /obj/item/reagent_containers/pill/oxandrolone(src)
- new /obj/item/reagent_containers/hypospray/medipen(src)
- new /obj/item/healthanalyzer(src)
-
-/obj/item/storage/firstaid/toxin
- name = "toxin treatment kit"
- desc = "Used to treat toxic blood content and radiation poisoning."
- icon_state = "antitoxin"
- item_state = "firstaid-toxin"
-
-/obj/item/storage/firstaid/toxin/suicide_act(mob/living/carbon/user)
- user.visible_message("[user] begins licking the lead paint off \the [src]! It looks like [user.p_theyre()] trying to commit suicide!")
- return TOXLOSS
-
-/obj/item/storage/firstaid/toxin/Initialize(mapload)
- . = ..()
- icon_state = pick("antitoxin","antitoxfirstaid","antitoxfirstaid2","antitoxfirstaid3")
-
-/obj/item/storage/firstaid/toxin/PopulateContents()
- if(empty)
- return
- for(var/i in 1 to 4)
- new /obj/item/reagent_containers/syringe/charcoal(src)
- for(var/i in 1 to 2)
- new /obj/item/storage/pill_bottle/charcoal(src)
- new /obj/item/healthanalyzer(src)
-
-/obj/item/storage/firstaid/radbgone
- name = "radiation treatment kit"
- desc = "Used to treat minor toxic blood content and major radiation poisoning."
- icon_state = "antitoxin"
- item_state = "firstaid-toxin"
-
-/obj/item/storage/firstaid/radbgone/suicide_act(mob/living/carbon/user)
- user.visible_message("[user] begins licking the lead paint off \the [src]! It looks like [user.p_theyre()] trying to commit suicide!")
- return TOXLOSS
-
-/obj/item/storage/firstaid/radbgone/PopulateContents()
- if(empty)
- return
- if(prob(50))
- new /obj/item/reagent_containers/pill/mutarad(src)
- if(prob(80))
- new /obj/item/reagent_containers/pill/antirad_plus(src)
- new /obj/item/reagent_containers/syringe/charcoal(src)
- new /obj/item/storage/pill_bottle/charcoal(src)
- new /obj/item/reagent_containers/pill/mutadone(src)
- new /obj/item/reagent_containers/pill/antirad(src)
- new /obj/item/reagent_containers/food/drinks/bottle/vodka(src)
- new /obj/item/healthanalyzer(src)
-
-
-/obj/item/storage/firstaid/o2
- name = "oxygen deprivation treatment kit"
- desc = "A box full of oxygen goodies."
- icon_state = "o2"
- item_state = "firstaid-o2"
-
-/obj/item/storage/firstaid/o2/suicide_act(mob/living/carbon/user)
- user.visible_message("[user] begins hitting [user.p_their()] neck with \the [src]! It looks like [user.p_theyre()] trying to commit suicide!")
- return OXYLOSS
-
-/obj/item/storage/firstaid/o2/PopulateContents()
- if(empty)
- return
- for(var/i in 1 to 4)
- new /obj/item/reagent_containers/pill/salbutamol(src)
- new /obj/item/reagent_containers/hypospray/medipen(src)
- new /obj/item/reagent_containers/hypospray/medipen(src)
- new /obj/item/healthanalyzer(src)
-
-/obj/item/storage/firstaid/brute
- name = "brute trauma treatment kit"
- desc = "A first aid kit for when you get toolboxed."
- icon_state = "brute"
- item_state = "firstaid-brute"
-
-/obj/item/storage/firstaid/brute/suicide_act(mob/living/carbon/user)
- user.visible_message("[user] begins beating [user.p_them()]self over the head with \the [src]! It looks like [user.p_theyre()] trying to commit suicide!")
- return BRUTELOSS
-
-/obj/item/storage/firstaid/brute/PopulateContents()
- if(empty)
- return
- for(var/i in 1 to 4)
- new /obj/item/reagent_containers/pill/patch/styptic(src)
- new /obj/item/stack/medical/gauze(src)
- new /obj/item/stack/medical/gauze(src)
- new /obj/item/healthanalyzer(src)
-
-/obj/item/storage/firstaid/tactical
- name = "combat medical kit"
- desc = "I hope you've got insurance."
- icon_state = "bezerk"
-
-/obj/item/storage/firstaid/tactical/ComponentInitialize()
- . = ..()
- GET_COMPONENT(STR, /datum/component/storage)
- STR.max_w_class = WEIGHT_CLASS_NORMAL
-
-/obj/item/storage/firstaid/tactical/PopulateContents()
- if(empty)
- return
- new /obj/item/stack/medical/gauze(src)
- new /obj/item/defibrillator/compact/combat/loaded(src)
- new /obj/item/reagent_containers/hypospray/combat(src)
- new /obj/item/reagent_containers/pill/patch/styptic(src)
- new /obj/item/reagent_containers/pill/patch/silver_sulf(src)
- new /obj/item/reagent_containers/syringe/lethal/choral(src)
- new /obj/item/clothing/glasses/hud/health/night(src)
-
-/*
- * Pill Bottles
- */
-
-/obj/item/storage/pill_bottle
- name = "pill bottle"
- desc = "It's an airtight container for storing medication."
- icon_state = "pill_canister"
- icon = 'icons/obj/chemical.dmi'
- item_state = "contsolid"
- lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi'
- righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi'
- w_class = WEIGHT_CLASS_SMALL
-
-/obj/item/storage/pill_bottle/ComponentInitialize()
- . = ..()
- GET_COMPONENT(STR, /datum/component/storage)
- STR.allow_quick_gather = TRUE
- STR.click_gather = TRUE
- STR.can_hold = typecacheof(list(/obj/item/reagent_containers/pill, /obj/item/dice))
-
-/obj/item/storage/pill_bottle/suicide_act(mob/user)
- user.visible_message("[user] is trying to get the cap off [src]! It looks like [user.p_theyre()] trying to commit suicide!")
- return (TOXLOSS)
-
-/obj/item/storage/pill_bottle/charcoal
- name = "bottle of charcoal pills"
- desc = "Contains pills used to counter toxins."
-
-/obj/item/storage/pill_bottle/charcoal/PopulateContents()
- for(var/i in 1 to 7)
- new /obj/item/reagent_containers/pill/charcoal(src)
-
-/obj/item/storage/pill_bottle/antirad
- name = "bottle of charcoal pills"
- desc = "Contains pills used to counter radiation poisoning."
-
-/obj/item/storage/pill_bottle/anitrad/PopulateContents()
- for(var/i in 1 to 5)
- new /obj/item/reagent_containers/pill/antirad(src)
-
-/obj/item/storage/pill_bottle/epinephrine
- name = "bottle of epinephrine pills"
- desc = "Contains pills used to stabilize patients."
-
-/obj/item/storage/pill_bottle/epinephrine/PopulateContents()
- for(var/i in 1 to 7)
- new /obj/item/reagent_containers/pill/epinephrine(src)
-
-/obj/item/storage/pill_bottle/mutadone
- name = "bottle of mutadone pills"
- desc = "Contains pills used to treat genetic abnormalities."
-
-/obj/item/storage/pill_bottle/mutadone/PopulateContents()
- for(var/i in 1 to 7)
- new /obj/item/reagent_containers/pill/mutadone(src)
-
-/obj/item/storage/pill_bottle/mannitol
- name = "bottle of mannitol pills"
- desc = "Contains pills used to treat brain damage."
-
-/obj/item/storage/pill_bottle/mannitol/PopulateContents()
- for(var/i in 1 to 7)
- new /obj/item/reagent_containers/pill/mannitol(src)
-
-/obj/item/storage/pill_bottle/stimulant
- name = "bottle of stimulant pills"
- desc = "Guaranteed to give you that extra burst of energy during a long shift!"
-
-/obj/item/storage/pill_bottle/stimulant/PopulateContents()
- for(var/i in 1 to 5)
- new /obj/item/reagent_containers/pill/stimulant(src)
-
-/obj/item/storage/pill_bottle/mining
- name = "bottle of patches"
- desc = "Contains patches used to treat brute and burn damage."
-
-/obj/item/storage/pill_bottle/mining/PopulateContents()
- new /obj/item/reagent_containers/pill/patch/silver_sulf(src)
- for(var/i in 1 to 3)
- new /obj/item/reagent_containers/pill/patch/styptic(src)
-
-/obj/item/storage/pill_bottle/zoom
- name = "suspicious pill bottle"
- desc = "The label is pretty old and almost unreadable, you recognize some chemical compounds."
-
-/obj/item/storage/pill_bottle/zoom/PopulateContents()
- for(var/i in 1 to 5)
- new /obj/item/reagent_containers/pill/zoom(src)
-
-/obj/item/storage/pill_bottle/happy
- name = "suspicious pill bottle"
- desc = "There is a smiley on the top."
-
-/obj/item/storage/pill_bottle/happy/PopulateContents()
- for(var/i in 1 to 5)
- new /obj/item/reagent_containers/pill/happy(src)
-
-/obj/item/storage/pill_bottle/lsd
- name = "suspicious pill bottle"
- desc = "There is a badly drawn thing with the shape of a mushroom."
-
-/obj/item/storage/pill_bottle/lsd/PopulateContents()
- for(var/i in 1 to 5)
- new /obj/item/reagent_containers/pill/lsd(src)
-
-/obj/item/storage/pill_bottle/aranesp
- name = "suspicious pill bottle"
- desc = "The label says 'gotta go fast'."
-
-/obj/item/storage/pill_bottle/aranesp/PopulateContents()
- for(var/i in 1 to 5)
- new /obj/item/reagent_containers/pill/aranesp(src)
-
-/obj/item/storage/pill_bottle/antirad_plus
- name = "anti radiation deluxe pill bottle"
- desc = "The label says 'Med-Co branded pills'."
-
-/obj/item/storage/pill_bottle/antirad_plus/PopulateContents()
- for(var/i in 1 to 7)
- new /obj/item/reagent_containers/pill/antirad_plus(src)
-
-/obj/item/storage/pill_bottle/mutarad
- name = "radiation treatment deluxe pill bottle"
- desc = "The label says 'Med-Co branded pills' and below that 'Contains Mutadone in each pill!`."
-
-/obj/item/storage/pill_bottle/mutarad/PopulateContents()
- for(var/i in 1 to 7)
- new /obj/item/reagent_containers/pill/mutarad(src)
-
-/obj/item/storage/pill_bottle/penis_enlargement
- name = "penis enlargement pills"
- desc = "You want penis enlargement pills?"
-
-/obj/item/storage/pill_bottle/penis_enlargement/PopulateContents()
- for(var/i in 1 to 7)
- new /obj/item/reagent_containers/pill/penis_enlargement(src)
-
-/////////////
-//Organ Box//
-/////////////
-
-/obj/item/storage/belt/organbox
- name = "Organ Storge"
- desc = "A compact box that helps hold massive amounts of implants, organs, and some tools. Has a belt clip for easy carrying"
- w_class = WEIGHT_CLASS_BULKY
- icon = 'icons/obj/mysterybox.dmi'
- icon_state = "organbox_open"
- lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi'
- righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi'
- throw_speed = 1
- throw_range = 1
-
-/obj/item/storage/belt/organbox/ComponentInitialize()
- . = ..()
- GET_COMPONENT(STR, /datum/component/storage)
- STR.max_items = 16
- STR.max_w_class = WEIGHT_CLASS_BULKY
- STR.max_combined_w_class = 20
- STR.can_hold = typecacheof(list(
- /obj/item/storage/pill_bottle,
- /obj/item/reagent_containers/hypospray,
- /obj/item/healthanalyzer,
- /obj/item/reagent_containers/syringe,
- /obj/item/clothing/glasses/hud/health,
- /obj/item/hemostat,
- /obj/item/scalpel,
- /obj/item/retractor,
- /obj/item/cautery,
- /obj/item/surgical_drapes,
- /obj/item/autosurgeon,
- /obj/item/organ,
- /obj/item/implant,
- /obj/item/implantpad,
- /obj/item/implantcase,
- /obj/item/implanter,
- /obj/item/circuitboard/computer/operating,
- /obj/item/stack/sheet/mineral/silver,
- /obj/item/organ_storage
- ))
+
+/* First aid storage
+ * Contains:
+ * First Aid Kits
+ * Pill Bottles
+ * Dice Pack (in a pill bottle)
+ */
+
+/*
+ * First Aid Kits
+ */
+/obj/item/storage/firstaid
+ name = "first-aid kit"
+ desc = "It's an emergency medical kit for those serious boo-boos."
+ icon_state = "firstaid"
+ lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi'
+ throw_speed = 3
+ throw_range = 7
+ var/empty = FALSE
+
+/obj/item/storage/firstaid/regular
+ icon_state = "firstaid"
+ desc = "A first aid kit with the ability to heal common types of injuries."
+
+/obj/item/storage/firstaid/regular/suicide_act(mob/living/carbon/user)
+ user.visible_message("[user] begins giving [user.p_them()]self aids with \the [src]! It looks like [user.p_theyre()] trying to commit suicide!")
+ return BRUTELOSS
+
+/obj/item/storage/firstaid/regular/PopulateContents()
+ if(empty)
+ return
+ new /obj/item/stack/medical/gauze(src)
+ new /obj/item/stack/medical/bruise_pack(src)
+ new /obj/item/stack/medical/bruise_pack(src)
+ new /obj/item/stack/medical/ointment(src)
+ new /obj/item/stack/medical/ointment(src)
+ new /obj/item/reagent_containers/hypospray/medipen(src)
+ new /obj/item/healthanalyzer(src)
+
+/obj/item/storage/firstaid/ancient
+ icon_state = "firstaid"
+ desc = "A first aid kit with the ability to heal common types of injuries."
+
+/obj/item/storage/firstaid/ancient/PopulateContents()
+ if(empty)
+ return
+ new /obj/item/stack/medical/gauze(src)
+ new /obj/item/stack/medical/bruise_pack(src)
+ new /obj/item/stack/medical/bruise_pack(src)
+ new /obj/item/stack/medical/bruise_pack(src)
+ new /obj/item/stack/medical/ointment(src)
+ new /obj/item/stack/medical/ointment(src)
+ new /obj/item/stack/medical/ointment(src)
+
+/obj/item/storage/firstaid/fire
+ name = "burn treatment kit"
+ desc = "A specialized medical kit for when the toxins lab -spontaneously- burns down."
+ icon_state = "ointment"
+ item_state = "firstaid-ointment"
+
+/obj/item/storage/firstaid/fire/suicide_act(mob/living/carbon/user)
+ user.visible_message("[user] begins rubbing \the [src] against [user.p_them()]self! It looks like [user.p_theyre()] trying to start a fire!")
+ return FIRELOSS
+
+/obj/item/storage/firstaid/fire/Initialize(mapload)
+ . = ..()
+ icon_state = pick("ointment","firefirstaid")
+
+/obj/item/storage/firstaid/fire/PopulateContents()
+ if(empty)
+ return
+ for(var/i in 1 to 3)
+ new /obj/item/reagent_containers/pill/patch/silver_sulf(src)
+ new /obj/item/reagent_containers/pill/oxandrolone(src)
+ new /obj/item/reagent_containers/pill/oxandrolone(src)
+ new /obj/item/reagent_containers/hypospray/medipen(src)
+ new /obj/item/healthanalyzer(src)
+
+/obj/item/storage/firstaid/toxin
+ name = "toxin treatment kit"
+ desc = "Used to treat toxic blood content and radiation poisoning."
+ icon_state = "antitoxin"
+ item_state = "firstaid-toxin"
+
+/obj/item/storage/firstaid/toxin/suicide_act(mob/living/carbon/user)
+ user.visible_message("[user] begins licking the lead paint off \the [src]! It looks like [user.p_theyre()] trying to commit suicide!")
+ return TOXLOSS
+
+/obj/item/storage/firstaid/toxin/Initialize(mapload)
+ . = ..()
+ icon_state = pick("antitoxin","antitoxfirstaid","antitoxfirstaid2","antitoxfirstaid3")
+
+/obj/item/storage/firstaid/toxin/PopulateContents()
+ if(empty)
+ return
+ for(var/i in 1 to 4)
+ new /obj/item/reagent_containers/syringe/charcoal(src)
+ for(var/i in 1 to 2)
+ new /obj/item/storage/pill_bottle/charcoal(src)
+ new /obj/item/healthanalyzer(src)
+
+/obj/item/storage/firstaid/radbgone
+ name = "radiation treatment kit"
+ desc = "Used to treat minor toxic blood content and major radiation poisoning."
+ icon_state = "antitoxin"
+ item_state = "firstaid-toxin"
+
+/obj/item/storage/firstaid/radbgone/suicide_act(mob/living/carbon/user)
+ user.visible_message("[user] begins licking the lead paint off \the [src]! It looks like [user.p_theyre()] trying to commit suicide!")
+ return TOXLOSS
+
+/obj/item/storage/firstaid/radbgone/PopulateContents()
+ if(empty)
+ return
+ if(prob(50))
+ new /obj/item/reagent_containers/pill/mutarad(src)
+ if(prob(80))
+ new /obj/item/reagent_containers/pill/antirad_plus(src)
+ new /obj/item/reagent_containers/syringe/charcoal(src)
+ new /obj/item/storage/pill_bottle/charcoal(src)
+ new /obj/item/reagent_containers/pill/mutadone(src)
+ new /obj/item/reagent_containers/pill/antirad(src)
+ new /obj/item/reagent_containers/food/drinks/bottle/vodka(src)
+ new /obj/item/healthanalyzer(src)
+
+
+/obj/item/storage/firstaid/o2
+ name = "oxygen deprivation treatment kit"
+ desc = "A box full of oxygen goodies."
+ icon_state = "o2"
+ item_state = "firstaid-o2"
+
+/obj/item/storage/firstaid/o2/suicide_act(mob/living/carbon/user)
+ user.visible_message("[user] begins hitting [user.p_their()] neck with \the [src]! It looks like [user.p_theyre()] trying to commit suicide!")
+ return OXYLOSS
+
+/obj/item/storage/firstaid/o2/PopulateContents()
+ if(empty)
+ return
+ for(var/i in 1 to 4)
+ new /obj/item/reagent_containers/pill/salbutamol(src)
+ new /obj/item/reagent_containers/hypospray/medipen(src)
+ new /obj/item/reagent_containers/hypospray/medipen(src)
+ new /obj/item/healthanalyzer(src)
+
+/obj/item/storage/firstaid/brute
+ name = "brute trauma treatment kit"
+ desc = "A first aid kit for when you get toolboxed."
+ icon_state = "brute"
+ item_state = "firstaid-brute"
+
+/obj/item/storage/firstaid/brute/suicide_act(mob/living/carbon/user)
+ user.visible_message("[user] begins beating [user.p_them()]self over the head with \the [src]! It looks like [user.p_theyre()] trying to commit suicide!")
+ return BRUTELOSS
+
+/obj/item/storage/firstaid/brute/PopulateContents()
+ if(empty)
+ return
+ for(var/i in 1 to 4)
+ new /obj/item/reagent_containers/pill/patch/styptic(src)
+ new /obj/item/stack/medical/gauze(src)
+ new /obj/item/stack/medical/gauze(src)
+ new /obj/item/healthanalyzer(src)
+
+/obj/item/storage/firstaid/tactical
+ name = "combat medical kit"
+ desc = "I hope you've got insurance."
+ icon_state = "bezerk"
+
+/obj/item/storage/firstaid/tactical/ComponentInitialize()
+ . = ..()
+ GET_COMPONENT(STR, /datum/component/storage)
+ STR.max_w_class = WEIGHT_CLASS_NORMAL
+
+/obj/item/storage/firstaid/tactical/PopulateContents()
+ if(empty)
+ return
+ new /obj/item/stack/medical/gauze(src)
+ new /obj/item/defibrillator/compact/combat/loaded(src)
+ new /obj/item/reagent_containers/hypospray/combat(src)
+ new /obj/item/reagent_containers/pill/patch/styptic(src)
+ new /obj/item/reagent_containers/pill/patch/silver_sulf(src)
+ new /obj/item/reagent_containers/syringe/lethal/choral(src)
+ new /obj/item/clothing/glasses/hud/health/night(src)
+
+/*
+ * Pill Bottles
+ */
+
+/obj/item/storage/pill_bottle
+ name = "pill bottle"
+ desc = "It's an airtight container for storing medication."
+ icon_state = "pill_canister"
+ icon = 'icons/obj/chemical.dmi'
+ item_state = "contsolid"
+ lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi'
+ w_class = WEIGHT_CLASS_SMALL
+
+/obj/item/storage/pill_bottle/ComponentInitialize()
+ . = ..()
+ GET_COMPONENT(STR, /datum/component/storage)
+ STR.allow_quick_gather = TRUE
+ STR.click_gather = TRUE
+ STR.can_hold = typecacheof(list(/obj/item/reagent_containers/pill, /obj/item/dice))
+
+/obj/item/storage/pill_bottle/suicide_act(mob/user)
+ user.visible_message("[user] is trying to get the cap off [src]! It looks like [user.p_theyre()] trying to commit suicide!")
+ return (TOXLOSS)
+
+/obj/item/storage/pill_bottle/charcoal
+ name = "bottle of charcoal pills"
+ desc = "Contains pills used to counter toxins."
+
+/obj/item/storage/pill_bottle/charcoal/PopulateContents()
+ for(var/i in 1 to 7)
+ new /obj/item/reagent_containers/pill/charcoal(src)
+
+/obj/item/storage/pill_bottle/antirad
+ name = "bottle of charcoal pills"
+ desc = "Contains pills used to counter radiation poisoning."
+
+/obj/item/storage/pill_bottle/anitrad/PopulateContents()
+ for(var/i in 1 to 5)
+ new /obj/item/reagent_containers/pill/antirad(src)
+
+/obj/item/storage/pill_bottle/epinephrine
+ name = "bottle of epinephrine pills"
+ desc = "Contains pills used to stabilize patients."
+
+/obj/item/storage/pill_bottle/epinephrine/PopulateContents()
+ for(var/i in 1 to 7)
+ new /obj/item/reagent_containers/pill/epinephrine(src)
+
+/obj/item/storage/pill_bottle/mutadone
+ name = "bottle of mutadone pills"
+ desc = "Contains pills used to treat genetic abnormalities."
+
+/obj/item/storage/pill_bottle/mutadone/PopulateContents()
+ for(var/i in 1 to 7)
+ new /obj/item/reagent_containers/pill/mutadone(src)
+
+/obj/item/storage/pill_bottle/mannitol
+ name = "bottle of mannitol pills"
+ desc = "Contains pills used to treat brain damage."
+
+/obj/item/storage/pill_bottle/mannitol/PopulateContents()
+ for(var/i in 1 to 7)
+ new /obj/item/reagent_containers/pill/mannitol(src)
+
+/obj/item/storage/pill_bottle/stimulant
+ name = "bottle of stimulant pills"
+ desc = "Guaranteed to give you that extra burst of energy during a long shift!"
+
+/obj/item/storage/pill_bottle/stimulant/PopulateContents()
+ for(var/i in 1 to 5)
+ new /obj/item/reagent_containers/pill/stimulant(src)
+
+/obj/item/storage/pill_bottle/mining
+ name = "bottle of patches"
+ desc = "Contains patches used to treat brute and burn damage."
+
+/obj/item/storage/pill_bottle/mining/PopulateContents()
+ new /obj/item/reagent_containers/pill/patch/silver_sulf(src)
+ for(var/i in 1 to 3)
+ new /obj/item/reagent_containers/pill/patch/styptic(src)
+
+/obj/item/storage/pill_bottle/zoom
+ name = "suspicious pill bottle"
+ desc = "The label is pretty old and almost unreadable, you recognize some chemical compounds."
+
+/obj/item/storage/pill_bottle/zoom/PopulateContents()
+ for(var/i in 1 to 5)
+ new /obj/item/reagent_containers/pill/zoom(src)
+
+/obj/item/storage/pill_bottle/happy
+ name = "suspicious pill bottle"
+ desc = "There is a smiley on the top."
+
+/obj/item/storage/pill_bottle/happy/PopulateContents()
+ for(var/i in 1 to 5)
+ new /obj/item/reagent_containers/pill/happy(src)
+
+/obj/item/storage/pill_bottle/lsd
+ name = "suspicious pill bottle"
+ desc = "There is a badly drawn thing with the shape of a mushroom."
+
+/obj/item/storage/pill_bottle/lsd/PopulateContents()
+ for(var/i in 1 to 5)
+ new /obj/item/reagent_containers/pill/lsd(src)
+
+/obj/item/storage/pill_bottle/aranesp
+ name = "suspicious pill bottle"
+ desc = "The label says 'gotta go fast'."
+
+/obj/item/storage/pill_bottle/aranesp/PopulateContents()
+ for(var/i in 1 to 5)
+ new /obj/item/reagent_containers/pill/aranesp(src)
+
+/obj/item/storage/pill_bottle/psicodine
+ name = "bottle of psicodine pills"
+ desc = "Contains pills used to treat mental distress and traumas."
+
+/obj/item/storage/pill_bottle/psicodine/PopulateContents()
+ for(var/i in 1 to 7)
+ new /obj/item/reagent_containers/pill/psicodine(src)
+
+/obj/item/storage/pill_bottle/happiness
+ name = "happiness pill bottle"
+ desc = "The label is long gone, in its place an 'H' written with a marker."
+
+/obj/item/storage/pill_bottle/happiness/PopulateContents()
+ for(var/i in 1 to 5)
+ new /obj/item/reagent_containers/pill/happiness(src)
+
+/obj/item/storage/pill_bottle/antirad_plus
+ name = "anti radiation deluxe pill bottle"
+ desc = "The label says 'Med-Co branded pills'."
+
+/obj/item/storage/pill_bottle/antirad_plus/PopulateContents()
+ for(var/i in 1 to 7)
+ new /obj/item/reagent_containers/pill/antirad_plus(src)
+
+/obj/item/storage/pill_bottle/mutarad
+ name = "radiation treatment deluxe pill bottle"
+ desc = "The label says 'Med-Co branded pills' and below that 'Contains Mutadone in each pill!`."
+
+/obj/item/storage/pill_bottle/mutarad/PopulateContents()
+ for(var/i in 1 to 7)
+ new /obj/item/reagent_containers/pill/mutarad(src)
+
+/obj/item/storage/pill_bottle/penis_enlargement
+ name = "penis enlargement pills"
+ desc = "You want penis enlargement pills?"
+
+/obj/item/storage/pill_bottle/penis_enlargement/PopulateContents()
+ for(var/i in 1 to 7)
+ new /obj/item/reagent_containers/pill/penis_enlargement(src)
+
+/obj/item/storage/pill_bottle/breast_enlargement
+ name = "breast enlargement pills"
+ desc = "Made by Fermichem - They have a woman with breasts larger than she is on them. The warming states not to take more than 10u at a time."
+
+/obj/item/storage/pill_bottle/breast_enlargement/PopulateContents()
+ for(var/i in 1 to 7)
+ new /obj/item/reagent_containers/pill/breast_enlargement(src)
+
+/////////////
+//Organ Box//
+/////////////
+
+/obj/item/storage/belt/organbox
+ name = "Organ Storge"
+ desc = "A compact box that helps hold massive amounts of implants, organs, and some tools. Has a belt clip for easy carrying"
+ w_class = WEIGHT_CLASS_BULKY
+ icon = 'icons/obj/mysterybox.dmi'
+ icon_state = "organbox_open"
+ lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi'
+ throw_speed = 1
+ throw_range = 1
+
+/obj/item/storage/belt/organbox/ComponentInitialize()
+ . = ..()
+ GET_COMPONENT(STR, /datum/component/storage)
+ STR.max_items = 16
+ STR.max_w_class = WEIGHT_CLASS_BULKY
+ STR.max_combined_w_class = 20
+ STR.can_hold = typecacheof(list(
+ /obj/item/storage/pill_bottle,
+ /obj/item/reagent_containers/hypospray,
+ /obj/item/healthanalyzer,
+ /obj/item/reagent_containers/syringe,
+ /obj/item/clothing/glasses/hud/health,
+ /obj/item/hemostat,
+ /obj/item/scalpel,
+ /obj/item/retractor,
+ /obj/item/cautery,
+ /obj/item/surgical_drapes,
+ /obj/item/autosurgeon,
+ /obj/item/organ,
+ /obj/item/implant,
+ /obj/item/implantpad,
+ /obj/item/implantcase,
+ /obj/item/implanter,
+ /obj/item/circuitboard/computer/operating,
+ /obj/item/stack/sheet/mineral/silver,
+ /obj/item/organ_storage
+ ))
+
diff --git a/code/game/objects/items/storage/lockbox.dm b/code/game/objects/items/storage/lockbox.dm
index 4924051855..eeebc6f4c5 100644
--- a/code/game/objects/items/storage/lockbox.dm
+++ b/code/game/objects/items/storage/lockbox.dm
@@ -48,14 +48,16 @@
to_chat(user, "It's locked!")
/obj/item/storage/lockbox/emag_act(mob/user)
- if(!broken)
- broken = TRUE
- SEND_SIGNAL(src, COMSIG_TRY_STORAGE_SET_LOCKSTATE, FALSE)
- desc += "It appears to be broken."
- icon_state = src.icon_broken
- if(user)
- visible_message("\The [src] has been broken by [user] with an electromagnetic card!")
- return
+ . = ..()
+ if(broken)
+ return
+ broken = TRUE
+ SEND_SIGNAL(src, COMSIG_TRY_STORAGE_SET_LOCKSTATE, FALSE)
+ desc += "It appears to be broken."
+ icon_state = src.icon_broken
+ if(user)
+ visible_message("\The [src] has been broken by [user] with an electromagnetic card!")
+ return TRUE
/obj/item/storage/lockbox/Entered()
. = ..()
diff --git a/code/game/objects/items/storage/storage.dm b/code/game/objects/items/storage/storage.dm
index 7c2694016b..b69567a2a5 100644
--- a/code/game/objects/items/storage/storage.dm
+++ b/code/game/objects/items/storage/storage.dm
@@ -16,7 +16,7 @@
AddComponent(component_type)
/obj/item/storage/AllowDrop()
- return TRUE
+ return FALSE
/obj/item/storage/contents_explosion(severity, target)
for(var/atom/A in contents)
diff --git a/code/game/objects/items/storage/toolbox.dm b/code/game/objects/items/storage/toolbox.dm
index b2313969f0..d18212be42 100644
--- a/code/game/objects/items/storage/toolbox.dm
+++ b/code/game/objects/items/storage/toolbox.dm
@@ -1,3 +1,5 @@
+GLOBAL_LIST_EMPTY(rubber_toolbox_icons)
+
/obj/item/storage/toolbox
name = "toolbox"
desc = "Danger. Very robust."
@@ -16,21 +18,26 @@
hitsound = 'sound/weapons/smash.ogg'
var/latches = "single_latch"
var/has_latches = TRUE
+ var/can_rubberify = TRUE
+ rad_flags = RAD_PROTECT_CONTENTS | RAD_NO_CONTAMINATE //very protecc too
-/obj/item/storage/toolbox/Initialize()
+/obj/item/storage/toolbox/Initialize(mapload)
. = ..()
if(has_latches)
if(prob(10))
latches = "double_latch"
if(prob(1))
latches = "triple_latch"
+ if(mapload && can_rubberify && prob(5))
+ rubberify()
update_icon()
/obj/item/storage/toolbox/update_icon()
..()
cut_overlays()
if(has_latches)
- add_overlay(latches)
+ var/icon/I = icon('icons/obj/storage.dmi', latches)
+ add_overlay(I)
/obj/item/storage/toolbox/suicide_act(mob/user)
@@ -39,8 +46,6 @@
/obj/item/storage/toolbox/emergency
name = "emergency toolbox"
- icon_state = "red"
- item_state = "toolbox_red"
/obj/item/storage/toolbox/emergency/PopulateContents()
new /obj/item/crowbar/red(src)
@@ -59,6 +64,7 @@
name = "rusty red toolbox"
icon_state = "toolbox_red_old"
has_latches = FALSE
+ can_rubberify = FALSE
/obj/item/storage/toolbox/mechanical
name = "mechanical toolbox"
@@ -77,6 +83,7 @@
name = "rusty blue toolbox"
icon_state = "toolbox_blue_old"
has_latches = FALSE
+ can_rubberify = FALSE
/obj/item/storage/toolbox/mechanical/old/heirloom
name = "old, robust toolbox" //this will be named "X family toolbox"
@@ -151,6 +158,7 @@
resistance_flags = FIRE_PROOF | ACID_PROOF
w_class = WEIGHT_CLASS_HUGE
attack_verb = list("robusted", "crushed", "smashed")
+ can_rubberify = FALSE
var/fabricator_type = /obj/item/clockwork/replica_fabricator/scarab
/obj/item/storage/toolbox/brass/ComponentInitialize()
@@ -191,6 +199,7 @@
item_state = "toolbox_blue"
w_class = WEIGHT_CLASS_HUGE //heyo no bohing this!
force = 18 //spear damage
+ can_rubberify = FALSE
/obj/item/storage/toolbox/plastitanium/afterattack(atom/A, mob/user, proximity)
. = ..()
@@ -204,7 +213,7 @@
name = "artistic toolbox"
desc = "A toolbox painted bright green. Why anyone would store art supplies in a toolbox is beyond you, but it has plenty of extra space."
icon_state = "green"
- item_state = "artistic_toolbox"
+ item_state = "toolbox_green"
w_class = WEIGHT_CLASS_GIGANTIC //Holds more than a regular toolbox!
/obj/item/storage/toolbox/artistic/ComponentInitialize()
@@ -253,9 +262,55 @@
/obj/item/storage/toolbox/gold_fake // used in crafting
name = "golden toolbox"
- desc = "A gold plated toolbox, fancy and harmless do to the gold plating being on cardboard!"
+ desc = "A gold plated toolbox, fancy and harmless due to the gold plating being on cardboard!"
icon_state = "gold"
item_state = "gold"
has_latches = FALSE
force = 0
throwforce = 0
+ can_rubberify = FALSE
+
+/obj/item/storage/toolbox/proc/rubberify()
+ name = "rubber [name]"
+ desc = replacetext(desc, "Danger", "Bouncy")
+ desc = replacetext(desc, "robust", "safe")
+ desc = replacetext(desc, "heavier", "bouncier")
+ DISABLE_BITFIELD(flags_1, CONDUCT_1)
+ materials = null
+ damtype = STAMINA
+ force += 3 //to compensate the higher stamina K.O. threshold compared to actual health.
+ throwforce += 3
+ attack_verb += "bounced"
+ hitsound = 'sound/effects/clownstep1.ogg'
+ if(!GLOB.rubber_toolbox_icons[icon_state])
+ generate_rubber_toolbox_icon()
+ icon = GLOB.rubber_toolbox_icons[icon_state]
+ AddComponent(/datum/component/bouncy)
+ . = ..()
+
+/obj/item/storage/toolbox/proc/generate_rubber_toolbox_icon()
+ var/icon/new_icon = icon(icon, icon_state)
+ var/icon/smooth = icon('icons/obj/storage.dmi', "rubber_toolbox_blend")
+ new_icon.Blend(smooth, ICON_MULTIPLY)
+ new_icon = fcopy_rsc(new_icon)
+ GLOB.rubber_toolbox_icons[icon_state] = new_icon
+
+/obj/item/storage/toolbox/rubber
+ name = "rubber toolbox"
+ desc = "Bouncy. Very safe."
+ flags_1 = null
+ materials = null
+ damtype = STAMINA
+ force = 17
+ throwforce = 17
+ attack_verb = list("robusted", "bounced")
+ can_rubberify = FALSE //we are already the future.
+
+/obj/item/storage/toolbox/rubber/Initialize()
+ icon_state = pick("blue", "red", "yellow", "green")
+ item_state = "toolbox_[icon_state]"
+ if(!GLOB.rubber_toolbox_icons[icon_state])
+ generate_rubber_toolbox_icon()
+ icon = GLOB.rubber_toolbox_icons[icon_state]
+ . = ..()
+ AddComponent(/datum/component/bouncy)
\ No newline at end of file
diff --git a/code/game/objects/items/storage/uplink_kits.dm b/code/game/objects/items/storage/uplink_kits.dm
index a6a3cea373..cf5d685b4a 100644
--- a/code/game/objects/items/storage/uplink_kits.dm
+++ b/code/game/objects/items/storage/uplink_kits.dm
@@ -1,7 +1,7 @@
/obj/item/storage/box/syndicate
/obj/item/storage/box/syndicate/PopulateContents()
- switch (pickweight(list("bloodyspai" = 3, "stealth" = 2, "bond" = 2, "screwed" = 2, "sabotage" = 3, "guns" = 2, "murder" = 2, "implant" = 1, "hacker" = 3, "darklord" = 1, "sniper" = 1, "metaops" = 1, "ninja" = 1)))
+ switch (pickweight(list("bloodyspai" = 3, "stealth" = 2, "bond" = 2, "screwed" = 2, "sabotage" = 3, "guns" = 2, "murder" = 2, "baseball" = 1, "implant" = 1, "hacker" = 3, "darklord" = 1, "sniper" = 1, "metaops" = 1, "ninja" = 1)))
if("bloodyspai") // 30 tc now this is more right
new /obj/item/clothing/under/chameleon(src) // 2 tc since it's not the full set
new /obj/item/clothing/mask/chameleon(src) // Goes with above
@@ -52,7 +52,7 @@
new /obj/item/clothing/under/suit_jacket/really_black(src)
new /obj/item/screwdriver/power(src) //2 tc item
- if("murder") // 35 tc now
+ if("murder") // 35 tc
new /obj/item/melee/transforming/energy/sword/saber(src)
new /obj/item/clothing/glasses/thermal/syndi(src)
new /obj/item/card/emag(src)
@@ -62,6 +62,17 @@
new /obj/item/clothing/glasses/phantomthief/syndicate(src)
new /obj/item/reagent_containers/syringe/stimulants(src)
+ if("baseball") // 42~ tc
+ new /obj/item/melee/baseball_bat/ablative/syndi(src) //Lets say 12 tc, lesser sleeping carp
+ new /obj/item/clothing/glasses/sunglasses/garb(src) //Lets say 2 tc
+ new /obj/item/card/emag(src) //6 tc
+ new /obj/item/clothing/shoes/sneakers/noslip(src) //2tc
+ new /obj/item/encryptionkey/syndicate(src) //1tc
+ new /obj/item/autosurgeon/anti_drop(src) //Lets just say 7~
+ new /obj/item/clothing/under/syndicate/baseball(src) //3tc
+ new /obj/item/clothing/head/soft/baseball(src) //Lets say 4 tc
+ new /obj/item/reagent_containers/hypospray/medipen/stimulants/baseball(src) //lets say 5tc
+
if("implant") // 67+ tc holy shit what the fuck this is a lottery disguised as fun boxes isn't it?
new /obj/item/implanter/freedom(src)
new /obj/item/implanter/uplink/precharged(src)
@@ -132,7 +143,7 @@
new /obj/item/card/emag(src) // 6 tc
if("ninja") // 40~ tc worth
- new /obj/item/katana(src) // Unique , basicly a better esword. 10 tc?
+ new /obj/item/katana(src) // Unique , basicly a better esword. 10 tc?
new /obj/item/implanter/adrenalin(src) // 8 tc
new /obj/item/throwing_star(src) // ~5 tc for all 6
new /obj/item/throwing_star(src)
@@ -294,6 +305,7 @@
new /obj/item/radio/headset/chameleon(src)
new /obj/item/stamp/chameleon(src)
new /obj/item/pda/chameleon(src)
+ new /obj/item/clothing/neck/cloak/chameleon(src)
//5*(2*4) = 5*8 = 45, 45 damage if you hit one person with all 5 stars.
//Not counting the damage it will do while embedded (2*4 = 8, at 15% chance)
diff --git a/code/game/objects/items/storage/wallets.dm b/code/game/objects/items/storage/wallets.dm
index c263c2669d..cb5790e45f 100644
--- a/code/game/objects/items/storage/wallets.dm
+++ b/code/game/objects/items/storage/wallets.dm
@@ -36,7 +36,8 @@
/obj/item/reagent_containers/syringe,
/obj/item/screwdriver,
/obj/item/valentine,
- /obj/item/stamp))
+ /obj/item/stamp,
+ /obj/item/key))
/obj/item/storage/wallet/Exited(atom/movable/AM)
. = ..()
@@ -60,7 +61,7 @@
/obj/item/storage/wallet/update_icon()
var/new_state = "wallet"
if(front_id)
- new_state = "wallet_[front_id.icon_state]"
+ new_state = "wallet_id"
if(new_state != icon_state) //avoid so many icon state changes.
icon_state = new_state
diff --git a/code/game/objects/items/tanks/tanks.dm b/code/game/objects/items/tanks/tanks.dm
index 1245b7de94..d409e40575 100644
--- a/code/game/objects/items/tanks/tanks.dm
+++ b/code/game/objects/items/tanks/tanks.dm
@@ -23,7 +23,7 @@
toggle_internals(user)
/obj/item/tank/proc/toggle_internals(mob/user)
- var/mob/living/carbon/human/H = user
+ var/mob/living/carbon/H = user
if(!istype(H))
return
@@ -33,13 +33,19 @@
H.update_internals_hud_icon(0)
else
if(!H.getorganslot(ORGAN_SLOT_BREATHING_TUBE))
- if(!H.wear_mask)
- to_chat(H, "You need a mask!")
- return
- if(H.wear_mask.mask_adjusted)
- H.wear_mask.adjustmask(H)
- if(!(H.wear_mask.clothing_flags & MASKINTERNALS))
- to_chat(H, "[H.wear_mask] can't use [src]!")
+ var/obj/item/clothing/check
+ var/internals = FALSE
+
+ for(check in GET_INTERNAL_SLOTS(H))
+ if(istype(check, /obj/item/clothing/mask))
+ var/obj/item/clothing/mask/M = check
+ if(M.mask_adjusted)
+ M.adjustmask(H)
+ if(CHECK_BITFIELD(check.clothing_flags, ALLOWINTERNALS))
+ internals = TRUE
+
+ if(!internals)
+ to_chat(H, "You are not wearing an internals mask!")
return
if(H.internal)
diff --git a/code/game/objects/items/taster.dm b/code/game/objects/items/taster.dm
index 8363c63c2c..3828beb921 100644
--- a/code/game/objects/items/taster.dm
+++ b/code/game/objects/items/taster.dm
@@ -6,10 +6,9 @@
w_class = WEIGHT_CLASS_TINY
- var/taste_sensitivity = 15
+ speech_span = null
-/obj/item/taster/get_spans()
- return list()
+ var/taste_sensitivity = 15
/obj/item/taster/afterattack(atom/O, mob/user, proximity)
. = ..()
diff --git a/code/game/objects/items/teleportation.dm b/code/game/objects/items/teleportation.dm
index 1ccc88d892..e16b0dd690 100644
--- a/code/game/objects/items/teleportation.dm
+++ b/code/game/objects/items/teleportation.dm
@@ -75,15 +75,14 @@
temp += "Implant Signals: "
for (var/obj/item/implant/tracking/W in GLOB.tracked_implants)
- if (!W.imp_in || !isliving(W.loc))
+ if (!isliving(W.imp_in))
continue
- else
- var/mob/living/M = W.loc
- if (M.stat == DEAD)
- if (M.timeofdeath + 6000 < world.time)
- continue
+ var/mob/living/M = W.imp_in
+ if (M.stat == DEAD)
+ if (M.timeofdeath + 6000 < world.time)
+ continue
- var/turf/tr = get_turf(W)
+ var/turf/tr = get_turf(M)
if (tr.z == sr.z && tr)
var/direct = max(abs(tr.x - sr.x), abs(tr.y - sr.y))
if (direct < 20)
@@ -188,8 +187,13 @@
user.show_message("\The [src] is recharging!")
return
var/atom/T = L[t1]
+ var/implantcheckmate = FALSE
+ if(isliving(T))
+ var/mob/living/M = T
+ if(!locate(/obj/item/implant/tracking) in M.implants) //The user was too slow and let the target mob's tracking implant expire or get removed.
+ implantcheckmate = TRUE
var/area/A = get_area(T)
- if(A.noteleport)
+ if(A.noteleport || implantcheckmate)
to_chat(user, "\The [src] is malfunctioning.")
return
current_location = get_turf(user) //Recheck.
diff --git a/code/game/objects/items/teleprod.dm b/code/game/objects/items/teleprod.dm
index 341c85fa1c..40392c19c3 100644
--- a/code/game/objects/items/teleprod.dm
+++ b/code/game/objects/items/teleprod.dm
@@ -10,7 +10,7 @@
. = ..()
if(!. || !istype(M) || M.anchored)
return
- do_teleport(M, get_turf(M), 15)
+ do_teleport(M, get_turf(M), 15, channel = TELEPORT_CHANNEL_BLUESPACE)
/obj/item/melee/baton/cattleprod/teleprod/clowning_around(mob/living/user)
user.visible_message("[user] accidentally hits [user.p_them()]self with [src]!", \
@@ -18,7 +18,7 @@
SEND_SIGNAL(user, COMSIG_LIVING_MINOR_SHOCK)
user.Knockdown(stunforce*3)
playsound(loc, 'sound/weapons/egloves.ogg', 50, 1, -1)
- if(do_teleport(user, get_turf(user), 50))
+ if(do_teleport(user, get_turf(user), 50, channel = TELEPORT_CHANNEL_BLUESPACE))
deductcharge(hitcost)
else
deductcharge(hitcost * 0.25)
diff --git a/code/game/objects/items/theft_tools.dm b/code/game/objects/items/theft_tools.dm
index 0c74f610df..b99be7e988 100644
--- a/code/game/objects/items/theft_tools.dm
+++ b/code/game/objects/items/theft_tools.dm
@@ -232,7 +232,7 @@
. = ..()
if(!sliver)
return
- if(ismovableatom(O) && O != sliver)
+ if(proximity && ismovableatom(O) && O != sliver)
Consume(O, user)
/obj/item/hemostat/supermatter/throw_impact(atom/hit_atom) // no instakill supermatter javelins
diff --git a/code/game/objects/items/tools/crowbar.dm b/code/game/objects/items/tools/crowbar.dm
index 6cee5def2a..f891a48df6 100644
--- a/code/game/objects/items/tools/crowbar.dm
+++ b/code/game/objects/items/tools/crowbar.dm
@@ -31,9 +31,15 @@
name = "brass crowbar"
desc = "A brass crowbar. It feels faintly warm to the touch."
resistance_flags = FIRE_PROOF | ACID_PROOF
- icon_state = "crowbar_brass"
+ icon_state = "crowbar_clock"
toolspeed = 0.5
+/obj/item/crowbar/bronze
+ name = "bronze plated crowbar"
+ desc = "A bronze plated crowbar."
+ icon_state = "crowbar_brass"
+ toolspeed = 0.95
+
/obj/item/crowbar/abductor
name = "alien crowbar"
desc = "A hard-light crowbar. It appears to pry by itself, without any effort required."
@@ -42,7 +48,6 @@
icon_state = "crowbar"
toolspeed = 0.1
-
/obj/item/crowbar/large
name = "crowbar"
desc = "It's a big crowbar. It doesn't fit in your pockets, because it's big."
@@ -85,4 +90,12 @@
var/obj/item/wirecutters/power/cutjaws = new /obj/item/wirecutters/power(drop_location())
to_chat(user, "You attach the cutting jaws to [src].")
qdel(src)
- user.put_in_active_hand(cutjaws)
\ No newline at end of file
+ user.put_in_active_hand(cutjaws)
+
+/obj/item/crowbar/advanced
+ name = "advanced crowbar"
+ desc = "A scientist's almost successful reproduction of an abductor's crowbar, it uses the same technology combined with a handle that can't quite hold it."
+ icon = 'icons/obj/advancedtools.dmi'
+ usesound = 'sound/weapons/sonic_jackhammer.ogg'
+ icon_state = "crowbar"
+ toolspeed = 0.2
\ No newline at end of file
diff --git a/code/game/objects/items/tools/screwdriver.dm b/code/game/objects/items/tools/screwdriver.dm
index 217b53c928..6cbede78a8 100644
--- a/code/game/objects/items/tools/screwdriver.dm
+++ b/code/game/objects/items/tools/screwdriver.dm
@@ -81,11 +81,19 @@
name = "brass screwdriver"
desc = "A screwdriver made of brass. The handle feels freezing cold."
resistance_flags = FIRE_PROOF | ACID_PROOF
- icon_state = "screwdriver_brass"
+ icon_state = "screwdriver_clock"
item_state = "screwdriver_brass"
toolspeed = 0.5
random_color = FALSE
+/obj/item/screwdriver/bronze
+ name = "bronze screwdriver"
+ desc = "A screwdriver plated with bronze."
+ icon_state = "screwdriver_brass"
+ item_state = "screwdriver_brass"
+ toolspeed = 0.95
+ random_color = FALSE
+
/obj/item/screwdriver/abductor
name = "alien screwdriver"
desc = "An ultrasonic screwdriver."
@@ -133,4 +141,14 @@
name = "powered screwdriver"
desc = "An electrical screwdriver, designed to be both precise and quick."
usesound = 'sound/items/drill_use.ogg'
- toolspeed = 0.5
\ No newline at end of file
+ toolspeed = 0.5
+
+/obj/item/screwdriver/advanced
+ name = "advanced screwdriver"
+ desc = "A classy silver screwdriver with an alien alloy tip, it works almost as well as the real thing."
+ icon = 'icons/obj/advancedtools.dmi'
+ icon_state = "screwdriver_a"
+ item_state = "screwdriver_nuke"
+ usesound = 'sound/items/pshoom.ogg'
+ toolspeed = 0.2
+ random_color = FALSE
\ No newline at end of file
diff --git a/code/game/objects/items/tools/weldingtool.dm b/code/game/objects/items/tools/weldingtool.dm
index 54f199969f..fb38e4335e 100644
--- a/code/game/objects/items/tools/weldingtool.dm
+++ b/code/game/objects/items/tools/weldingtool.dm
@@ -360,9 +360,16 @@
name = "brass welding tool"
desc = "A brass welder that seems to constantly refuel itself. It is faintly warm to the touch."
resistance_flags = FIRE_PROOF | ACID_PROOF
- icon_state = "brasswelder"
+ icon_state = "clockwelder"
item_state = "brasswelder"
+/obj/item/weldingtool/bronze
+ name = "bronze plated welding tool"
+ desc = "A bronze plated welder."
+ max_fuel = 21
+ toolspeed = 0.95
+ icon_state = "brasswelder"
+ item_state = "brasswelder"
/obj/item/weldingtool/experimental/process()
..()
@@ -370,4 +377,18 @@
nextrefueltick = world.time + 10
reagents.add_reagent("welding_fuel", 1)
+/obj/item/weldingtool/advanced
+ name = "advanced welding tool"
+ desc = "A modern welding tool combined with an alien welding tool, it never runs out of fuel and works almost as fast."
+ icon = 'icons/obj/advancedtools.dmi'
+ icon_state = "welder"
+ toolspeed = 0.2
+ light_intensity = 0
+ change_icons = 0
+
+/obj/item/weldingtool/advanced/process()
+ if(get_fuel() <= max_fuel)
+ reagents.add_reagent("welding_fuel", 1)
+ ..()
+
#undef WELDER_FUEL_BURN_INTERVAL
diff --git a/code/game/objects/items/tools/wirecutters.dm b/code/game/objects/items/tools/wirecutters.dm
index 2bf9c93ff9..e40ae8bdc1 100644
--- a/code/game/objects/items/tools/wirecutters.dm
+++ b/code/game/objects/items/tools/wirecutters.dm
@@ -63,19 +63,25 @@
/obj/item/wirecutters/brass
name = "brass wirecutters"
- desc = "A pair of wirecutters made of brass. The handle feels freezing cold to the touch."
+ desc = "A pair of eloquent wirecutters made of brass. The handle feels freezing cold to the touch."
resistance_flags = FIRE_PROOF | ACID_PROOF
- icon_state = "cutters_brass"
+ icon_state = "cutters_clock"
random_color = FALSE
toolspeed = 0.5
+/obj/item/wirecutters/bronze
+ name = "bronze plated wirecutters"
+ desc = "A pair of wirecutters plated with bronze."
+ icon_state = "cutters_brass"
+ random_color = FALSE
+ toolspeed = 0.95 //Wire cutters have 0 time bars though
+
/obj/item/wirecutters/abductor
name = "alien wirecutters"
desc = "Extremely sharp wirecutters, made out of a silvery-green metal."
icon = 'icons/obj/abductor.dmi'
icon_state = "cutters"
toolspeed = 0.1
-
random_color = FALSE
/obj/item/wirecutters/cyborg
@@ -119,3 +125,11 @@
return
else
..()
+
+/obj/item/wirecutters/advanced
+ name = "advanced wirecutters"
+ desc = "A set of reproduction alien wirecutters, they have a silver handle with an exceedingly sharp blade."
+ icon = 'icons/obj/advancedtools.dmi'
+ icon_state = "cutters"
+ toolspeed = 0.2
+ random_color = FALSE
\ No newline at end of file
diff --git a/code/game/objects/items/tools/wrench.dm b/code/game/objects/items/tools/wrench.dm
index dc78e9aace..462eb22aaa 100644
--- a/code/game/objects/items/tools/wrench.dm
+++ b/code/game/objects/items/tools/wrench.dm
@@ -32,9 +32,15 @@
name = "brass wrench"
desc = "A brass wrench. It's faintly warm to the touch."
resistance_flags = FIRE_PROOF | ACID_PROOF
- icon_state = "wrench_brass"
+ icon_state = "wrench_clock"
toolspeed = 0.5
+/obj/item/wrench/bronze
+ name = "bronze plated wrench"
+ desc = "A bronze plated wrench."
+ icon_state = "wrench_brass"
+ toolspeed = 0.95
+
/obj/item/wrench/abductor
name = "alien wrench"
desc = "A polarized wrench. It causes anything placed between the jaws to turn."
@@ -43,7 +49,6 @@
usesound = 'sound/effects/empulse.ogg'
toolspeed = 0.1
-
/obj/item/wrench/power
name = "hand drill"
desc = "A simple powered hand drill. It's fitted with a bolt bit."
@@ -107,4 +112,12 @@
user.dust()
- return OXYLOSS
\ No newline at end of file
+ return OXYLOSS
+
+/obj/item/wrench/advanced
+ name = "advanced wrench"
+ desc = "A wrench that uses the same magnetic technology that abductor tools use, but slightly more ineffeciently."
+ icon = 'icons/obj/advancedtools.dmi'
+ icon_state = "wrench"
+ usesound = 'sound/effects/empulse.ogg'
+ toolspeed = 0.2
\ No newline at end of file
diff --git a/code/game/objects/items/toys.dm b/code/game/objects/items/toys.dm
index 274afe15e0..46fabea8b0 100644
--- a/code/game/objects/items/toys.dm
+++ b/code/game/objects/items/toys.dm
@@ -30,6 +30,7 @@
throw_speed = 3
throw_range = 7
force = 0
+ total_mass = TOTAL_MASS_TINY_ITEM
/*
@@ -112,10 +113,6 @@
/obj/item/toy/syndicateballoon
name = "syndicate balloon"
desc = "There is a tag on the back that reads \"FUK NT!11!\"."
- throwforce = 0
- throw_speed = 3
- throw_range = 7
- force = 0
icon = 'icons/obj/items_and_weapons.dmi'
icon_state = "syndballoon"
item_state = "syndballoon"
@@ -225,6 +222,8 @@
w_class = WEIGHT_CLASS_SMALL
attack_verb = list("attacked", "struck", "hit")
var/hacked = FALSE
+ total_mass = 0.4
+ var/total_mass_on = TOTAL_MASS_TOY_SWORD
/obj/item/toy/sword/attack_self(mob/user)
active = !( active )
@@ -249,8 +248,8 @@
// Copied from /obj/item/melee/transforming/energy/sword/attackby
/obj/item/toy/sword/attackby(obj/item/W, mob/living/user, params)
if(istype(W, /obj/item/toy/sword))
- if((W.item_flags & NODROP) || (item_flags & NODROP))
- to_chat(user, "\the [item_flags & NODROP ? src : W] is stuck to your hand, you can't attach it to \the [item_flags & NODROP ? W : src]!")
+ if(HAS_TRAIT(W, TRAIT_NODROP) || HAS_TRAIT(src, TRAIT_NODROP))
+ to_chat(user, "\the [HAS_TRAIT(src, TRAIT_NODROP) ? src : W] is stuck to your hand, you can't attach it to \the [HAS_TRAIT(src, TRAIT_NODROP) ? W : src]!")
return
else
to_chat(user, "You attach the ends of the two plastic swords, making a single double-bladed toy! You're fake-cool.")
@@ -274,6 +273,9 @@
else
return ..()
+/obj/item/toy/sword/getweight()
+ return (active ? total_mass_on : total_mass) || w_class *1.25
+
/*
* Foam armblade
*/
@@ -294,7 +296,7 @@
name = "windup toolbox"
desc = "A replica toolbox that rumbles when you turn the key."
icon_state = "his_grace"
- item_state = "artistic_toolbox"
+ item_state = "toolbox_green"
lefthand_file = 'icons/mob/inhands/equipment/toolbox_lefthand.dmi'
righthand_file = 'icons/mob/inhands/equipment/toolbox_righthand.dmi'
var/active = FALSE
@@ -327,12 +329,13 @@
force_unwielded = 0
force_wielded = 0
attack_verb = list("attacked", "struck", "hit")
+ total_mass_on = TOTAL_MASS_TOY_SWORD
/obj/item/twohanded/dualsaber/toy/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK)
- return 0
+ return FALSE
/obj/item/twohanded/dualsaber/toy/IsReflect()//Stops Toy Dualsabers from reflecting energy projectiles
- return 0
+ return FALSE
/obj/item/toy/katana
name = "replica katana"
@@ -346,6 +349,7 @@
slot_flags = ITEM_SLOT_BELT | ITEM_SLOT_BACK
force = 5
throwforce = 5
+ total_mass = null
w_class = WEIGHT_CLASS_NORMAL
attack_verb = list("attacked", "slashed", "stabbed", "sliced")
hitsound = 'sound/weapons/bladeslice.ogg'
diff --git a/code/game/objects/items/trash.dm b/code/game/objects/items/trash.dm
index 539a60986a..37ab948332 100644
--- a/code/game/objects/items/trash.dm
+++ b/code/game/objects/items/trash.dm
@@ -44,6 +44,10 @@
icon_state = "plate"
resistance_flags = NONE
+/obj/item/trash/plate/alt
+ desc = "Still some dip left. Sadly still just trash..."
+ icon_state = "plate1"
+
/obj/item/trash/pistachios
name = "pistachios pack"
icon_state = "pistachios_pack"
diff --git a/code/game/objects/items/twohanded.dm b/code/game/objects/items/twohanded.dm
index bf63a96f05..b1c2c36585 100644
--- a/code/game/objects/items/twohanded.dm
+++ b/code/game/objects/items/twohanded.dm
@@ -28,12 +28,14 @@
var/force_wielded = 0
var/wieldsound = null
var/unwieldsound = null
+ var/slowdown_wielded = 0
+ item_flags = SLOWS_WHILE_IN_HAND
/obj/item/twohanded/proc/unwield(mob/living/carbon/user, show_message = TRUE)
if(!wielded || !user)
return
wielded = 0
- if(force_unwielded)
+ if(!isnull(force_unwielded))
force = force_unwielded
var/sf = findtext(name," (Wielded)")
if(sf)
@@ -55,7 +57,7 @@
var/obj/item/twohanded/offhand/O = user.get_inactive_held_item()
if(O && istype(O))
O.unwield()
- return
+ slowdown -= slowdown_wielded
/obj/item/twohanded/proc/wield(mob/living/carbon/user)
if(wielded)
@@ -85,7 +87,7 @@
O.desc = "Your second grip on [src]."
O.wielded = TRUE
user.put_in_inactive_hand(O)
- return
+ slowdown += slowdown_wielded
/obj/item/twohanded/dropped(mob/user)
. = ..()
@@ -279,6 +281,7 @@
wieldsound = 'sound/weapons/saberon.ogg'
unwieldsound = 'sound/weapons/saberoff.ogg'
hitsound = "swing_hit"
+ var/hitsound_on = 'sound/weapons/blade1.ogg'
armour_penetration = 35
item_color = "green"
light_color = "#00ff00"//green
@@ -290,8 +293,10 @@
var/hacked = FALSE
var/brightness_on = 6 //TWICE AS BRIGHT AS A REGULAR ESWORD
var/list/possible_colors = list("red", "blue", "green", "purple")
- total_mass = 0.375 //Survival flashlights typically weigh around 5 ounces.
- var/total_mass_on = 3.4 //The typical medieval sword, on the other hand, weighs roughly 3 pounds. //Values copied from the regular e-sword
+ var/list/rainbow_colors = list(LIGHT_COLOR_RED, LIGHT_COLOR_GREEN, LIGHT_COLOR_LIGHT_CYAN, LIGHT_COLOR_LAVENDER)
+ var/spinnable = TRUE
+ total_mass = 0.4 //Survival flashlights typically weigh around 5 ounces.
+ var/total_mass_on = 3.4
/obj/item/twohanded/dualsaber/suicide_act(mob/living/carbon/user)
if(wielded)
@@ -299,7 +304,7 @@
var/obj/item/bodypart/head/myhead = user.get_bodypart(BODY_ZONE_HEAD)//stole from chainsaw code
var/obj/item/organ/brain/B = user.getorganslot(ORGAN_SLOT_BRAIN)
- B.vital = FALSE//this cant possibly be a good idea
+ B.organ_flags &= ~ORGAN_VITAL //this cant possibly be a good idea
var/randdir
for(var/i in 1 to 24)//like a headless chicken!
if(user.is_holding(src))
@@ -353,7 +358,7 @@
if(HAS_TRAIT(user, TRAIT_CLUMSY) && (wielded) && prob(40))
impale(user)
return
- if((wielded) && prob(50))
+ if(spinnable && (wielded) && prob(50))
INVOKE_ASYNC(src, .proc/jedi_spin, user)
/obj/item/twohanded/dualsaber/proc/jedi_spin(mob/living/user)
@@ -406,11 +411,14 @@
/obj/item/twohanded/dualsaber/process()
if(wielded)
if(hacked)
- light_color = pick(LIGHT_COLOR_RED, LIGHT_COLOR_GREEN, LIGHT_COLOR_LIGHT_CYAN, LIGHT_COLOR_LAVENDER)
+ rainbow_process()
open_flame()
else
STOP_PROCESSING(SSobj, src)
+/obj/item/twohanded/dualsaber/proc/rainbow_process()
+ light_color = pick(rainbow_colors)
+
/obj/item/twohanded/dualsaber/IsReflect()
if(wielded)
return 1
@@ -428,7 +436,8 @@
playsound(loc, hitsound, get_clamped_volume(), 1, -1)
add_fingerprint(user)
// Light your candles while spinning around the room
- INVOKE_ASYNC(src, .proc/jedi_spin, user)
+ if(spinnable)
+ INVOKE_ASYNC(src, .proc/jedi_spin, user)
/obj/item/twohanded/dualsaber/green
possible_colors = list("green")
@@ -478,6 +487,7 @@
armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 30)
var/obj/item/grenade/explosive = null
var/war_cry = "AAAAARGH!!!"
+ var/icon_prefix = "spearglass"
/obj/item/twohanded/spear/Initialize()
. = ..()
@@ -520,7 +530,7 @@
if(explosive)
icon_state = "spearbomb[wielded]"
else
- icon_state = "spearglass[wielded]"
+ icon_state = "[icon_prefix][wielded]"
/obj/item/twohanded/spear/afterattack(atom/movable/AM, mob/user, proximity)
. = ..()
@@ -547,6 +557,13 @@
src.war_cry = input
/obj/item/twohanded/spear/CheckParts(list/parts_list)
+ var/obj/item/shard/tip = locate() in parts_list
+ if (istype(tip, /obj/item/shard/plasma))
+ force_wielded = 19
+ force_unwielded = 11
+ throwforce = 21
+ icon_prefix = "spearplasma"
+ qdel(tip)
var/obj/item/twohanded/spear/S = locate() in parts_list
if(S)
if(S.explosive)
@@ -582,6 +599,8 @@
sharpness = IS_SHARP
actions_types = list(/datum/action/item_action/startchainsaw)
var/on = FALSE
+ tool_behaviour = TOOL_SAW
+ toolspeed = 0.5
/obj/item/twohanded/required/chainsaw/Initialize()
. = ..()
diff --git a/code/game/objects/items/weaponry.dm b/code/game/objects/items/weaponry.dm
index ccc703296e..4552e846ad 100644
--- a/code/game/objects/items/weaponry.dm
+++ b/code/game/objects/items/weaponry.dm
@@ -69,6 +69,7 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
max_integrity = 200
armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 50)
resistance_flags = FIRE_PROOF
+ total_mass = TOTAL_MASS_MEDIEVAL_WEAPON
/obj/item/claymore/Initialize()
. = ..()
@@ -81,7 +82,7 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
/obj/item/claymore/highlander //ALL COMMENTS MADE REGARDING THIS SWORD MUST BE MADE IN ALL CAPS
desc = "THERE CAN BE ONLY ONE, AND IT WILL BE YOU!!!\nActivate it in your hand to point to the nearest victim."
flags_1 = CONDUCT_1
- item_flags = NODROP | DROPDEL
+ item_flags = DROPDEL
slot_flags = null
block_chance = 0 //RNG WON'T HELP YOU NOW, PANSY
light_range = 3
@@ -91,6 +92,7 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
/obj/item/claymore/highlander/Initialize()
. = ..()
+ ADD_TRAIT(src, TRAIT_NODROP, HIGHLANDER)
START_PROCESSING(SSobj, src)
/obj/item/claymore/highlander/Destroy()
@@ -222,10 +224,14 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
max_integrity = 200
armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 50)
resistance_flags = FIRE_PROOF
+ total_mass = TOTAL_MASS_MEDIEVAL_WEAPON
/obj/item/katana/cursed
slot_flags = null
- item_flags = NODROP
+
+/obj/item/katana/cursed/Initialize()
+ . = ..()
+ ADD_TRAIT(src, TRAIT_NODROP, CURSED_ITEM_TRAIT)
/obj/item/katana/suicide_act(mob/user)
user.visible_message("[user] is slitting [user.p_their()] stomach open with [src]! It looks like [user.p_theyre()] trying to commit seppuku!")
@@ -249,13 +255,15 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
var/obj/item/twohanded/spear/S = new /obj/item/twohanded/spear
remove_item_from_storage(user)
- qdel(I)
+ if (!user.transferItemToLoc(I, S))
+ return
+ S.CheckParts(list(I))
qdel(src)
user.put_in_hands(S)
to_chat(user, "You fasten the glass shard to the top of the rod with the cable.")
- else if(istype(I, /obj/item/assembly/igniter) && !(I.item_flags & NODROP))
+ else if(istype(I, /obj/item/assembly/igniter) && !HAS_TRAIT(I, TRAIT_NODROP))
var/obj/item/melee/baton/cattleprod/P = new /obj/item/melee/baton/cattleprod
remove_item_from_storage(user)
@@ -418,7 +426,7 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
item_state = "mounted_chainsaw"
lefthand_file = 'icons/mob/inhands/weapons/chainsaw_lefthand.dmi'
righthand_file = 'icons/mob/inhands/weapons/chainsaw_righthand.dmi'
- item_flags = NODROP | ABSTRACT | DROPDEL
+ item_flags = ABSTRACT | DROPDEL
w_class = WEIGHT_CLASS_HUGE
force = 24
throwforce = 0
@@ -427,6 +435,13 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
sharpness = IS_SHARP
attack_verb = list("sawed", "torn", "cut", "chopped", "diced")
hitsound = 'sound/weapons/chainsawhit.ogg'
+ total_mass = TOTAL_MASS_HAND_REPLACEMENT
+ tool_behaviour = TOOL_SAW
+ toolspeed = 1
+
+/obj/item/mounted_chainsaw/Initialize()
+ . = ..()
+ ADD_TRAIT(src, TRAIT_NODROP, HAND_REPLACEMENT_TRAIT)
/obj/item/mounted_chainsaw/Destroy()
var/obj/item/bodypart/part
@@ -502,6 +517,19 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
var/homerun_able = 0
total_mass = 2.7 //a regular wooden major league baseball bat weighs somewhere between 2 to 3.4 pounds, according to google
+/obj/item/melee/baseball_bat/chaplain
+ name = "blessed baseball bat"
+ desc = "There ain't a cult in the league that can withstand a swatter."
+ force = 14
+ throwforce = 14
+ obj_flags = UNIQUE_RENAME
+ var/chaplain_spawnable = TRUE
+ total_mass = TOTAL_MASS_MEDIEVAL_WEAPON
+
+/obj/item/melee/baseball_bat/chaplain/Initialize()
+ . = ..()
+ AddComponent(/datum/component/anti_magic, TRUE, TRUE)
+
/obj/item/melee/baseball_bat/homerun
name = "home run bat"
desc = "This thing looks dangerous... Dangerously good at baseball, that is."
@@ -552,6 +580,12 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
playsound(turf, 'sound/weapons/effects/batreflect2.ogg', 50, 1)
return 1
+/obj/item/melee/baseball_bat/ablative/syndi
+ name = "syndicate major league bat"
+ desc = "A metal bat made by the syndicate for the major league team."
+ force = 18 //Spear damage...
+ throwforce = 30
+
/obj/item/melee/flyswatter
name = "flyswatter"
desc = "Useful for killing insects of all sizes."
diff --git a/code/game/objects/obj_defense.dm b/code/game/objects/obj_defense.dm
index 2db2cd08d8..70a05a8d40 100644
--- a/code/game/objects/obj_defense.dm
+++ b/code/game/objects/obj_defense.dm
@@ -48,7 +48,12 @@
/obj/hitby(atom/movable/AM)
..()
- take_damage(AM.throwforce, BRUTE, "melee", 1, get_dir(src, AM))
+ var/throwdamage = AM.throwforce
+ if(isobj(AM))
+ var/obj/O = AM
+ if(O.damtype == STAMINA)
+ throwdamage = 0
+ take_damage(throwdamage, BRUTE, "melee", 1, get_dir(src, AM))
/obj/ex_act(severity, target)
if(resistance_flags & INDESTRUCTIBLE)
diff --git a/code/game/objects/objs.dm b/code/game/objects/objs.dm
index fd6a9f2141..6dd8a43130 100644
--- a/code/game/objects/objs.dm
+++ b/code/game/objects/objs.dm
@@ -2,6 +2,7 @@
/obj
var/crit_fail = FALSE
animate_movement = 2
+ speech_span = SPAN_ROBOT
var/obj_flags = CAN_BE_HIT
var/set_obj_flags // ONLY FOR MAPPING: Sets flags from a string list, handled in Initialize. Usage: set_obj_flags = "EMAGGED;!CAN_BE_HIT" to set EMAGGED and clear CAN_BE_HIT.
@@ -204,9 +205,6 @@
if(!anchored || current_size >= STAGE_FIVE)
step_towards(src,S)
-/obj/get_spans()
- return ..() | SPAN_ROBOT
-
/obj/get_dumping_location(datum/component/storage/source,mob/user)
return get_turf(src)
diff --git a/code/game/objects/structures.dm b/code/game/objects/structures.dm
index 5f21862c17..7526807eeb 100644
--- a/code/game/objects/structures.dm
+++ b/code/game/objects/structures.dm
@@ -8,6 +8,7 @@
var/climbable = FALSE
var/mob/living/structureclimber
var/broken = 0 //similar to machinery's stat BROKEN
+ layer = BELOW_OBJ_LAYER
/obj/structure/Initialize()
if (!armor)
diff --git a/code/game/objects/structures/artstuff.dm b/code/game/objects/structures/artstuff.dm
index b8320c80fb..405e697d3b 100644
--- a/code/game/objects/structures/artstuff.dm
+++ b/code/game/objects/structures/artstuff.dm
@@ -99,7 +99,7 @@ GLOBAL_LIST_INIT(globalBlankCanvases, new(AMT_OF_CANVASES))
return
//Cleaning one pixel with a soap or rag
- if(istype(I, /obj/item/soap) || istype(I, /obj/item/reagent_containers/glass/rag))
+ if(istype(I, /obj/item/soap) || istype(I, /obj/item/reagent_containers/rag))
//Pixel info created only when needed
var/icon/masterpiece = icon(icon,icon_state)
var/thePix = masterpiece.GetPixel(pixX,pixY)
diff --git a/code/game/objects/structures/barsigns.dm b/code/game/objects/structures/barsigns.dm
index 964f60cb73..2093ae5660 100644
--- a/code/game/objects/structures/barsigns.dm
+++ b/code/game/objects/structures/barsigns.dm
@@ -112,12 +112,16 @@
/obj/structure/sign/barsign/emag_act(mob/user)
+ . = ..()
if(broken || (obj_flags & EMAGGED))
to_chat(user, "Nothing interesting happens!")
return
obj_flags |= EMAGGED
to_chat(user, "You emag the barsign. Takeover in progress...")
- sleep(10 SECONDS)
+ addtimer(CALLBACK(src, .proc/syndie_bar_good), 10 SECONDS)
+ return TRUE
+
+/obj/structure/sign/barsign/proc/syndie_bar_good()
set_sign(new /datum/barsign/hiddensigns/syndibarsign)
req_access = list(ACCESS_SYNDICATE)
@@ -294,6 +298,16 @@
icon = "the_lightbulb"
desc = "A cafe popular among moths and moffs. Once shut down for a week after the bartender used mothballs to protect her spare uniforms."
+/datum/barsign/cybersylph
+ name = "Cyber Sylph's"
+ icon = "cybersylph"
+ desc = "A cafe renowed for its out-of-boundaries futuristic insignia."
+
+/datum/barsign/meow_mix
+ name = "Meow Mix"
+ icon = "meow_mix"
+ desc = "No, we don't serve catnip, officer!"
+
/datum/barsign/hiddensigns
hidden = TRUE
diff --git a/code/game/objects/structures/beds_chairs/chair.dm b/code/game/objects/structures/beds_chairs/chair.dm
index e944eb32da..dde9bce1bc 100644
--- a/code/game/objects/structures/beds_chairs/chair.dm
+++ b/code/game/objects/structures/beds_chairs/chair.dm
@@ -317,9 +317,6 @@
new stack_type(get_turf(loc))
qdel(src)
-
-
-
/obj/item/chair/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK)
if(attack_type == UNARMED_ATTACK && prob(hit_reaction_chance))
owner.visible_message("[owner] fends off [attack_text] with [src]!")
@@ -338,7 +335,6 @@
C.Knockdown(20)
smash(user)
-
/obj/item/chair/stool
name = "stool"
icon_state = "stool_toppled"
@@ -352,6 +348,70 @@
item_state = "stool_bar"
origin_type = /obj/structure/chair/stool/bar
+//////////////////////////
+//Brass & Bronze stools!//
+//////////////////////////
+
+/obj/structure/chair/stool/bar/brass
+ name = "brass bar stool"
+ desc = "A brass bar stool with red silk for a pillow."
+ icon_state = "barbrass"
+ item_chair = /obj/item/chair/stool/bar/brass
+ buildstacktype = /obj/item/stack/tile/brass
+ buildstackamount = 1
+
+/obj/structure/chair/stool/bar/bronze
+ name = "bronze bar stool"
+ desc = "A bronze bar stool with red silk for a pillow."
+ icon_state = "barbrass"
+ item_chair = /obj/item/chair/stool/bar/bronze
+ buildstacktype = /obj/item/stack/tile/bronze
+ buildstackamount = 1
+
+/obj/structure/chair/stool/brass
+ name = "brass stool"
+ desc = "A brass stool with a silk top for comfort."
+ icon_state = "stoolbrass"
+ item_chair = /obj/item/chair/stool/brass
+ buildstacktype = /obj/item/stack/tile/brass
+ buildstackamount = 1
+
+/obj/structure/chair/stool/bronze
+ name = "bronze stool"
+ desc = "A bronze stool with a silk top for comfort."
+ icon_state = "stoolbrass"
+ item_chair = /obj/item/chair/stool/bronze
+ buildstacktype = /obj/item/stack/tile/bronze
+ buildstackamount = 1
+
+/obj/item/chair/stool/brass
+ name = "brass stool"
+ icon_state = "stoolbrass_toppled"
+ item_state = "stoolbrass"
+ origin_type = /obj/structure/chair/stool/brass
+
+/obj/item/chair/stool/bar/brass
+ name = "brass bar stool"
+ icon_state = "barbrass_toppled"
+ item_state = "stoolbrass_bar"
+ origin_type = /obj/structure/chair/stool/bar/brass
+
+/obj/item/chair/stool/bronze
+ name = "bronze stool"
+ icon_state = "stoolbrass_toppled"
+ item_state = "stoolbrass"
+ origin_type = /obj/structure/chair/stool/bronze
+
+/obj/item/chair/stool/bar/bronze
+ name = "bronze bar stool"
+ icon_state = "barbrass_toppled"
+ item_state = "stoolbrass_bar"
+ origin_type = /obj/structure/chair/stool/bar/bronze
+
+/////////////////////////////////
+//End of Brass & Bronze stools!//
+/////////////////////////////////
+
/obj/item/chair/stool/narsie_act()
return //sturdy enough to ignore a god
@@ -429,3 +489,19 @@
. = ..()
if(has_gravity())
playsound(src, 'sound/machines/clockcult/integration_cog_install.ogg', 50, TRUE)
+
+/obj/structure/chair/sofa
+ name = "old ratty sofa"
+ icon_state = "sofamiddle"
+ icon = 'icons/obj/sofa.dmi'
+ buildstackamount = 1
+ item_chair = null
+
+/obj/structure/chair/sofa/left
+ icon_state = "sofaend_left"
+
+/obj/structure/chair/sofa/right
+ icon_state = "sofaend_right"
+
+/obj/structure/chair/sofa/corner
+ icon_state = "sofacorner"
\ No newline at end of file
diff --git a/code/game/objects/structures/bedsheet_bin.dm b/code/game/objects/structures/bedsheet_bin.dm
index 89dcc75042..1f7f0ab391 100644
--- a/code/game/objects/structures/bedsheet_bin.dm
+++ b/code/game/objects/structures/bedsheet_bin.dm
@@ -225,6 +225,39 @@ LINEN BINS
item_color = "ian"
dream_messages = list("a dog", "a corgi", "woof", "bark", "arf")
+/obj/item/bedsheet/runtime
+ icon_state = "sheetruntime"
+ item_color = "runtime"
+ dream_messages = list("a kitty", "a cat", "meow", "purr", "nya~")
+
+/obj/item/bedsheet/pirate
+ name = "pirate's bedsheet"
+ desc = "It has a Jolly Roger emblem on it and has a faint scent of grog."
+ icon_state = "sheetpirate"
+ item_color = "black"
+ dream_messages = list("doing whatever oneself wants", "cause a pirate is free", "being a pirate", "stealing", "landlubbers", "gold", "a buried treasure", "yarr", "avast", "a swashbuckler", "sailing the Seven Seas", "a parrot", "a monkey", "an island", "a talking skull")
+
+/obj/item/bedsheet/gondola
+ name = "gondola bedsheet"
+ desc = "A precious bedsheet made from the hide of a rare and peculiar critter."
+ icon_state = "sheetgondola"
+ item_color = "cargo"
+ var/g_mouth
+ var/g_eyes
+
+/obj/item/bedsheet/gondola/Initialize()
+ . = ..()
+ g_mouth = "sheetgondola_mouth[rand(1, 4)]"
+ g_eyes = "sheetgondola_eyes[rand(1, 4)]"
+ add_overlay(g_mouth)
+ add_overlay(g_eyes)
+
+/obj/item/bedsheet/gondola/worn_overlays(isinhands = FALSE, icon_file)
+ . = ..()
+ if(!isinhands)
+ . += mutable_appearance(icon_file, g_mouth)
+ . += mutable_appearance(icon_file, g_eyes)
+
/obj/item/bedsheet/cosmos
name = "cosmic space bedsheet"
desc = "Made from the dreams of those who wonder at the stars."
@@ -255,18 +288,19 @@ LINEN BINS
resistance_flags = FLAMMABLE
max_integrity = 70
var/amount = 10
+ var/list/sheet_types = list(/obj/item/bedsheet)
+ var/static/allowed_sheets = list(/obj/item/bedsheet, /obj/item/reagent_containers/rag/towel)
var/list/sheets = list()
var/obj/item/hidden = null
-
/obj/structure/bedsheetbin/examine(mob/user)
..()
if(amount < 1)
- to_chat(user, "There are no bed sheets in the bin.")
+ to_chat(user, "There are no sheets in the bin.")
else if(amount == 1)
- to_chat(user, "There is one bed sheet in the bin.")
+ to_chat(user, "There is one sheet in the bin.")
else
- to_chat(user, "There are [amount] bed sheets in the bin.")
+ to_chat(user, "There are [amount] sheets in the bin.")
/obj/structure/bedsheetbin/update_icon()
@@ -285,8 +319,9 @@ LINEN BINS
..()
/obj/structure/bedsheetbin/attackby(obj/item/I, mob/user, params)
- if(istype(I, /obj/item/bedsheet))
+ if(is_type_in_list(I, allowed_sheets))
if(!user.transferItemToLoc(I, src))
+ to_chat(user, "\The [I] is stuck to your hand, you cannot place it into the bin!")
return
sheets.Add(I)
amount++
@@ -307,18 +342,19 @@ LINEN BINS
. = ..()
if(.)
return
- if(user.lying)
+ if(user.incapacitated())
return
if(amount >= 1)
amount--
- var/obj/item/bedsheet/B
+ var/obj/item/B
if(sheets.len > 0)
B = sheets[sheets.len]
sheets.Remove(B)
else
- B = new /obj/item/bedsheet(loc)
+ var/chosen = pick(sheet_types)
+ B = new chosen
B.forceMove(drop_location())
user.put_in_hands(B)
@@ -330,19 +366,20 @@ LINEN BINS
to_chat(user, "[hidden] falls out of [B]!")
hidden = null
-
add_fingerprint(user)
+
/obj/structure/bedsheetbin/attack_tk(mob/user)
if(amount >= 1)
amount--
- var/obj/item/bedsheet/B
+ var/obj/item/B
if(sheets.len > 0)
B = sheets[sheets.len]
sheets.Remove(B)
else
- B = new /obj/item/bedsheet(loc)
+ var/chosen = pick(sheet_types)
+ B = new chosen
B.forceMove(drop_location())
to_chat(user, "You telekinetically remove [B] from [src].")
@@ -352,5 +389,11 @@ LINEN BINS
hidden.forceMove(drop_location())
hidden = null
+/obj/structure/bedsheetbin/towel
+ desc = "It looks rather cosy. This one is designed to hold towels."
+ sheet_types = list(/obj/item/reagent_containers/rag/towel)
- add_fingerprint(user)
+/obj/structure/bedsheetbin/color
+ sheet_types = list(/obj/item/bedsheet, /obj/item/bedsheet/blue, /obj/item/bedsheet/green, /obj/item/bedsheet/orange, \
+ /obj/item/bedsheet/purple, /obj/item/bedsheet/red, /obj/item/bedsheet/yellow, /obj/item/bedsheet/brown, \
+ /obj/item/bedsheet/black)
\ No newline at end of file
diff --git a/code/game/objects/structures/crates_lockers/closets.dm b/code/game/objects/structures/crates_lockers/closets.dm
index cbdb0750e2..8b0d410a72 100644
--- a/code/game/objects/structures/crates_lockers/closets.dm
+++ b/code/game/objects/structures/crates_lockers/closets.dm
@@ -4,7 +4,6 @@
icon = 'icons/obj/closet.dmi'
icon_state = "generic"
density = TRUE
- layer = BELOW_OBJ_LAYER
var/icon_door = null
var/icon_door_override = FALSE //override to have open overlay use icon different to its base's
var/secure = FALSE //secure locker or not, also used if overriding a non-secure locker with a secure door overlay to add fancy lights
@@ -34,6 +33,10 @@
var/delivery_icon = "deliverycloset" //which icon to use when packagewrapped. null to be unwrappable.
var/anchorable = TRUE
var/icon_welded = "welded"
+ var/obj/item/electronics/airlock/lockerelectronics //Installed electronics
+ var/lock_in_use = FALSE //Someone is doing some stuff with the lock here, better not proceed further
+ var/eigen_teleport = FALSE //If the closet leads to Mr Tumnus.
+ var/obj/structure/closet/eigen_target //Where you go to.
/obj/structure/closet/Initialize(mapload)
@@ -42,47 +45,56 @@
PopulateContents()
if(mapload && !opened) // if closed, any item at the crate's loc is put in the contents
take_contents()
+ if(secure)
+ lockerelectronics = new(src)
+ lockerelectronics.accesses = req_access
//USE THIS TO FILL IT, NOT INITIALIZE OR NEW
/obj/structure/closet/proc/PopulateContents()
return
/obj/structure/closet/Destroy()
- dump_contents()
+ dump_contents(override = FALSE)
return ..()
/obj/structure/closet/update_icon()
cut_overlays()
- if(!opened)
+ if(opened & icon_door_override)
+ add_overlay("[icon_door]_open")
layer = OBJ_LAYER
- if(icon_door)
- add_overlay("[icon_door]_door")
- else
- add_overlay("[icon_state]_door")
- if(welded)
- add_overlay(icon_welded)
- if(secure && !broken)
- if(locked)
- add_overlay("locked")
- else
- add_overlay("unlocked")
-
+ return
+ else if(opened)
+ add_overlay("[icon_state]_open")
+ return
+ if(icon_door)
+ add_overlay("[icon_door]_door")
else
layer = BELOW_OBJ_LAYER
- if(icon_door_override)
- add_overlay("[icon_door]_open")
- else
- add_overlay("[icon_state]_open")
+ add_overlay("[icon_state]_door")
+ if(welded)
+ add_overlay("welded")
+ if(!secure)
+ return
+ if(broken)
+ add_overlay("off")
+ add_overlay("sparking")
+ else if(locked)
+ add_overlay("locked")
+ else
+ add_overlay("unlocked")
/obj/structure/closet/examine(mob/user)
..()
if(welded)
- to_chat(user, "It's welded shut.")
+ to_chat(user, "It's welded shut.")
if(anchored)
to_chat(user, "It is bolted to the ground.")
if(opened)
to_chat(user, "The parts are welded together.")
else if(secure && !opened)
+ else if(broken)
+ to_chat(user, "The lock is screwed in.")
+ else if(secure)
to_chat(user, "Alt-click to [locked ? "unlock" : "lock"].")
if(isliving(user))
var/mob/living/L = user
@@ -117,9 +129,39 @@
return FALSE
return TRUE
-/obj/structure/closet/proc/dump_contents()
+/obj/structure/closet/proc/can_lock(mob/living/user, var/check_access = TRUE) //set check_access to FALSE if you only need to check if a locker has a functional lock rather than access
+ if(!secure)
+ return FALSE
+ if(broken)
+ to_chat(user, "[src] is broken!")
+ return FALSE
+ if(QDELETED(lockerelectronics) && !locked) //We want to be able to unlock it regardless of electronics, but only lockable with electronics
+ to_chat(user, "[src] is missing locker electronics!")
+ return FALSE
+ if(!check_access)
+ return TRUE
+ if(allowed(user))
+ return TRUE
+ to_chat(user, "Access denied.")
+
+/obj/structure/closet/proc/togglelock(mob/living/user)
+ add_fingerprint(user)
+ if(eigen_target)
+ return
+ if(opened)
+ return
+ if(!can_lock(user))
+ return
+ locked = !locked
+ user.visible_message("[user] [locked ? null : "un"]locks [src].",
+ "You [locked ? null : "un"]lock [src].")
+ update_icon()
+
+/obj/structure/closet/proc/dump_contents(var/override = TRUE) //Override is for not revealing the locker electronics when you open the locker, for example
var/atom/L = drop_location()
for(var/atom/movable/AM in src)
+ if(AM == lockerelectronics && override)
+ continue
AM.forceMove(L)
if(throwing) // you keep some momentum when getting out of a thrown closet
step(AM, dir)
@@ -147,47 +189,53 @@
/obj/structure/closet/proc/insert(atom/movable/AM)
if(contents.len >= storage_capacity)
return -1
+ if(insertion_allowed(AM))
+ if(eigen_teleport) // For teleporting people with linked lockers.
+ do_teleport(AM, get_turf(eigen_target), 0)
+ if(eigen_target.opened == FALSE)
+ eigen_target.bust_open()
+ else
+ AM.forceMove(src)
+ return TRUE
+ else
+ return FALSE
+/obj/structure/closet/proc/insertion_allowed(atom/movable/AM)
if(ismob(AM))
if(!isliving(AM)) //let's not put ghosts or camera mobs inside closets...
- return
+ return FALSE
var/mob/living/L = AM
if(L.anchored || L.buckled || L.incorporeal_move || L.has_buckled_mobs())
- return
+ return FALSE
if(L.mob_size > MOB_SIZE_TINY) // Tiny mobs are treated as items.
if(horizontal && L.density)
- return
+ return FALSE
if(L.mob_size > max_mob_size)
- return
+ return FALSE
var/mobs_stored = 0
for(var/mob/living/M in contents)
if(++mobs_stored >= mob_storage_capacity)
- return
+ return FALSE
L.stop_pulling()
+
else if(istype(AM, /obj/structure/closet))
- return
+ return FALSE
else if(istype(AM, /obj/effect))
- return
+ return FALSE
else if(isobj(AM))
- if (istype(AM, /obj/item))
- var/obj/item/I = AM
- if (I.item_flags & NODROP)
- return
+ if((!allow_dense && AM.density) || AM.anchored || AM.has_buckled_mobs())
+ return FALSE
+ if(isitem(AM) && !HAS_TRAIT(AM, TRAIT_NODROP))
+ return TRUE
else if(!allow_objects && !istype(AM, /obj/effect/dummy/chameleon))
- return
- if(!allow_dense && AM.density)
- return
- if(AM.anchored || AM.has_buckled_mobs())
- return
+ return FALSE
else
- return
+ return FALSE
- AM.forceMove(src)
-
- return 1
+ return TRUE
/obj/structure/closet/proc/close(mob/living/user)
if(!opened || !can_close(user))
@@ -206,6 +254,73 @@
else
return open(user)
+/obj/structure/closet/proc/bust_open()
+ welded = FALSE //applies to all lockers
+ locked = FALSE //applies to critter crates and secure lockers only
+ broken = TRUE //applies to secure lockers only
+ open()
+
+/obj/structure/closet/proc/handle_lock_addition(mob/user, obj/item/electronics/airlock/E)
+ add_fingerprint(user)
+ if(lock_in_use)
+ to_chat(user, "Wait for work on [src] to be done first!")
+ return
+ if(secure)
+ to_chat(user, "This locker already has a lock!")
+ return
+ if(broken)
+ to_chat(user, "Unscrew the broken lock first!")
+ return
+ if(!istype(E))
+ return
+ user.visible_message("[user] begins installing a lock on [src]...","You begin installing a lock on [src]...")
+ lock_in_use = TRUE
+ playsound(loc, 'sound/items/screwdriver.ogg', 50, 1)
+ if(!do_after(user, 60, target = src))
+ lock_in_use = FALSE
+ return
+ lock_in_use = FALSE
+ to_chat(user, "You finish the lock on [src]!")
+ E.forceMove(src)
+ lockerelectronics = E
+ req_access = E.accesses
+ secure = TRUE
+ update_icon()
+ return TRUE
+
+/obj/structure/closet/proc/handle_lock_removal(mob/user, obj/item/screwdriver/S)
+ if(lock_in_use)
+ to_chat(user, "Wait for work on [src] to be done first!")
+ return
+ if(locked)
+ to_chat(user, "Unlock it first!")
+ return
+ if(!secure)
+ to_chat(user, "[src] doesn't have a lock that you can remove!")
+ return
+ if(!istype(S))
+ return
+ var/brokenword = broken ? "broken " : null
+ user.visible_message("[user] begins removing the [brokenword]lock on [src]...","You begin removing the [brokenword]lock on [src]...")
+ playsound(loc, S.usesound, 50, 1)
+ lock_in_use = TRUE
+ if(!do_after(user, 100 * S.toolspeed, target = src))
+ lock_in_use = FALSE
+ return
+ to_chat(user, "You remove the [brokenword]lock from [src]!")
+ if(!QDELETED(lockerelectronics))
+ lockerelectronics.add_fingerprint(user)
+ lockerelectronics.forceMove(user.loc)
+ lockerelectronics = null
+ req_access = null
+ secure = FALSE
+ broken = FALSE
+ locked = FALSE
+ lock_in_use = FALSE
+ update_icon()
+ return TRUE
+
+
/obj/structure/closet/deconstruct(disassembled = TRUE)
if(ispath(material_drop) && material_drop_amount && !(flags_1 & NODECONSTRUCT_1))
new material_drop(loc, material_drop_amount)
@@ -233,6 +348,9 @@
to_chat(user, "You begin cutting \the [src] apart...")
if(W.use_tool(src, user, 40, volume=50))
+ if(eigen_teleport)
+ to_chat(user, "The unstable nature of \the [src] makes it impossible to cut!")
+ return
if(!opened)
return
user.visible_message("[user] slices apart \the [src].",
@@ -246,18 +364,25 @@
deconstruct(TRUE)
return
if(user.transferItemToLoc(W, drop_location())) // so we put in unlit welder too
- return
+ return TRUE
+ else if(istype(W, /obj/item/electronics/airlock))
+ handle_lock_addition(user, W)
+ else if(istype(W, /obj/item/screwdriver))
+ handle_lock_removal(user, W)
else if(istype(W, /obj/item/weldingtool) && can_weld_shut)
if(!W.tool_start_check(user, amount=0))
return
to_chat(user, "You begin [welded ? "unwelding":"welding"] \the [src]...")
if(W.use_tool(src, user, 40, volume=50))
+ if(eigen_teleport)
+ to_chat(user, "The unstable nature of \the [src] makes it impossible to weld!")
+ return
if(opened)
return
welded = !welded
after_weld(welded)
- user.visible_message("[user] [welded ? "welds shut" : "unwelded"] \the [src].",
+ user.visible_message("[user] [welded ? "welds shut" : "unwelds"] \the [src].",
"You [welded ? "weld" : "unwelded"] \the [src] with \the [W].",
"You hear welding.")
update_icon()
@@ -400,20 +525,12 @@
if(user.loc == src) //so we don't get the message if we resisted multiple times and succeeded.
to_chat(user, "You fail to break out of [src]!")
-/obj/structure/closet/proc/bust_open()
- welded = FALSE //applies to all lockers
- locked = FALSE //applies to critter crates and secure lockers only
- broken = TRUE //applies to secure lockers only
- open()
-
/obj/structure/closet/AltClick(mob/user)
..()
- if(!user.canUseTopic(src, BE_CLOSE) || !isturf(loc))
+ if(!user.canUseTopic(src, be_close=TRUE) || !isturf(loc))
+ to_chat(user, "You can't do that right now!")
return
- if(opened || !secure)
- return
- else
- togglelock(user)
+ togglelock(user)
/obj/structure/closet/CtrlShiftClick(mob/living/user)
if(!HAS_TRAIT(user, TRAIT_SKITTISH))
@@ -422,29 +539,19 @@
return
dive_into(user)
-/obj/structure/closet/proc/togglelock(mob/living/user, silent)
- if(secure && !broken)
- if(allowed(user))
- if(iscarbon(user))
- add_fingerprint(user)
- locked = !locked
- user.visible_message("[user] [locked ? null : "un"]locks [src].",
- "You [locked ? null : "un"]lock [src].")
- update_icon()
- else if(!silent)
- to_chat(user, "Access Denied")
- else if(secure && broken)
- to_chat(user, "\The [src] is broken!")
-
/obj/structure/closet/emag_act(mob/user)
- if(secure && !broken)
- user.visible_message("Sparks fly from [src]!",
- "You scramble [src]'s lock, breaking it open!",
- "You hear a faint electrical spark.")
- playsound(src, "sparks", 50, 1)
- broken = TRUE
- locked = FALSE
- update_icon()
+ . = ..()
+ if(!secure || broken)
+ return
+ user.visible_message("Sparks fly from [src]!",
+ "You scramble [src]'s lock, breaking it open!",
+ "You hear a faint electrical spark.")
+ playsound(src, "sparks", 50, 1)
+ broken = TRUE
+ locked = FALSE
+ if(!QDELETED(lockerelectronics))
+ QDEL_NULL(lockerelectronics)
+ update_icon()
/obj/structure/closet/get_remote_view_fullscreens(mob/user)
if(user.stat == DEAD || !(user.sight & (SEEOBJS|SEEMOBS)))
@@ -457,16 +564,19 @@
if (!(. & EMP_PROTECT_CONTENTS))
for(var/obj/O in src)
O.emp_act(severity)
- if(secure && !broken && !(. & EMP_PROTECT_SELF))
- if(prob(50 / severity))
- locked = !locked
- update_icon()
- if(prob(20 / severity) && !opened)
- if(!locked)
- open()
- else
- req_access = list()
- req_access += pick(get_all_accesses())
+ if(!secure || broken)
+ return ..()
+ if(prob(50 / severity))
+ locked = !locked
+ update_icon()
+ if(prob(20 / severity) && !opened)
+ if(!locked)
+ open()
+ else
+ req_access = list()
+ req_access += pick(get_all_accesses())
+ if(!QDELETED(lockerelectronics))
+ lockerelectronics.accesses = req_access
/obj/structure/closet/contents_explosion(severity, target)
for(var/atom/A in contents)
diff --git a/code/game/objects/structures/crates_lockers/closets/bodybag.dm b/code/game/objects/structures/crates_lockers/closets/bodybag.dm
index 502b23354c..1c34850274 100644
--- a/code/game/objects/structures/crates_lockers/closets/bodybag.dm
+++ b/code/game/objects/structures/crates_lockers/closets/bodybag.dm
@@ -49,6 +49,12 @@
return 1
return 0
+/obj/structure/closet/body_bag/handle_lock_addition()
+ return
+
+/obj/structure/closet/body_bag/handle_lock_removal()
+ return
+
/obj/structure/closet/body_bag/MouseDrop(over_object, src_location, over_location)
. = ..()
if(over_object == usr && Adjacent(usr) && (in_range(src, usr) || usr.contents.Find(src)))
diff --git a/code/game/objects/structures/crates_lockers/closets/cardboardbox.dm b/code/game/objects/structures/crates_lockers/closets/cardboardbox.dm
index 82b0d1a441..aad68b2166 100644
--- a/code/game/objects/structures/crates_lockers/closets/cardboardbox.dm
+++ b/code/game/objects/structures/crates_lockers/closets/cardboardbox.dm
@@ -57,6 +57,11 @@
I.alpha = 0
animate(I, pixel_z = 32, alpha = 255, time = 5, easing = ELASTIC_EASING)
+/obj/structure/closet/cardboard/handle_lock_addition() //Whoever heard of a lockable cardboard box anyway
+ return
+
+/obj/structure/closet/cardboard/handle_lock_removal()
+ return
/obj/structure/closet/cardboard/metal
name = "large metal box"
diff --git a/code/game/objects/structures/crates_lockers/closets/job_closets.dm b/code/game/objects/structures/crates_lockers/closets/job_closets.dm
index 0809edaa71..b49d0a77d5 100644
--- a/code/game/objects/structures/crates_lockers/closets/job_closets.dm
+++ b/code/game/objects/structures/crates_lockers/closets/job_closets.dm
@@ -21,8 +21,8 @@
new /obj/item/clothing/head/soft/black(src)
new /obj/item/clothing/shoes/sneakers/black(src)
new /obj/item/clothing/shoes/sneakers/black(src)
- new /obj/item/reagent_containers/glass/rag(src)
- new /obj/item/reagent_containers/glass/rag(src)
+ new /obj/item/reagent_containers/rag(src)
+ new /obj/item/reagent_containers/rag(src)
new /obj/item/storage/box/beanbag(src)
new /obj/item/clothing/suit/armor/vest/alt(src)
new /obj/item/circuitboard/machine/dish_drive(src)
@@ -53,7 +53,7 @@
new /obj/item/clothing/suit/toggle/chef(src)
new /obj/item/clothing/under/rank/chef(src)
new /obj/item/clothing/head/chefhat(src)
- new /obj/item/reagent_containers/glass/rag(src)
+ new /obj/item/reagent_containers/rag(src)
/obj/structure/closet/jcloset
name = "custodial closet"
@@ -111,9 +111,9 @@
new /obj/item/clothing/accessory/pocketprotector/cosmetology(src)
new /obj/item/clothing/under/rank/chaplain(src)
new /obj/item/clothing/shoes/sneakers/black(src)
- new /obj/item/clothing/suit/nun(src)
+ new /obj/item/clothing/suit/chaplain/nun(src)
new /obj/item/clothing/head/nun_hood(src)
- new /obj/item/clothing/suit/holidaypriest(src)
+ new /obj/item/clothing/suit/chaplain/holidaypriest(src)
new /obj/item/storage/backpack/cultpack(src)
new /obj/item/storage/fancy/candle_box(src)
new /obj/item/storage/fancy/candle_box(src)
@@ -358,3 +358,8 @@
new /obj/item/clothing/shoes/workboots/mining(src)
new /obj/item/storage/backpack/satchel/explorer(src)
+/obj/structure/closet/coffin/handle_lock_addition()
+ return
+
+/obj/structure/closet/coffin/handle_lock_removal()
+ return
diff --git a/code/game/objects/structures/crates_lockers/closets/secure/cargo.dm b/code/game/objects/structures/crates_lockers/closets/secure/cargo.dm
index 5a9228e397..18928424c0 100644
--- a/code/game/objects/structures/crates_lockers/closets/secure/cargo.dm
+++ b/code/game/objects/structures/crates_lockers/closets/secure/cargo.dm
@@ -9,6 +9,7 @@
new /obj/item/clothing/head/beret/qm(src)
new /obj/item/storage/lockbox/medal/cargo(src)
new /obj/item/clothing/under/rank/cargo(src)
+ new /obj/item/clothing/under/rank/cargo/skirt(src)
new /obj/item/clothing/shoes/sneakers/brown(src)
new /obj/item/radio/headset/headset_cargo(src)
new /obj/item/clothing/suit/fire/firefighter(src)
diff --git a/code/game/objects/structures/crates_lockers/closets/secure/engineering.dm b/code/game/objects/structures/crates_lockers/closets/secure/engineering.dm
index 0d06276876..a7adafdad4 100644
--- a/code/game/objects/structures/crates_lockers/closets/secure/engineering.dm
+++ b/code/game/objects/structures/crates_lockers/closets/secure/engineering.dm
@@ -8,6 +8,7 @@
new /obj/item/clothing/neck/cloak/ce(src)
new /obj/item/clothing/head/beret/ce(src)
new /obj/item/clothing/under/rank/chief_engineer(src)
+ new /obj/item/clothing/under/rank/chief_engineer/skirt(src)
new /obj/item/clothing/head/hardhat/white(src)
new /obj/item/clothing/head/welding(src)
new /obj/item/clothing/gloves/color/yellow(src)
@@ -32,6 +33,7 @@
new /obj/item/extinguisher/advanced(src)
new /obj/item/storage/photo_album/CE(src)
new /obj/item/storage/lockbox/medal/engineering(src)
+ new /obj/item/construction/rcd/loaded/upgraded(src)
/obj/structure/closet/secure_closet/engineering_electrical
name = "electrical supplies locker"
diff --git a/code/game/objects/structures/crates_lockers/closets/secure/freezer.dm b/code/game/objects/structures/crates_lockers/closets/secure/freezer.dm
index 6e841bcd93..9dab3679fa 100644
--- a/code/game/objects/structures/crates_lockers/closets/secure/freezer.dm
+++ b/code/game/objects/structures/crates_lockers/closets/secure/freezer.dm
@@ -2,6 +2,24 @@
icon_state = "freezer"
var/jones = FALSE
+/obj/structure/closet/secure_closet/freezer/Destroy()
+ recursive_organ_check(src)
+ ..()
+
+/obj/structure/closet/secure_closet/freezer/Initialize()
+ ..()
+ recursive_organ_check(src)
+
+/obj/structure/closet/secure_closet/freezer/open(mob/living/user)
+ if(opened || !can_open(user)) //dupe check just so we don't let the organs decay when someone fails to open the locker
+ return FALSE
+ recursive_organ_check(src)
+ return ..()
+
+/obj/structure/closet/secure_closet/freezer/close(mob/living/user)
+ if(..()) //if we actually closed the locker
+ recursive_organ_check(src)
+
/obj/structure/closet/secure_closet/freezer/ex_act()
if(!jones)
jones = TRUE
diff --git a/code/game/objects/structures/crates_lockers/closets/secure/medical.dm b/code/game/objects/structures/crates_lockers/closets/secure/medical.dm
index 9081cddbe4..0f810225b3 100644
--- a/code/game/objects/structures/crates_lockers/closets/secure/medical.dm
+++ b/code/game/objects/structures/crates_lockers/closets/secure/medical.dm
@@ -61,6 +61,7 @@
new /obj/item/clothing/head/bio_hood/cmo(src)
new /obj/item/clothing/suit/toggle/labcoat/cmo(src)
new /obj/item/clothing/under/rank/chief_medical_officer(src)
+ new /obj/item/clothing/under/rank/chief_medical_officer/skirt(src)
new /obj/item/clothing/shoes/sneakers/brown (src)
new /obj/item/cartridge/cmo(src)
new /obj/item/radio/headset/heads/cmo(src)
diff --git a/code/game/objects/structures/crates_lockers/closets/secure/personal.dm b/code/game/objects/structures/crates_lockers/closets/secure/personal.dm
index eb764fc230..e44d3c9079 100644
--- a/code/game/objects/structures/crates_lockers/closets/secure/personal.dm
+++ b/code/game/objects/structures/crates_lockers/closets/secure/personal.dm
@@ -4,6 +4,18 @@
req_access = list(ACCESS_ALL_PERSONAL_LOCKERS)
var/registered_name = null
+/obj/structure/closet/secure_closet/personal/examine(mob/user)
+ ..()
+ if(registered_name)
+ to_chat(user, "The display reads, \"Owned by [registered_name]\".")
+
+/obj/structure/closet/secure_closet/personal/check_access(obj/item/card/id/I)
+ . = ..()
+ if(!I || !istype(I))
+ return
+ if(registered_name == I.registered_name)
+ return TRUE
+
/obj/structure/closet/secure_closet/personal/PopulateContents()
..()
if(prob(50))
@@ -33,21 +45,21 @@
/obj/structure/closet/secure_closet/personal/attackby(obj/item/W, mob/user, params)
var/obj/item/card/id/I = W.GetID()
- if(istype(I))
- if(broken)
- to_chat(user, "It appears to be broken.")
- return
- if(!I || !I.registered_name)
- return
- if(allowed(user) || !registered_name || (istype(I) && (registered_name == I.registered_name)))
- //they can open all lockers, or nobody owns this, or they own this locker
- locked = !locked
- update_icon()
-
- if(!registered_name)
- registered_name = I.registered_name
- desc = "Owned by [I.registered_name]."
- else
- to_chat(user, "Access Denied.")
- else
+ if(!I || !istype(I))
return ..()
+ if(!can_lock(user, FALSE)) //Can't do anything if there isn't a lock!
+ return
+ if(I.registered_name && !registered_name)
+ to_chat(user, "You claim [src].")
+ registered_name = I.registered_name
+ else
+ ..()
+
+/obj/structure/closet/secure_closet/personal/handle_lock_addition() //If lock construction is successful we don't care what access the electronics had, so we override it
+ if(..())
+ req_access = list(ACCESS_ALL_PERSONAL_LOCKERS)
+ lockerelectronics.accesses = req_access
+
+/obj/structure/closet/secure_closet/personal/handle_lock_removal()
+ if(..())
+ registered_name = null
diff --git a/code/game/objects/structures/crates_lockers/closets/secure/scientist.dm b/code/game/objects/structures/crates_lockers/closets/secure/scientist.dm
index 7fe1247eb7..efcc2aa7ca 100644
--- a/code/game/objects/structures/crates_lockers/closets/secure/scientist.dm
+++ b/code/game/objects/structures/crates_lockers/closets/secure/scientist.dm
@@ -11,8 +11,11 @@
new /obj/item/clothing/head/bio_hood/scientist(src)
new /obj/item/clothing/suit/toggle/labcoat(src)
new /obj/item/clothing/under/rank/research_director(src)
+ new /obj/item/clothing/under/rank/research_director/skirt(src)
new /obj/item/clothing/under/rank/research_director/alt(src)
+ new /obj/item/clothing/under/rank/research_director/alt/skirt(src)
new /obj/item/clothing/under/rank/research_director/turtleneck(src)
+ new /obj/item/clothing/under/rank/research_director/turtleneck/skirt(src)
new /obj/item/clothing/shoes/sneakers/brown(src)
new /obj/item/cartridge/rd(src)
new /obj/item/clothing/gloves/color/latex(src)
diff --git a/code/game/objects/structures/crates_lockers/closets/secure/security.dm b/code/game/objects/structures/crates_lockers/closets/secure/security.dm
index 8c06af91a4..3cb8ceb22b 100644
--- a/code/game/objects/structures/crates_lockers/closets/secure/security.dm
+++ b/code/game/objects/structures/crates_lockers/closets/secure/security.dm
@@ -2,7 +2,6 @@
name = "\proper captain's locker"
req_access = list(ACCESS_CAPTAIN)
icon_state = "cap"
-
/obj/structure/closet/secure_closet/captains/PopulateContents()
..()
new /obj/item/clothing/suit/hooded/wintercoat/captain(src)
@@ -14,6 +13,7 @@
new /obj/item/pet_carrier(src)
new /obj/item/clothing/shoes/sneakers/brown(src)
new /obj/item/clothing/under/rank/captain(src)
+ new /obj/item/clothing/under/rank/captain/skirt(src)
new /obj/item/clothing/suit/armor/vest/capcarapace(src)
new /obj/item/clothing/head/caphat(src)
new /obj/item/clothing/under/captainparade(src)
@@ -34,16 +34,15 @@
new /obj/item/gun/energy/e_gun(src)
new /obj/item/door_remote/captain(src)
new /obj/item/storage/photo_album/Captain(src)
-
/obj/structure/closet/secure_closet/hop
name = "\proper head of personnel's locker"
req_access = list(ACCESS_HOP)
icon_state = "hop"
-
/obj/structure/closet/secure_closet/hop/PopulateContents()
..()
new /obj/item/clothing/neck/cloak/hop(src)
new /obj/item/clothing/under/rank/head_of_personnel(src)
+ new /obj/item/clothing/under/rank/head_of_personnel/skirt(src)
new /obj/item/clothing/head/hopcap(src)
new /obj/item/clothing/head/hopcap/beret(src)
new /obj/item/cartridge/hop(src)
@@ -62,12 +61,10 @@
new /obj/item/door_remote/civillian(src)
new /obj/item/circuitboard/machine/techfab/department/service(src)
new /obj/item/storage/photo_album/HoP(src)
-
/obj/structure/closet/secure_closet/hos
name = "\proper head of security's locker"
req_access = list(ACCESS_HOS)
icon_state = "hos"
-
/obj/structure/closet/secure_closet/hos/PopulateContents()
..()
new /obj/item/clothing/neck/cloak/hos(src)
@@ -77,7 +74,9 @@
new /obj/item/clothing/under/hosparademale(src)
new /obj/item/clothing/suit/armor/vest/leather(src)
new /obj/item/clothing/suit/armor/hos(src)
+ new /obj/item/clothing/under/rank/head_of_security/skirt(src)
new /obj/item/clothing/under/rank/head_of_security/alt(src)
+ new /obj/item/clothing/under/rank/head_of_security/alt/skirt(src)
new /obj/item/clothing/head/HoS(src)
new /obj/item/clothing/glasses/hud/security/sunglasses/eyepatch(src)
new /obj/item/clothing/glasses/hud/security/sunglasses/gars/supergars(src)
@@ -95,12 +94,10 @@
new /obj/item/pinpointer/nuke(src)
new /obj/item/circuitboard/machine/techfab/department/security(src)
new /obj/item/storage/photo_album/HoS(src)
-
/obj/structure/closet/secure_closet/warden
name = "\proper warden's locker"
req_access = list(ACCESS_ARMORY)
icon_state = "warden"
-
/obj/structure/closet/secure_closet/warden/PopulateContents()
..()
new /obj/item/radio/headset/headset_sec(src)
@@ -110,6 +107,7 @@
new /obj/item/clothing/head/beret/sec/navywarden(src)
new /obj/item/clothing/suit/armor/vest/warden/alt(src)
new /obj/item/clothing/under/rank/warden/navyblue(src)
+ new /obj/item/clothing/under/rank/warden/skirt(src)
new /obj/item/clothing/glasses/hud/security/sunglasses(src)
new /obj/item/holosign_creator/security(src)
new /obj/item/clothing/mask/gas/sechailer(src)
@@ -120,12 +118,10 @@
new /obj/item/clothing/gloves/krav_maga/sec(src)
new /obj/item/door_remote/head_of_security(src)
new /obj/item/gun/ballistic/shotgun/automatic/combat/compact(src)
-
/obj/structure/closet/secure_closet/security
name = "security officer's locker"
req_access = list(ACCESS_SECURITY)
icon_state = "sec"
-
/obj/structure/closet/secure_closet/security/PopulateContents()
..()
new /obj/item/clothing/suit/armor/vest(src)
@@ -134,55 +130,45 @@
new /obj/item/radio/headset/headset_sec/alt(src)
new /obj/item/clothing/glasses/hud/security/sunglasses(src)
new /obj/item/flashlight/seclite(src)
-
/obj/structure/closet/secure_closet/security/sec
-
/obj/structure/closet/secure_closet/security/sec/PopulateContents()
..()
new /obj/item/storage/belt/security/full(src)
-
/obj/structure/closet/secure_closet/security/cargo
-
/obj/structure/closet/secure_closet/security/cargo/PopulateContents()
..()
new /obj/item/clothing/accessory/armband/cargo(src)
new /obj/item/encryptionkey/headset_cargo(src)
-
/obj/structure/closet/secure_closet/security/engine
-
/obj/structure/closet/secure_closet/security/engine/PopulateContents()
..()
new /obj/item/clothing/accessory/armband/engine(src)
new /obj/item/encryptionkey/headset_eng(src)
-
/obj/structure/closet/secure_closet/security/science
-
/obj/structure/closet/secure_closet/security/science/PopulateContents()
..()
new /obj/item/clothing/accessory/armband/science(src)
new /obj/item/encryptionkey/headset_sci(src)
-
/obj/structure/closet/secure_closet/security/med
-
/obj/structure/closet/secure_closet/security/med/PopulateContents()
..()
new /obj/item/clothing/accessory/armband/medblue(src)
new /obj/item/encryptionkey/headset_med(src)
-
/obj/structure/closet/secure_closet/detective
name = "\improper detective's cabinet"
req_access = list(ACCESS_FORENSICS_LOCKERS)
icon_state = "cabinet"
resistance_flags = FLAMMABLE
max_integrity = 70
-
/obj/structure/closet/secure_closet/detective/PopulateContents()
..()
new /obj/item/clothing/under/rank/det(src)
+ new /obj/item/clothing/under/rank/det/skirt(src)
new /obj/item/clothing/suit/det_suit(src)
new /obj/item/clothing/head/fedora/det_hat(src)
new /obj/item/clothing/gloves/color/black(src)
new /obj/item/clothing/under/rank/det/grey(src)
+ new /obj/item/clothing/under/rank/det/grey/skirt(src)
new /obj/item/clothing/accessory/waistcoat(src)
new /obj/item/clothing/suit/det_suit/grey(src)
new /obj/item/clothing/head/fedora(src)
@@ -200,33 +186,29 @@
/obj/structure/closet/secure_closet/injection
name = "lethal injections"
req_access = list(ACCESS_HOS)
-
/obj/structure/closet/secure_closet/injection/PopulateContents()
..()
for(var/i in 1 to 5)
new /obj/item/reagent_containers/syringe/lethal/execution(src)
-
/obj/structure/closet/secure_closet/brig
name = "brig locker"
req_access = list(ACCESS_BRIG)
anchored = TRUE
var/id = null
-
/obj/structure/closet/secure_closet/evidence
anchored = TRUE
name = "Secure Evidence Closet"
req_access_txt = "0"
req_one_access_txt = list(ACCESS_ARMORY, ACCESS_FORENSICS_LOCKERS)
-
/obj/structure/closet/secure_closet/brig/PopulateContents()
..()
new /obj/item/clothing/under/rank/prisoner( src )
+ new /obj/item/clothing/under/rank/prisoner/skirt( src )
new /obj/item/clothing/shoes/sneakers/orange( src )
/obj/structure/closet/secure_closet/courtroom
name = "courtroom locker"
req_access = list(ACCESS_COURT)
-
/obj/structure/closet/secure_closet/courtroom/PopulateContents()
..()
new /obj/item/clothing/shoes/sneakers/brown(src)
@@ -236,22 +218,18 @@
new /obj/item/clothing/suit/judgerobe (src)
new /obj/item/clothing/head/powdered_wig (src)
new /obj/item/storage/briefcase(src)
-
/obj/structure/closet/secure_closet/contraband/armory
anchored = TRUE
name = "Contraband Locker"
req_access = list(ACCESS_ARMORY)
-
/obj/structure/closet/secure_closet/contraband/heads
anchored = TRUE
name = "Contraband Locker"
req_access = list(ACCESS_HEADS)
-
/obj/structure/closet/secure_closet/armory1
name = "armory armor locker"
req_access = list(ACCESS_ARMORY)
icon_state = "armory"
-
/obj/structure/closet/secure_closet/armory1/PopulateContents()
..()
new /obj/item/clothing/suit/armor/laserproof(src)
@@ -261,12 +239,10 @@
new /obj/item/clothing/head/helmet/riot(src)
for(var/i in 1 to 3)
new /obj/item/shield/riot(src)
-
/obj/structure/closet/secure_closet/armory2
name = "armory ballistics locker"
req_access = list(ACCESS_ARMORY)
icon_state = "armory"
-
/obj/structure/closet/secure_closet/armory2/PopulateContents()
..()
new /obj/item/storage/box/firingpins(src)
@@ -274,12 +250,10 @@
new /obj/item/storage/box/rubbershot(src)
for(var/i in 1 to 3)
new /obj/item/gun/ballistic/shotgun/riot(src)
-
/obj/structure/closet/secure_closet/armory3
name = "armory energy gun locker"
req_access = list(ACCESS_ARMORY)
icon_state = "armory"
-
/obj/structure/closet/secure_closet/armory3/PopulateContents()
..()
new /obj/item/storage/box/firingpins(src)
@@ -288,24 +262,20 @@
new /obj/item/gun/energy/e_gun(src)
for(var/i in 1 to 3)
new /obj/item/gun/energy/laser(src)
-
/obj/structure/closet/secure_closet/tac
name = "armory tac locker"
req_access = list(ACCESS_ARMORY)
icon_state = "tac"
-
/obj/structure/closet/secure_closet/tac/PopulateContents()
..()
new /obj/item/gun/ballistic/automatic/wt550(src)
new /obj/item/clothing/head/helmet/alt(src)
new /obj/item/clothing/mask/gas/sechailer(src)
new /obj/item/clothing/suit/armor/bulletproof(src)
-
/obj/structure/closet/secure_closet/lethalshots
name = "shotgun lethal rounds"
req_access = list(ACCESS_ARMORY)
icon_state = "tac"
-
/obj/structure/closet/secure_closet/lethalshots/PopulateContents()
..()
for(var/i in 1 to 3)
diff --git a/code/game/objects/structures/crates_lockers/closets/syndicate.dm b/code/game/objects/structures/crates_lockers/closets/syndicate.dm
index f2d32b773e..94d1b03fdb 100644
--- a/code/game/objects/structures/crates_lockers/closets/syndicate.dm
+++ b/code/game/objects/structures/crates_lockers/closets/syndicate.dm
@@ -9,6 +9,7 @@
/obj/structure/closet/syndicate/personal/PopulateContents()
..()
new /obj/item/clothing/under/syndicate(src)
+ new /obj/item/clothing/under/syndicate/skirt(src)
new /obj/item/clothing/shoes/sneakers/black(src)
new /obj/item/radio/headset/syndicate(src)
new /obj/item/ammo_box/magazine/m10mm(src)
diff --git a/code/game/objects/structures/crates_lockers/closets/wardrobe.dm b/code/game/objects/structures/crates_lockers/closets/wardrobe.dm
index 7493603ad4..d83922d708 100644
--- a/code/game/objects/structures/crates_lockers/closets/wardrobe.dm
+++ b/code/game/objects/structures/crates_lockers/closets/wardrobe.dm
@@ -2,33 +2,34 @@
name = "wardrobe"
desc = "It's a storage unit for standard-issue Nanotrasen attire."
icon_door = "blue"
-
/obj/structure/closet/wardrobe/PopulateContents()
..()
for(var/i in 1 to 3)
new /obj/item/clothing/under/color/blue(src)
+ for(var/i in 1 to 3)
+ new /obj/item/clothing/under/skirt/color/blue(src)
for(var/i in 1 to 3)
new /obj/item/clothing/shoes/sneakers/brown(src)
return
-
/obj/structure/closet/wardrobe/pink
name = "pink wardrobe"
icon_door = "pink"
-
/obj/structure/closet/wardrobe/pink/PopulateContents()
for(var/i in 1 to 3)
new /obj/item/clothing/under/color/pink(src)
+ for(var/i in 1 to 3)
+ new /obj/item/clothing/under/skirt/color/pink(src)
for(var/i in 1 to 3)
new /obj/item/clothing/shoes/sneakers/brown(src)
return
-
/obj/structure/closet/wardrobe/black
name = "black wardrobe"
icon_door = "black"
-
/obj/structure/closet/wardrobe/black/PopulateContents()
for(var/i in 1 to 3)
new /obj/item/clothing/under/color/black(src)
+ for(var/i in 1 to 3)
+ new /obj/item/clothing/under/skirt/color/black(src)
if(prob(25))
new /obj/item/clothing/suit/jacket/leather(src)
if(prob(20))
@@ -44,66 +45,60 @@
if(prob(40))
new /obj/item/clothing/mask/bandana/skull(src)
return
-
-
/obj/structure/closet/wardrobe/green
name = "green wardrobe"
icon_door = "green"
-
/obj/structure/closet/wardrobe/green/PopulateContents()
for(var/i in 1 to 3)
new /obj/item/clothing/under/color/green(src)
+ for(var/i in 1 to 3)
+ new /obj/item/clothing/under/skirt/color/green(src)
for(var/i in 1 to 3)
new /obj/item/clothing/shoes/sneakers/black(src)
new /obj/item/clothing/mask/bandana/green(src)
new /obj/item/clothing/mask/bandana/green(src)
return
-
-
/obj/structure/closet/wardrobe/orange
name = "prison wardrobe"
desc = "It's a storage unit for Nanotrasen-regulation prisoner attire."
icon_door = "orange"
-
/obj/structure/closet/wardrobe/orange/PopulateContents()
for(var/i in 1 to 3)
new /obj/item/clothing/under/rank/prisoner(src)
+ for(var/i in 1 to 3)
+ new /obj/item/clothing/under/rank/prisoner/skirt(src)
for(var/i in 1 to 3)
new /obj/item/clothing/shoes/sneakers/orange(src)
return
-
-
/obj/structure/closet/wardrobe/yellow
name = "yellow wardrobe"
icon_door = "yellow"
-
/obj/structure/closet/wardrobe/yellow/PopulateContents()
for(var/i in 1 to 3)
new /obj/item/clothing/under/color/yellow(src)
+ for(var/i in 1 to 3)
+ new /obj/item/clothing/under/skirt/color/yellow(src)
for(var/i in 1 to 3)
new /obj/item/clothing/shoes/sneakers/orange(src)
new /obj/item/clothing/mask/bandana/gold(src)
new /obj/item/clothing/mask/bandana/gold(src)
return
-
-
/obj/structure/closet/wardrobe/white
name = "white wardrobe"
icon_door = "white"
-
/obj/structure/closet/wardrobe/white/PopulateContents()
for(var/i in 1 to 3)
new /obj/item/clothing/under/color/white(src)
+ for(var/i in 1 to 3)
+ new /obj/item/clothing/under/skirt/color/white(src)
for(var/i in 1 to 3)
new /obj/item/clothing/shoes/sneakers/white(src)
for(var/i in 1 to 3)
new /obj/item/clothing/head/soft/mime(src)
return
-
/obj/structure/closet/wardrobe/pjs
name = "pajama wardrobe"
icon_door = "white"
-
/obj/structure/closet/wardrobe/pjs/PopulateContents()
new /obj/item/clothing/under/pj/red(src)
new /obj/item/clothing/under/pj/red(src)
@@ -112,15 +107,14 @@
for(var/i in 1 to 4)
new /obj/item/clothing/shoes/sneakers/white(src)
return
-
-
/obj/structure/closet/wardrobe/grey
name = "grey wardrobe"
icon_door = "grey"
-
/obj/structure/closet/wardrobe/grey/PopulateContents()
for(var/i in 1 to 3)
new /obj/item/clothing/under/color/grey(src)
+ for(var/i in 1 to 3)
+ new /obj/item/clothing/under/skirt/color/grey(src)
for(var/i in 1 to 3)
new /obj/item/clothing/shoes/sneakers/black(src)
for(var/i in 1 to 3)
@@ -140,28 +134,36 @@
if(prob(30))
new /obj/item/clothing/accessory/pocketprotector(src)
return
-
-
/obj/structure/closet/wardrobe/mixed
name = "mixed wardrobe"
icon_door = "mixed"
-
/obj/structure/closet/wardrobe/mixed/PopulateContents()
if(prob(40))
new /obj/item/clothing/suit/jacket(src)
if(prob(40))
new /obj/item/clothing/suit/jacket(src)
new /obj/item/clothing/under/color/white(src)
+ new /obj/item/clothing/under/skirt/color/white(src)
new /obj/item/clothing/under/color/blue(src)
+ new /obj/item/clothing/under/skirt/color/blue(src)
new /obj/item/clothing/under/color/yellow(src)
+ new /obj/item/clothing/under/skirt/color/yellow(src)
new /obj/item/clothing/under/color/green(src)
+ new /obj/item/clothing/under/skirt/color/green(src)
new /obj/item/clothing/under/color/orange(src)
+ new /obj/item/clothing/under/skirt/color/orange(src)
new /obj/item/clothing/under/color/pink(src)
+ new /obj/item/clothing/under/skirt/color/pink(src)
new /obj/item/clothing/under/color/red(src)
+ new /obj/item/clothing/under/skirt/color/red(src)
new /obj/item/clothing/under/color/darkblue(src)
+ new /obj/item/clothing/under/skirt/color/darkblue(src)
new /obj/item/clothing/under/color/teal(src)
+ new /obj/item/clothing/under/skirt/color/teal(src)
new /obj/item/clothing/under/color/lightpurple(src)
+ new /obj/item/clothing/under/skirt/color/lightpurple(src)
new /obj/item/clothing/under/color/green(src)
+ new /obj/item/clothing/under/skirt/color/green(src)
new /obj/item/clothing/mask/bandana/red(src)
new /obj/item/clothing/mask/bandana/red(src)
new /obj/item/clothing/mask/bandana/blue(src)
diff --git a/code/game/objects/structures/crates_lockers/crates.dm b/code/game/objects/structures/crates_lockers/crates.dm
index 067b1b0eb1..23703c7891 100644
--- a/code/game/objects/structures/crates_lockers/crates.dm
+++ b/code/game/objects/structures/crates_lockers/crates.dm
@@ -54,6 +54,12 @@
manifest = null
update_icon()
+/obj/structure/closet/crate/handle_lock_addition()
+ return
+
+/obj/structure/closet/crate/handle_lock_removal()
+ return
+
/obj/structure/closet/crate/proc/tear_manifest(mob/user)
to_chat(user, "You tear the manifest off of [src].")
playsound(src, 'sound/items/poster_ripped.ogg', 75, 1)
@@ -93,6 +99,25 @@
name = "freezer"
icon_state = "freezer"
+//Snowflake organ freezer code
+//Order is important, since we check source, we need to do the check whenever we have all the organs in the crate
+
+/obj/structure/closet/crate/freezer/open()
+ recursive_organ_check(src)
+ ..()
+
+/obj/structure/closet/crate/freezer/close()
+ ..()
+ recursive_organ_check(src)
+
+/obj/structure/closet/crate/freezer/Destroy()
+ recursive_organ_check(src)
+ ..()
+
+/obj/structure/closet/crate/freezer/Initialize()
+ . = ..()
+ recursive_organ_check(src)
+
/obj/structure/closet/crate/freezer/blood
name = "blood freezer"
desc = "A freezer containing packs of blood."
diff --git a/code/game/objects/structures/dresser.dm b/code/game/objects/structures/dresser.dm
index 9e88d52444..05e62c196f 100644
--- a/code/game/objects/structures/dresser.dm
+++ b/code/game/objects/structures/dresser.dm
@@ -21,35 +21,62 @@
/obj/structure/dresser/attack_hand(mob/user)
. = ..()
- if(.)
+ if(. || !ishuman(user) || !user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK))
return
- if(!Adjacent(user))//no tele-grooming
+ var/mob/living/carbon/human/H = user
+
+ if(H.dna && H.dna.species && (NO_UNDERWEAR in H.dna.species.species_traits))
+ to_chat(H, "You are not capable of wearing underwear.")
return
- if(ishuman(user))
- var/mob/living/carbon/human/H = user
- if(H.dna && H.dna.species && (NO_UNDERWEAR in H.dna.species.species_traits))
- to_chat(user, "You are not capable of wearing underwear.")
- return
+ var/list/undergarment_choices = list("Underwear", "Underwear Color", "Undershirt", "Undershirt Color", "Socks", "Socks Color")
+ if(!UNDIE_COLORABLE(GLOB.underwear_list[H.underwear]))
+ undergarment_choices -= "Underwear Color"
+ if(!UNDIE_COLORABLE(GLOB.undershirt_list[H.undershirt]))
+ undergarment_choices -= "Undershirt Color"
+ if(!UNDIE_COLORABLE(GLOB.socks_list[H.socks]))
+ undergarment_choices -= "Socks Color"
- var/choice = input(user, "Underwear, Undershirt, or Socks?", "Changing") as null|anything in list("Underwear","Undershirt","Socks")
+ var/choice = input(H, "Underwear, Undershirt, or Socks?", "Changing") as null|anything in undergarment_choices
+ if(!H.canUseTopic(src, BE_CLOSE, FALSE, NO_TK))
+ return
+ var/dye_undie = FALSE
+ var/dye_shirt = FALSE
+ var/dye_socks = FALSE
+ switch(choice)
+ if("Underwear")
+ var/new_undies = input(H, "Select your underwear", "Changing") as null|anything in GLOB.underwear_list
+ if(H.underwear)
+ H.underwear = new_undies
+ H.saved_underwear = new_undies
+ var/datum/sprite_accessory/underwear/bottom/B = GLOB.underwear_list[new_undies]
+ dye_undie = B?.has_color
+ if("Undershirt")
+ var/new_undershirt = input(H, "Select your undershirt", "Changing") as null|anything in GLOB.undershirt_list
+ if(new_undershirt)
+ H.undershirt = new_undershirt
+ H.saved_undershirt = new_undershirt
+ var/datum/sprite_accessory/underwear/top/T = GLOB.undershirt_list[new_undershirt]
+ dye_shirt = T?.has_color
+ if("Socks")
+ var/new_socks = input(H, "Select your socks", "Changing") as null|anything in GLOB.socks_list
+ if(new_socks)
+ H.socks = new_socks
+ H.saved_socks = new_socks
+ var/datum/sprite_accessory/underwear/socks/S = GLOB.socks_list[new_socks]
+ dye_socks = S?.has_color
+ if(dye_undie || choice == "Underwear Color")
+ H.undie_color = recolor_undergarment(H, "underwear", H.undie_color)
+ if(dye_shirt || choice == "Undershirt Color")
+ H.shirt_color = recolor_undergarment(H, "undershirt", H.shirt_color)
+ if(dye_socks || choice == "Socks Color")
+ H.socks_color = recolor_undergarment(H, "socks", H.socks_color)
- if(!Adjacent(user))
- return
- switch(choice)
- if("Underwear")
- var/new_undies = input(user, "Select your underwear", "Changing") as null|anything in GLOB.underwear_list
- if(new_undies)
- H.underwear = new_undies
+ add_fingerprint(H)
+ H.update_body()
- if("Undershirt")
- var/new_undershirt = input(user, "Select your undershirt", "Changing") as null|anything in GLOB.undershirt_list
- if(new_undershirt)
- H.undershirt = new_undershirt
- if("Socks")
- var/new_socks = input(user, "Select your socks", "Changing") as null|anything in GLOB.socks_list
- if(new_socks)
- H.socks= new_socks
-
- add_fingerprint(H)
- H.update_body()
+/obj/structure/dresser/proc/recolor_undergarment(mob/living/carbon/human/H, garment_type = "underwear", default_color)
+ var/n_color = input(H, "Choose your [garment_type]'\s color.", "Character Preference", default_color) as color|null
+ if(!n_color || !H.canUseTopic(src, BE_CLOSE, FALSE, NO_TK))
+ return default_color
+ return sanitize_hexcolor(n_color, 3, FALSE, default_color)
diff --git a/code/game/objects/structures/femur_breaker.dm b/code/game/objects/structures/femur_breaker.dm
new file mode 100644
index 0000000000..e3002a8fae
--- /dev/null
+++ b/code/game/objects/structures/femur_breaker.dm
@@ -0,0 +1,175 @@
+#define BREAKER_ANIMATION_LENGTH 32
+#define BREAKER_SLAT_RAISED 1
+#define BREAKER_SLAT_MOVING 2
+#define BREAKER_SLAT_DROPPED 3
+#define BREAKER_ACTIVATE_DELAY 30
+#define BREAKER_WRENCH_DELAY 10
+#define BREAKER_ACTION_INUSE 5
+#define BREAKER_ACTION_WRENCH 6
+
+/obj/structure/femur_breaker
+ name = "femur breaker"
+ desc = "A large structure used to break the femurs of traitors and treasonists."
+ icon = 'icons/obj/femur_breaker.dmi'
+ icon_state = "breaker_raised"
+ can_buckle = TRUE
+ anchored = TRUE
+ density = TRUE
+ max_buckled_mobs = 1
+ buckle_lying = TRUE
+ buckle_prevents_pull = TRUE
+ layer = ABOVE_MOB_LAYER
+ var/slat_status = BREAKER_SLAT_RAISED
+ var/current_action = 0 // What's currently happening to the femur breaker
+
+/obj/structure/femur_breaker/examine(mob/user)
+ ..()
+
+ var/msg = ""
+
+ msg += "It is [anchored ? "secured to the floor." : "unsecured."] "
+
+ if (slat_status == BREAKER_SLAT_RAISED)
+ msg += "The breaker slat is in a neutral position."
+ else
+ msg += "The breaker slat is lowered, and must be raised."
+
+ if (LAZYLEN(buckled_mobs))
+ msg += " "
+ msg += "Someone appears to be strapped in. You can help them unbuckle, or activate the femur breaker."
+
+ to_chat(user, msg)
+
+ return msg
+
+/obj/structure/femur_breaker/attack_hand(mob/user)
+ add_fingerprint(user)
+
+ // Currently being used
+ if (current_action)
+ return
+
+ switch (slat_status)
+ if (BREAKER_SLAT_MOVING)
+ return
+ if (BREAKER_SLAT_DROPPED)
+ slat_status = BREAKER_SLAT_MOVING
+ icon_state = "breaker_raise"
+ addtimer(CALLBACK(src, .proc/raise_slat), BREAKER_ANIMATION_LENGTH)
+ return
+ if (BREAKER_SLAT_RAISED)
+ if (LAZYLEN(buckled_mobs))
+ if (user.a_intent == INTENT_HARM)
+ user.visible_message("[user] begins to pull the lever!",
+ "You begin to the pull the lever.")
+ current_action = BREAKER_ACTION_INUSE
+
+ if (do_after(user, BREAKER_ACTIVATE_DELAY, target = src) && slat_status == BREAKER_SLAT_RAISED)
+ current_action = 0
+ slat_status = BREAKER_SLAT_MOVING
+ icon_state = "breaker_drop"
+ drop_slat(user)
+ else
+ current_action = 0
+ else
+ var/mob/living/carbon/human/H = buckled_mobs[1]
+
+ if (H)
+ H.regenerate_icons()
+
+ unbuckle_all_mobs()
+ else //HERE
+ slat_status = BREAKER_SLAT_DROPPED
+ icon_state = "breaker_drop"
+
+/obj/structure/femur_breaker/proc/damage_leg(mob/living/carbon/human/H)
+ H.emote("scream")
+ H.apply_damage(150, BRUTE, pick(BODY_ZONE_L_LEG, BODY_ZONE_R_LEG))
+ H.adjustBruteLoss(rand(5,20) + (max(0, H.health))) //Make absolutely sure they end up in crit, so that they can succumb if they wish.
+
+/obj/structure/femur_breaker/proc/raise_slat()
+ slat_status = BREAKER_SLAT_RAISED
+ icon_state = "breaker_raised"
+
+/obj/structure/femur_breaker/proc/drop_slat(mob/user)
+ if (buckled_mobs.len)
+ var/mob/living/carbon/human/H = buckled_mobs[1]
+
+ if (!H)
+ return
+
+ playsound(src, 'sound/effects/femur_breaker.ogg', 100, FALSE)
+ H.Stun(BREAKER_ANIMATION_LENGTH)
+ addtimer(CALLBACK(src, .proc/damage_leg, H), BREAKER_ANIMATION_LENGTH, TIMER_UNIQUE)
+ log_combat(user, H, "femur broke", src)
+
+ slat_status = BREAKER_SLAT_DROPPED
+ icon_state = "breaker"
+
+/obj/structure/femur_breaker/buckle_mob(mob/living/M, force = FALSE, check_loc = TRUE)
+ if (!anchored)
+ to_chat(usr, "The [src] needs to be wrenched to the floor!")
+ return FALSE
+
+ if (!istype(M, /mob/living/carbon/human))
+ to_chat(usr, "It doesn't look like [M.p_they()] can fit into this properly!")
+ return FALSE
+
+ if (slat_status != BREAKER_SLAT_RAISED)
+ to_chat(usr, "The femur breaker must be in its neutral position before buckling someone in!")
+ return FALSE
+
+ return ..(M, force, FALSE)
+
+/obj/structure/femur_breaker/post_buckle_mob(mob/living/M)
+ if (!istype(M, /mob/living/carbon/human))
+ return
+
+ var/mob/living/carbon/human/H = M
+
+ if (H.dna)
+ if (H.dna.species)
+ var/datum/species/S = H.dna.species
+
+ if (!istype(S))
+ unbuckle_all_mobs()
+ else
+ unbuckle_all_mobs()
+ else
+ unbuckle_all_mobs()
+
+ ..()
+
+/obj/structure/femur_breaker/can_be_unfasten_wrench(mob/user, silent)
+ if (LAZYLEN(buckled_mobs))
+ if (!silent)
+ to_chat(user, "Can't unfasten, someone's strapped in!")
+ return FAILED_UNFASTEN
+
+ if (current_action)
+ return FAILED_UNFASTEN
+
+ return ..()
+
+/obj/structure/femur_breaker/wrench_act(mob/living/user, obj/item/I)
+ if (current_action)
+ return
+
+ current_action = BREAKER_ACTION_WRENCH
+
+ if (do_after(user, BREAKER_WRENCH_DELAY, target = src))
+ current_action = 0
+ default_unfasten_wrench(user, I, 0)
+ setDir(SOUTH)
+ return TRUE
+ else
+ current_action = 0
+
+#undef BREAKER_ANIMATION_LENGTH
+#undef BREAKER_SLAT_RAISED
+#undef BREAKER_SLAT_MOVING
+#undef BREAKER_SLAT_DROPPED
+#undef BREAKER_ACTIVATE_DELAY
+#undef BREAKER_WRENCH_DELAY
+#undef BREAKER_ACTION_INUSE
+#undef BREAKER_ACTION_WRENCH
diff --git a/code/game/objects/structures/ghost_role_spawners.dm b/code/game/objects/structures/ghost_role_spawners.dm
index 12827a0aba..b820e93c7b 100644
--- a/code/game/objects/structures/ghost_role_spawners.dm
+++ b/code/game/objects/structures/ghost_role_spawners.dm
@@ -45,7 +45,7 @@
death = FALSE
anchored = FALSE
density = FALSE
- flavour_text = "You are an ash walker. Your tribe worships the Necropolis. The wastes are sacred ground, its monsters a blessed bounty. \
+ flavour_text = "You are an ash walker. Your tribe worships the Necropolis. The wastes are sacred ground, its monsters a blessed bounty. You would never leave its beautiful expanse. \
You have seen lights in the distance... they foreshadow the arrival of outsiders that seek to tear apart the Necropolis and its domain. Fresh sacrifices for your nest."
assignedrole = "Ash Walker"
@@ -561,7 +561,7 @@
icon = 'icons/obj/machines/sleeper.dmi'
icon_state = "sleeper"
mob_name = "a space pirate"
- mob_species = /datum/species/skeleton/pirate
+ mob_species = /datum/species/skeleton/space
outfit = /datum/outfit/pirate/space
roundstart = FALSE
death = FALSE
diff --git a/code/game/objects/structures/girders.dm b/code/game/objects/structures/girders.dm
index cd87075258..31bf9318ce 100644
--- a/code/game/objects/structures/girders.dm
+++ b/code/game/objects/structures/girders.dm
@@ -4,7 +4,6 @@
desc = "A large structural assembly made out of metal; It requires a layer of metal before it can be considered a wall."
anchored = TRUE
density = TRUE
- layer = BELOW_OBJ_LAYER
var/state = GIRDER_NORMAL
var/girderpasschance = 20 // percentage chance that a projectile passes through the girder.
var/can_displace = TRUE //If the girder can be moved around by wrenching it
diff --git a/code/game/objects/structures/grille.dm b/code/game/objects/structures/grille.dm
index 6fac5da2a5..00d1690d86 100644
--- a/code/game/objects/structures/grille.dm
+++ b/code/game/objects/structures/grille.dm
@@ -262,7 +262,7 @@
if(isobj(AM))
if(prob(50) && anchored && !broken)
var/obj/O = AM
- if(O.throwforce != 0)//don't want to let people spam tesla bolts, this way it will break after time
+ if(O.throwforce != 0 && O.damtype != STAMINA)//don't want to let people spam tesla bolts, this way it will break after time
var/turf/T = get_turf(src)
var/obj/structure/cable/C = T.get_cable_node()
if(C)
diff --git a/code/game/objects/structures/loom.dm b/code/game/objects/structures/loom.dm
new file mode 100644
index 0000000000..c4e1968e59
--- /dev/null
+++ b/code/game/objects/structures/loom.dm
@@ -0,0 +1,21 @@
+//Loom, turns raw cotton and durathread into their respective fabrics.
+
+/obj/structure/loom
+ name = "loom"
+ desc = "A simple device used to weave cloth and other thread-based fabrics together into usable material."
+ icon = 'icons/obj/hydroponics/equipment.dmi'
+ icon_state = "loom"
+ density = TRUE
+ anchored = TRUE
+
+/obj/structure/loom/attackby(obj/item/stack/sheet/W, mob/user)
+ if(W.is_fabric && W.amount > 1)
+ user.show_message("You start weaving the [W.name] through the loom..", 1)
+ if(W.use_tool(src, user, W.pull_effort))
+ new W.loom_result(drop_location())
+ user.show_message("You weave the [W.name] into a workable fabric.", 1)
+ W.amount = (W.amount - 2)
+ if(W.amount < 1)
+ qdel(W)
+ else
+ user.show_message("You need a valid fabric and at least 2 of said fabric before using this.", 1)
\ No newline at end of file
diff --git a/code/game/objects/structures/manned_turret.dm b/code/game/objects/structures/manned_turret.dm
index aa55cd81b1..80ffced5a2 100644
--- a/code/game/objects/structures/manned_turret.dm
+++ b/code/game/objects/structures/manned_turret.dm
@@ -180,12 +180,13 @@
icon = 'icons/obj/items_and_weapons.dmi'
icon_state = "offhand"
w_class = WEIGHT_CLASS_HUGE
- item_flags = ABSTRACT | NODROP | NOBLUDGEON | DROPDEL
+ item_flags = ABSTRACT | NOBLUDGEON | DROPDEL
resistance_flags = FIRE_PROOF | UNACIDABLE | ACID_PROOF
var/obj/machinery/manned_turret/turret
/obj/item/gun_control/Initialize()
. = ..()
+ ADD_TRAIT(src, TRAIT_NODROP, ABSTRACT_ITEM_TRAIT)
turret = loc
if(!istype(turret))
return INITIALIZE_HINT_QDEL
diff --git a/code/game/objects/structures/mineral_doors.dm b/code/game/objects/structures/mineral_doors.dm
index 13ca421daa..5733ea123c 100644
--- a/code/game/objects/structures/mineral_doors.dm
+++ b/code/game/objects/structures/mineral_doors.dm
@@ -6,6 +6,7 @@
density = TRUE
anchored = TRUE
opacity = TRUE
+ layer = CLOSED_DOOR_LAYER
icon = 'icons/obj/doors/mineral_doors.dmi'
icon_state = "metal"
@@ -90,6 +91,7 @@
flick("[initial_state]opening",src)
sleep(10)
density = FALSE
+ layer = OPEN_DOOR_LAYER
state = 1
air_update_turf(1)
update_icon()
@@ -111,6 +113,7 @@
density = TRUE
set_opacity(TRUE)
state = 0
+ layer = initial(layer)
air_update_turf(1)
update_icon()
isSwitchingStates = 0
diff --git a/code/game/objects/structures/morgue.dm b/code/game/objects/structures/morgue.dm
index 09e5510d62..e7be30520e 100644
--- a/code/game/objects/structures/morgue.dm
+++ b/code/game/objects/structures/morgue.dm
@@ -31,6 +31,7 @@ GLOBAL_LIST_EMPTY(bodycontainers) //Let them act as spawnpoints for revenants an
/obj/structure/bodycontainer/Initialize()
. = ..()
GLOB.bodycontainers += src
+ recursive_organ_check(src)
/obj/structure/bodycontainer/Destroy()
GLOB.bodycontainers -= src
@@ -101,6 +102,7 @@ GLOBAL_LIST_EMPTY(bodycontainers) //Let them act as spawnpoints for revenants an
/obj/structure/bodycontainer/deconstruct(disassembled = TRUE)
new /obj/item/stack/sheet/metal (loc, 5)
+ recursive_organ_check(src)
qdel(src)
/obj/structure/bodycontainer/container_resist(mob/living/user)
@@ -120,6 +122,7 @@ GLOBAL_LIST_EMPTY(bodycontainers) //Let them act as spawnpoints for revenants an
open()
/obj/structure/bodycontainer/proc/open()
+ recursive_organ_check(src)
playsound(src.loc, 'sound/items/deconstruct.ogg', 50, 1)
playsound(src, 'sound/effects/roll.ogg', 5, 1)
var/turf/T = get_step(src, dir)
@@ -130,10 +133,13 @@ GLOBAL_LIST_EMPTY(bodycontainers) //Let them act as spawnpoints for revenants an
/obj/structure/bodycontainer/proc/close()
playsound(src, 'sound/effects/roll.ogg', 5, 1)
- playsound(src.loc, 'sound/items/deconstruct.ogg', 50, 1)
+ playsound(src, 'sound/items/deconstruct.ogg', 50, 1)
for(var/atom/movable/AM in connected.loc)
if(!AM.anchored || AM == connected)
+ if(ismob(AM) && !isliving(AM))
+ continue
AM.forceMove(src)
+ recursive_organ_check(src)
update_icon()
/obj/structure/bodycontainer/get_remote_view_fullscreens(mob/user)
@@ -301,7 +307,7 @@ GLOBAL_LIST_EMPTY(crematoriums)
/obj/structure/tray
icon = 'icons/obj/stationobjs.dmi'
density = TRUE
- layer = BELOW_OBJ_LAYER
+ layer = TRAY_LAYER
var/obj/structure/bodycontainer/connected = null
anchored = TRUE
pass_flags = LETPASSTHROW
diff --git a/code/game/objects/structures/reflector.dm b/code/game/objects/structures/reflector.dm
index 889cdab388..419502e2b0 100644
--- a/code/game/objects/structures/reflector.dm
+++ b/code/game/objects/structures/reflector.dm
@@ -5,7 +5,6 @@
desc = "A base for reflector assemblies."
anchored = FALSE
density = FALSE
- layer = BELOW_OBJ_LAYER
var/deflector_icon_state
var/image/deflector_overlay
var/finished = FALSE
diff --git a/code/game/objects/structures/table_frames.dm b/code/game/objects/structures/table_frames.dm
index e979d4f18e..f62bed878b 100644
--- a/code/game/objects/structures/table_frames.dm
+++ b/code/game/objects/structures/table_frames.dm
@@ -22,84 +22,22 @@
var/framestackamount = 2
/obj/structure/table_frame/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.loc, 'sound/items/deconstruct.ogg', 50, TRUE)
deconstruct(TRUE)
- else if(istype(I, /obj/item/stack/sheet/plasteel))
- var/obj/item/stack/sheet/plasteel/P = I
- if(P.get_amount() < 1)
- to_chat(user, "You need one plasteel sheet to do this!")
+ return
+
+ var/obj/item/stack/material = I
+ if (istype(I, /obj/item/stack) && material?.tableVariant)
+ if(material.get_amount() < 1)
+ to_chat(user, "You need one [material.name] sheet to do this!")
return
- to_chat(user, "You start adding [P] to [src]...")
- if(do_after(user, 50, target = src) && P.use(1))
- make_new_table(/obj/structure/table/reinforced)
- else if(istype(I, /obj/item/stack/sheet/metal))
- var/obj/item/stack/sheet/metal/M = I
- if(M.get_amount() < 1)
- to_chat(user, "You need one metal sheet to do this!")
- return
- to_chat(user, "You start adding [M] to [src]...")
- if(do_after(user, 20, target = src) && M.use(1))
- make_new_table(/obj/structure/table)
- else if(istype(I, /obj/item/stack/sheet/glass))
- var/obj/item/stack/sheet/glass/G = I
- if(G.get_amount() < 1)
- to_chat(user, "You need one glass sheet to do this!")
- return
- to_chat(user, "You start adding [G] to [src]...")
- if(do_after(user, 20, target = src) && G.use(1))
- make_new_table(/obj/structure/table/glass)
- else if(istype(I, /obj/item/stack/sheet/mineral/silver))
- var/obj/item/stack/sheet/mineral/silver/S = I
- if(S.get_amount() < 1)
- to_chat(user, "You need one silver sheet to do this!")
- return
- to_chat(user, "You start adding [S] to [src]...")
- if(do_after(user, 20, target = src) && S.use(1))
- make_new_table(/obj/structure/table/optable)
- else if(istype(I, /obj/item/stack/tile/carpet/black))
- var/obj/item/stack/tile/carpet/black/C = I
- if(C.get_amount() < 1)
- to_chat(user, "You need one black carpet sheet to do this!")
- return
- to_chat(user, "You start adding [C] to [src]...")
- if(do_after(user, 20, target = src) && C.use(1))
- make_new_table(/obj/structure/table/wood/fancy/black)
- else if(istype(I, /obj/item/stack/tile/carpet/blackred))
- var/obj/item/stack/tile/carpet/blackred/C = I
- if(C.get_amount() < 1)
- to_chat(user, "You need one red carpet sheet to do this!")
- return
- to_chat(user, "You start adding [C] to [src]...")
- if(do_after(user, 20, target = src) && C.use(1))
- make_new_table(/obj/structure/table/wood/fancy/blackred)
- else if(istype(I, /obj/item/stack/tile/carpet/monochrome))
- var/obj/item/stack/tile/carpet/monochrome/C = I
- if(C.get_amount() < 1)
- to_chat(user, "You need one monochrome carpet sheet to do this!")
- return
- to_chat(user, "You start adding [C] to [src]...")
- if(do_after(user, 20, target = src) && C.use(1))
- make_new_table(/obj/structure/table/wood/fancy/monochrome)
- else if(istype(I, /obj/item/stack/tile/carpet))
- var/obj/item/stack/tile/carpet/C = I
- if(C.get_amount() < 1)
- to_chat(user, "You need one carpet sheet to do this!")
- return
- to_chat(user, "You start adding [C] to [src]...")
- if(do_after(user, 20, target = src) && C.use(1))
- make_new_table(/obj/structure/table/wood/fancy)
- else if(istype(I, /obj/item/stack/tile/bronze))
- var/obj/item/stack/tile/bronze/B = I
- if(B.get_amount() < 1)
- to_chat(user, "You need one bronze sheet to do this!")
- return
- to_chat(user, "You start adding [B] to [src]...")
- if(do_after(user, 20, target = src) && B.use(1))
- make_new_table(/obj/structure/table/bronze)
+ to_chat(user, "You start adding [material] to [src]...")
+ if(do_after(user, 20, target = src) && material.use(1))
+ make_new_table(material.tableVariant)
else
return ..()
@@ -135,23 +73,21 @@
resistance_flags = FLAMMABLE
/obj/structure/table_frame/wood/attackby(obj/item/I, mob/user, params)
- if(istype(I, /obj/item/stack/sheet/mineral/wood))
- var/obj/item/stack/sheet/mineral/wood/W = I
- if(W.get_amount() < 1)
- to_chat(user, "You need one wood sheet to do this!")
- return
- to_chat(user, "You start adding [W] to [src]...")
- if(do_after(user, 20, target = src) && W.use(1))
- make_new_table(/obj/structure/table/wood)
- return
- else if(istype(I, /obj/item/stack/tile/carpet))
- var/obj/item/stack/tile/carpet/C = I
- if(C.get_amount() < 1)
- to_chat(user, "You need one carpet sheet to do this!")
- return
- to_chat(user, "You start adding [C] to [src]...")
- if(do_after(user, 20, target = src) && C.use(1))
- make_new_table(/obj/structure/table/wood/poker)
+ if (istype(I, /obj/item/stack))
+ var/obj/item/stack/material = I
+ var/toConstruct // stores the table variant
+ if(istype(I, /obj/item/stack/sheet/mineral/wood))
+ toConstruct = /obj/structure/table/wood
+ else if(istype(I, /obj/item/stack/tile/carpet))
+ toConstruct = /obj/structure/table/wood/poker
+
+ if (toConstruct)
+ if(material.get_amount() < 1)
+ to_chat(user, "You need one [material.name] sheet to do this!")
+ return
+ to_chat(user, "You start adding [material] to [src]...")
+ if(do_after(user, 20, target = src) && material.use(1))
+ make_new_table(toConstruct)
else
return ..()
diff --git a/code/game/objects/structures/tables_racks.dm b/code/game/objects/structures/tables_racks.dm
index 8c29c1b2c5..b12a26cd00 100644
--- a/code/game/objects/structures/tables_racks.dm
+++ b/code/game/objects/structures/tables_racks.dm
@@ -21,6 +21,7 @@
anchored = TRUE
layer = TABLE_LAYER
climbable = TRUE
+ obj_flags = CAN_BE_HIT|SHOVABLE_ONTO
pass_flags = LETPASSTHROW //You can throw objects over this, despite it's density.")
var/frame = /obj/structure/table_frame
var/framestack = /obj/item/stack/rods
@@ -115,6 +116,9 @@
log_combat(user, pushed_mob, "placed")
/obj/structure/table/proc/tablepush(mob/living/user, mob/living/pushed_mob)
+ if(HAS_TRAIT(user, TRAIT_PACIFISM))
+ to_chat(user, "Throwing [pushed_mob] onto the table might hurt them!")
+ return
var/added_passtable = FALSE
if(!pushed_mob.pass_flags & PASSTABLE)
added_passtable = TRUE
@@ -125,14 +129,23 @@
if(pushed_mob.loc != loc) //Something prevented the tabling
return
pushed_mob.Knockdown(40)
- pushed_mob.visible_message("[user] pushes [pushed_mob] onto [src].", \
- "[user] pushes [pushed_mob] onto [src].")
- log_combat(user, pushed_mob, "pushed")
+ pushed_mob.visible_message("[user] slams [pushed_mob] onto [src]!", \
+ "[user] slams you onto [src]!")
+ log_combat(user, pushed_mob, "tabled", null, "onto [src]")
if(!ishuman(pushed_mob))
return
var/mob/living/carbon/human/H = pushed_mob
SEND_SIGNAL(H, COMSIG_ADD_MOOD_EVENT, "table", /datum/mood_event/table)
+/obj/structure/table/shove_act(mob/living/target, mob/living/user)
+ if(!target.resting)
+ target.Knockdown(SHOVE_KNOCKDOWN_TABLE)
+ user.visible_message("[user.name] shoves [target.name] onto \the [src]!",
+ "You shove [target.name] onto \the [src]!", null, COMBAT_MESSAGE_RANGE)
+ target.forceMove(src.loc)
+ log_combat(user, target, "shoved", "onto [src] (table)")
+ return TRUE
+
/obj/structure/table/attackby(obj/item/I, mob/user, params)
if(!(flags_1 & NODECONSTRUCT_1))
if(istype(I, /obj/item/screwdriver) && deconstruction_ready)
@@ -258,6 +271,53 @@
for(var/obj/item/shard/S in debris)
S.color = NARSIE_WINDOW_COLOUR
+/*
+ * Plasmaglass tables
+ */
+/obj/structure/table/plasmaglass
+ name = "plasmaglass table"
+ desc = "A glasstable, but it's pink and more sturdy. What will Nanotrasen design next with plasma?"
+ icon = 'icons/obj/smooth_structures/plasmaglass_table.dmi'
+ icon_state = "plasmaglass_table"
+ climbable = TRUE
+ buildstack = /obj/item/stack/sheet/plasmaglass
+ canSmoothWith = null
+ max_integrity = 270
+ resistance_flags = ACID_PROOF
+ armor = list("melee" = 10, "bullet" = 5, "laser" = 0, "energy" = 0, "bomb" = 10, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 100)
+ var/list/debris = list()
+
+/obj/structure/table/plasmaglass/New()
+ . = ..()
+ debris += new frame
+ debris += new /obj/item/shard/plasma
+
+/obj/structure/table/plasmaglass/Destroy()
+ QDEL_LIST(debris)
+ . = ..()
+
+/obj/structure/table/plasmaglass/proc/check_break(mob/living/M)
+ return
+
+/obj/structure/table/plasmaglass/deconstruct(disassembled = TRUE, wrench_disassembly = 0)
+ if(!(flags_1 & NODECONSTRUCT_1))
+ if(disassembled)
+ ..()
+ return
+ else
+ var/turf/T = get_turf(src)
+ playsound(T, "shatter", 50, 1)
+ for(var/X in debris)
+ var/atom/movable/AM = X
+ AM.forceMove(T)
+ debris -= AM
+ qdel(src)
+
+/obj/structure/table/plasmaglass/narsie_act()
+ color = NARSIE_WINDOW_COLOUR
+ for(var/obj/item/shard/S in debris)
+ S.color = NARSIE_WINDOW_COLOUR
+
/*
* Wooden tables
*/
@@ -298,44 +358,83 @@
frame = /obj/structure/table_frame
framestack = /obj/item/stack/rods
buildstack = /obj/item/stack/tile/carpet
- canSmoothWith = list(/obj/structure/table/wood/fancy, /obj/structure/table/wood/fancy/black, /obj/structure/table/wood/fancy/blackred, /obj/structure/table/wood/fancy/monochrome)
+ canSmoothWith = list(/obj/structure/table/wood/fancy,
+ /obj/structure/table/wood/fancy/black,
+ /obj/structure/table/wood/fancy/blackred,
+ /obj/structure/table/wood/fancy/monochrome,
+ /obj/structure/table/wood/fancy/blue,
+ /obj/structure/table/wood/fancy/cyan,
+ /obj/structure/table/wood/fancy/green,
+ /obj/structure/table/wood/fancy/orange,
+ /obj/structure/table/wood/fancy/purple,
+ /obj/structure/table/wood/fancy/red,
+ /obj/structure/table/wood/fancy/royalblack,
+ /obj/structure/table/wood/fancy/royalblue)
+ var/smooth_icon = 'icons/obj/smooth_structures/fancy_table.dmi' // see Initialize()
-/obj/structure/table/wood/fancy/New()
- // New() is used so that the /black subtype can override `icon` easily and
- // the correct value will be used by the smoothing subsystem.
+/obj/structure/table/wood/fancy/Initialize()
. = ..()
// Needs to be set dynamically because table smooth sprites are 32x34,
// which the editor treats as a two-tile-tall object. The sprites are that
// size so that the north/south corners look nice - examine the detail on
// the sprites in the editor to see why.
- icon = 'icons/obj/smooth_structures/fancy_table.dmi'
+ icon = smooth_icon
/obj/structure/table/wood/fancy/black
icon_state = "fancy_table_black"
buildstack = /obj/item/stack/tile/carpet/black
+ smooth_icon = 'icons/obj/smooth_structures/fancy_table_black.dmi'
/obj/structure/table/wood/fancy/blackred
- icon = 'icons/obj/structures.dmi'
- icon_state = "fancy_table_blackred"
- buildstack = /obj/item/stack/tile/carpet/blackred
-
-/obj/structure/table/wood/fancy/blackred/New()
- . = ..()
- icon = 'icons/obj/smooth_structures/fancy_table_blackred.dmi'
+ icon_state = "fancy_table_blackred"
+ buildstack = /obj/item/stack/tile/carpet/blackred
+ smooth_icon = 'icons/obj/smooth_structures/fancy_table_blackred.dmi'
/obj/structure/table/wood/fancy/monochrome
- icon = 'icons/obj/structures.dmi'
- icon_state = "fancy_table_monochrome"
- buildstack = /obj/item/stack/tile/carpet/monochrome
+ icon_state = "fancy_table_monochrome"
+ buildstack = /obj/item/stack/tile/carpet/monochrome
+ smooth_icon = 'icons/obj/smooth_structures/fancy_table_monochrome.dmi'
-/obj/structure/table/wood/fancy/monochrome/New()
- . = ..()
- icon = 'icons/obj/smooth_structures/fancy_table_monochrome.dmi'
+/obj/structure/table/wood/fancy/blue
+ icon_state = "fancy_table_blue"
+ buildstack = /obj/item/stack/tile/carpet/blue
+ smooth_icon = 'icons/obj/smooth_structures/fancy_table_blue.dmi'
+
+/obj/structure/table/wood/fancy/cyan
+ icon_state = "fancy_table_cyan"
+ buildstack = /obj/item/stack/tile/carpet/cyan
+ smooth_icon = 'icons/obj/smooth_structures/fancy_table_cyan.dmi'
+
+/obj/structure/table/wood/fancy/green
+ icon_state = "fancy_table_green"
+ buildstack = /obj/item/stack/tile/carpet/green
+ smooth_icon = 'icons/obj/smooth_structures/fancy_table_green.dmi'
+
+/obj/structure/table/wood/fancy/orange
+ icon_state = "fancy_table_orange"
+ buildstack = /obj/item/stack/tile/carpet/orange
+ smooth_icon = 'icons/obj/smooth_structures/fancy_table_orange.dmi'
+
+/obj/structure/table/wood/fancy/purple
+ icon_state = "fancy_table_purple"
+ buildstack = /obj/item/stack/tile/carpet/purple
+ smooth_icon = 'icons/obj/smooth_structures/fancy_table_purple.dmi'
+
+/obj/structure/table/wood/fancy/red
+ icon_state = "fancy_table_red"
+ buildstack = /obj/item/stack/tile/carpet/red
+ smooth_icon = 'icons/obj/smooth_structures/fancy_table_red.dmi'
+
+/obj/structure/table/wood/fancy/royalblack
+ icon_state = "fancy_table_royalblack"
+ buildstack = /obj/item/stack/tile/carpet/royalblack
+ smooth_icon = 'icons/obj/smooth_structures/fancy_table_royalblack.dmi'
+
+/obj/structure/table/wood/fancy/royalblue
+ icon_state = "fancy_table_royalblue"
+ buildstack = /obj/item/stack/tile/carpet/royalblue
+ smooth_icon = 'icons/obj/smooth_structures/fancy_table_royalblue.dmi'
-/obj/structure/table/wood/fancy/black/New()
- . = ..()
- // Ditto above.
- icon = 'icons/obj/smooth_structures/fancy_table_black.dmi'
/*
* Reinforced tables
*/
diff --git a/code/game/objects/structures/transit_tubes/transit_tube_pod.dm b/code/game/objects/structures/transit_tubes/transit_tube_pod.dm
index ee46538be1..392c802ed8 100644
--- a/code/game/objects/structures/transit_tubes/transit_tube_pod.dm
+++ b/code/game/objects/structures/transit_tubes/transit_tube_pod.dm
@@ -4,7 +4,6 @@
animate_movement = FORWARD_STEPS
anchored = TRUE
density = TRUE
- layer = BELOW_OBJ_LAYER
var/moving = 0
var/datum/gas_mixture/air_contents = new()
diff --git a/code/game/objects/structures/watercloset.dm b/code/game/objects/structures/watercloset.dm
index d70838a30b..46db567b10 100644
--- a/code/game/objects/structures/watercloset.dm
+++ b/code/game/objects/structures/watercloset.dm
@@ -537,7 +537,7 @@
if(istype(O, /obj/item/stack/medical/gauze))
var/obj/item/stack/medical/gauze/G = O
- new /obj/item/reagent_containers/glass/rag(src.loc)
+ new /obj/item/reagent_containers/rag(src.loc)
to_chat(user, "You tear off a strip of gauze and make a rag.")
G.use(1)
return
diff --git a/code/game/objects/structures/window.dm b/code/game/objects/structures/window.dm
index a26d57e3e1..9fc055c2ba 100644
--- a/code/game/objects/structures/window.dm
+++ b/code/game/objects/structures/window.dm
@@ -281,6 +281,8 @@
. += new /obj/effect/decal/cleanable/glass(location)
if (reinf)
. += new /obj/item/stack/rods(location, (fulltile ? 2 : 1))
+ if (fulltile)
+ . += new /obj/item/shard(location)
/obj/structure/window/proc/can_be_rotated(mob/user,rotation_type)
if(anchored)
@@ -409,6 +411,15 @@
glass_type = /obj/item/stack/sheet/plasmaglass
rad_insulation = RAD_NO_INSULATION
+/obj/structure/window/plasma/spawnDebris(location)
+ . = list()
+ . += new /obj/item/shard/plasma(location)
+ . += new /obj/effect/decal/cleanable/glass/plasma(location)
+ if (reinf)
+ . += new /obj/item/stack/rods(location, (fulltile ? 2 : 1))
+ if (fulltile)
+ . += new /obj/item/shard/plasma(location)
+
/obj/structure/window/plasma/spawner/east
dir = EAST
diff --git a/code/game/say.dm b/code/game/say.dm
index 0788310038..4ce1d3c710 100644
--- a/code/game/say.dm
+++ b/code/game/say.dm
@@ -23,7 +23,7 @@ GLOBAL_LIST_INIT(freqtospan, list(
return
if(message == "" || !message)
return
- spans |= get_spans()
+ spans |= speech_span
if(!language)
language = get_default_language()
send_speech(message, 7, src, , spans, message_language=language)
@@ -40,10 +40,6 @@ GLOBAL_LIST_INIT(freqtospan, list(
var/atom/movable/AM = _AM
AM.Hear(rendered, src, message_language, message, , spans, message_mode)
-//To get robot span classes, stuff like that.
-/atom/movable/proc/get_spans()
- return list()
-
/atom/movable/proc/compose_message(atom/movable/speaker, datum/language/message_language, raw_message, radio_freq, list/spans, message_mode, face_name = FALSE)
//This proc uses text() because it is faster than appending strings. Thanks BYOND.
//Basic span
@@ -87,7 +83,7 @@ GLOBAL_LIST_INIT(freqtospan, list(
else
return verb_say
-/atom/movable/proc/say_quote(input, list/spans=list(), message_mode)
+/atom/movable/proc/say_quote(input, list/spans=list(speech_span), message_mode)
if(!input)
input = "..."
@@ -97,7 +93,7 @@ GLOBAL_LIST_INIT(freqtospan, list(
var/spanned = attach_spans(input, spans)
return "[say_mod(input, message_mode)][spanned ? ", \"[spanned]\"" : ""]"
// Citadel edit [spanned ? ", \"[spanned]\"" : ""]"
-
+
/atom/movable/proc/lang_treat(atom/movable/speaker, datum/language/language, raw_message, list/spans, message_mode)
if(has_language(language))
var/atom/movable/AM = speaker.GetSource()
diff --git a/code/game/sound.dm b/code/game/sound.dm
index 5503c6103d..e7562476a8 100644
--- a/code/game/sound.dm
+++ b/code/game/sound.dm
@@ -182,6 +182,8 @@
soundin = pick('sound/voice/beepsky/god.ogg', 'sound/voice/beepsky/iamthelaw.ogg', 'sound/voice/beepsky/secureday.ogg', 'sound/voice/beepsky/radio.ogg', 'sound/voice/beepsky/insult.ogg', 'sound/voice/beepsky/creep.ogg')
if("honkbot_e")
soundin = pick('sound/items/bikehorn.ogg', 'sound/items/AirHorn2.ogg', 'sound/misc/sadtrombone.ogg', 'sound/items/AirHorn.ogg', 'sound/effects/reee.ogg', 'sound/items/WEEOO1.ogg', 'sound/voice/beepsky/iamthelaw.ogg', 'sound/voice/beepsky/creep.ogg','sound/magic/Fireball.ogg' ,'sound/effects/pray.ogg', 'sound/voice/hiss1.ogg','sound/machines/buzz-sigh.ogg', 'sound/machines/ping.ogg', 'sound/weapons/flashbang.ogg', 'sound/weapons/bladeslice.ogg')
+ if("goose")
+ soundin = pick('sound/creatures/goose1.ogg', 'sound/creatures/goose2.ogg', 'sound/creatures/goose3.ogg', 'sound/creatures/goose4.ogg')
//START OF CIT CHANGES - adds random vore sounds
if ("struggle_sound")
soundin = pick( 'sound/vore/pred/struggle_01.ogg','sound/vore/pred/struggle_02.ogg','sound/vore/pred/struggle_03.ogg',
diff --git a/code/game/turfs/open.dm b/code/game/turfs/open.dm
index cff219c63e..f6d234b346 100644
--- a/code/game/turfs/open.dm
+++ b/code/game/turfs/open.dm
@@ -272,7 +272,7 @@
return 0
if(ishuman(C) && (lube&NO_SLIP_WHEN_WALKING))
var/mob/living/carbon/human/H = C
- if(!H.sprinting && H.getStaminaLoss() >= 20)
+ if(!H.sprinting && H.getStaminaLoss() <= 20)
return 0
if(!(lube&SLIDE_ICE))
to_chat(C, "You slipped[ O ? " on the [O.name]" : ""]!")
diff --git a/code/game/turfs/simulated/floor.dm b/code/game/turfs/simulated/floor.dm
index b00efc7ed6..194014b61a 100644
--- a/code/game/turfs/simulated/floor.dm
+++ b/code/game/turfs/simulated/floor.dm
@@ -236,6 +236,10 @@
return list("mode" = RCD_DECONSTRUCT, "delay" = 50, "cost" = 33)
if(RCD_WINDOWGRILLE)
return list("mode" = RCD_WINDOWGRILLE, "delay" = 10, "cost" = 4)
+ if(RCD_MACHINE)
+ return list("mode" = RCD_MACHINE, "delay" = 20, "cost" = 25)
+ if(RCD_COMPUTER)
+ return list("mode" = RCD_COMPUTER, "delay" = 20, "cost" = 25)
return FALSE
/turf/open/floor/rcd_act(mob/user, obj/item/construction/rcd/the_rcd, passed_mode)
@@ -274,4 +278,20 @@
var/obj/structure/grille/G = new(src)
G.anchored = TRUE
return TRUE
+ if(RCD_MACHINE)
+ if(locate(/obj/structure/frame/machine) in src)
+ return FALSE
+ var/obj/structure/frame/machine/M = new(src)
+ M.state = 2
+ M.icon_state = "box_1"
+ M.anchored = TRUE
+ return TRUE
+ if(RCD_COMPUTER)
+ if(locate(/obj/structure/frame/computer) in src)
+ return FALSE
+ var/obj/structure/frame/computer/C = new(src)
+ C.anchored = TRUE
+ C.setDir(the_rcd.computer_dir)
+ return TRUE
+
return FALSE
diff --git a/code/game/turfs/simulated/floor/fancy_floor.dm b/code/game/turfs/simulated/floor/fancy_floor.dm
index 7b45aa1fbb..ed6e279088 100644
--- a/code/game/turfs/simulated/floor/fancy_floor.dm
+++ b/code/game/turfs/simulated/floor/fancy_floor.dm
@@ -209,6 +209,46 @@
icon_state = "tile-carpet-monochrome"
canSmoothWith = list(/turf/open/floor/carpet/black, /turf/open/floor/carpet/blackred, /turf/open/floor/carpet/monochrome)
+/turf/open/floor/carpet/blue
+ icon = 'icons/turf/floors/carpet_blue.dmi'
+ floor_tile = /obj/item/stack/tile/carpet/blue
+ canSmoothWith = list(/turf/open/floor/carpet/blue)
+
+/turf/open/floor/carpet/cyan
+ icon = 'icons/turf/floors/carpet_cyan.dmi'
+ floor_tile = /obj/item/stack/tile/carpet/cyan
+ canSmoothWith = list(/turf/open/floor/carpet/cyan)
+
+/turf/open/floor/carpet/green
+ icon = 'icons/turf/floors/carpet_green.dmi'
+ floor_tile = /obj/item/stack/tile/carpet/green
+ canSmoothWith = list(/turf/open/floor/carpet/green)
+
+/turf/open/floor/carpet/orange
+ icon = 'icons/turf/floors/carpet_orange.dmi'
+ floor_tile = /obj/item/stack/tile/carpet/orange
+ canSmoothWith = list(/turf/open/floor/carpet/orange)
+
+/turf/open/floor/carpet/purple
+ icon = 'icons/turf/floors/carpet_purple.dmi'
+ floor_tile = /obj/item/stack/tile/carpet/purple
+ canSmoothWith = list(/turf/open/floor/carpet/purple)
+
+/turf/open/floor/carpet/red
+ icon = 'icons/turf/floors/carpet_red.dmi'
+ floor_tile = /obj/item/stack/tile/carpet/red
+ canSmoothWith = list(/turf/open/floor/carpet/red)
+
+/turf/open/floor/carpet/royalblack
+ icon = 'icons/turf/floors/carpet_royalblack.dmi'
+ floor_tile = /obj/item/stack/tile/carpet/royalblack
+ canSmoothWith = list(/turf/open/floor/carpet/royalblack)
+
+/turf/open/floor/carpet/royalblue
+ icon = 'icons/turf/floors/carpet_royalblue.dmi'
+ floor_tile = /obj/item/stack/tile/carpet/royalblue
+ canSmoothWith = list(/turf/open/floor/carpet/royalblue)
+
/turf/open/floor/carpet/narsie_act(force, ignore_mobs, probability = 20)
. = (prob(probability) || force)
for(var/I in src)
diff --git a/code/game/turfs/simulated/floor/misc_floor.dm b/code/game/turfs/simulated/floor/misc_floor.dm
index bff955086c..253a6ead90 100644
--- a/code/game/turfs/simulated/floor/misc_floor.dm
+++ b/code/game/turfs/simulated/floor/misc_floor.dm
@@ -144,6 +144,7 @@
barefootstep = FOOTSTEP_HARD_BAREFOOT
clawfootstep = FOOTSTEP_HARD_CLAW
heavyfootstep = FOOTSTEP_GENERIC_HEAVY
+ var/dropped_brass
var/uses_overlay = TRUE
var/obj/effect/clockwork/overlay/floor/realappearence
@@ -201,7 +202,10 @@
return
/turf/open/floor/clockwork/crowbar_act(mob/living/user, obj/item/I)
- if(baseturfs == type)
+ if(islist(baseturfs))
+ if(type in baseturfs)
+ return TRUE
+ else if(baseturfs == type)
return TRUE
user.visible_message("[user] begins slowly prying up [src]...", "You begin painstakingly prying up [src]...")
if(I.use_tool(src, user, 70, volume=80))
@@ -210,7 +214,14 @@
return TRUE
/turf/open/floor/clockwork/make_plating()
- new /obj/item/stack/tile/brass(src)
+ if(!dropped_brass)
+ new /obj/item/stack/tile/brass(src)
+ dropped_brass = TRUE
+ if(islist(baseturfs))
+ if(type in baseturfs)
+ return
+ else if(baseturfs == type)
+ return
return ..()
/turf/open/floor/clockwork/narsie_act()
diff --git a/code/game/turfs/simulated/wall/misc_walls.dm b/code/game/turfs/simulated/wall/misc_walls.dm
index dfc6972578..8b63d60939 100644
--- a/code/game/turfs/simulated/wall/misc_walls.dm
+++ b/code/game/turfs/simulated/wall/misc_walls.dm
@@ -7,6 +7,7 @@
smooth = SMOOTH_MORE
sheet_type = /obj/item/stack/sheet/runed_metal
sheet_amount = 1
+ explosion_block = 10
girder_type = /obj/structure/girder/cult
/turf/closed/wall/mineral/cult/Initialize()
@@ -49,7 +50,7 @@
/turf/closed/wall/clockwork
name = "clockwork wall"
desc = "A huge chunk of warm metal. The clanging of machinery emanates from within."
- explosion_block = 2
+ explosion_block = 5
hardness = 10
slicing_duration = 80
sheet_type = /obj/item/stack/tile/brass
diff --git a/code/game/turfs/turf.dm b/code/game/turfs/turf.dm
index 7110ff4405..ebf6f6626c 100755
--- a/code/game/turfs/turf.dm
+++ b/code/game/turfs/turf.dm
@@ -382,6 +382,19 @@
if(ismob(A) || .)
A.ratvar_act()
+//called on /datum/species/proc/altdisarm()
+/turf/shove_act(mob/living/target, mob/living/user, pre_act = FALSE)
+ var/list/possibilities
+ for(var/obj/O in contents)
+ if(CHECK_BITFIELD(O.obj_flags, SHOVABLE_ONTO))
+ LAZYADD(possibilities, O)
+ else if(!O.CanPass(target, src))
+ return FALSE
+ if(possibilities)
+ var/obj/O = pick(possibilities)
+ return O.shove_act(target, user)
+ return FALSE
+
/turf/proc/get_smooth_underlay_icon(mutable_appearance/underlay_appearance, turf/asking_turf, adjacency_dir)
underlay_appearance.icon = icon
underlay_appearance.icon_state = icon_state
@@ -452,16 +465,21 @@
/turf/AllowDrop()
return TRUE
-/turf/proc/add_vomit_floor(mob/living/carbon/M, toxvomit = NONE)
+/turf/proc/add_vomit_floor(mob/living/M, toxvomit = NONE)
+
var/obj/effect/decal/cleanable/vomit/V = new /obj/effect/decal/cleanable/vomit(src, M.get_static_viruses())
- // If the vomit combined, apply toxicity and reagents to the old vomit
+ //if the vomit combined, apply toxicity and reagents to the old vomit
if (QDELETED(V))
V = locate() in src
// Make toxins and blazaam vomit look different
if(toxvomit == VOMIT_PURPLE)
V.icon_state = "vomitpurp_[pick(1,4)]"
- else if(toxvomit == VOMIT_TOXIC)
+ else if (toxvomit == VOMIT_TOXIC)
V.icon_state = "vomittox_[pick(1,4)]"
+ if (iscarbon(M))
+ var/mob/living/carbon/C = M
+ if(C.reagents)
+ clear_reagents_to_vomit_pool(C,V)
/proc/clear_reagents_to_vomit_pool(mob/living/carbon/M, obj/effect/decal/cleanable/vomit/V)
M.reagents.trans_to(V, M.reagents.total_volume / 10)
@@ -474,4 +492,4 @@
//Whatever happens after high temperature fire dies out or thermite reaction works.
//Should return new turf
/turf/proc/Melt()
- return ScrapeAway()
\ No newline at end of file
+ return ScrapeAway()
diff --git a/code/game/world.dm b/code/game/world.dm
index dedf822597..25b8c4d9f3 100644
--- a/code/game/world.dm
+++ b/code/game/world.dm
@@ -100,6 +100,7 @@ GLOBAL_VAR(restart_counter)
GLOB.picture_log_directory = "data/picture_logs/[override_dir]"
GLOB.world_game_log = "[GLOB.log_directory]/game.log"
+ GLOB.world_virus_log = "[GLOB.log_directory]/virus.log"
GLOB.world_attack_log = "[GLOB.log_directory]/attack.log"
GLOB.world_pda_log = "[GLOB.log_directory]/pda.log"
GLOB.world_telecomms_log = "[GLOB.log_directory]/telecomms.log"
@@ -137,7 +138,7 @@ GLOBAL_VAR(restart_counter)
// but those are both private, so let's put the commit info in the runtime
// log which is ultimately public.
log_runtime(GLOB.revdata.get_log_message())
-
+
/world/Topic(T, addr, master, key)
TGS_TOPIC //redirect to server tools if necessary
@@ -270,7 +271,8 @@ GLOBAL_VAR(restart_counter)
if (M.client)
n++
- features += "[SSmapping.config.map_name]" //CIT CHANGE - makes the hub entry display the current map
+ if(SSmapping.config) // this just stops the runtime, honk.
+ features += "[SSmapping.config.map_name]" //CIT CHANGE - makes the hub entry display the current map
if(get_security_level())//CIT CHANGE - makes the hub entry show the security level
features += "[get_security_level()] alert"
diff --git a/code/modules/VR/vr_human.dm b/code/modules/VR/vr_human.dm
deleted file mode 100644
index 53a4bbe540..0000000000
--- a/code/modules/VR/vr_human.dm
+++ /dev/null
@@ -1,61 +0,0 @@
-/mob/living/carbon/human/virtual_reality
- var/datum/mind/real_mind // where is my mind t. pixies
- var/obj/machinery/vr_sleeper/vr_sleeper
- var/datum/action/quit_vr/quit_action
-
-/mob/living/carbon/human/virtual_reality/Initialize()
- . = ..()
- quit_action = new()
- quit_action.Grant(src)
-
-/mob/living/carbon/human/virtual_reality/death()
- revert_to_reality()
- ..()
-
-/mob/living/carbon/human/virtual_reality/Destroy()
- revert_to_reality()
- return ..()
-
-/mob/living/carbon/human/virtual_reality/Life()
- . = ..()
- if(real_mind)
- var/mob/living/real_me = real_mind.current
- if (real_me && real_me.stat == CONSCIOUS)
- return
- revert_to_reality(FALSE)
-
-/mob/living/carbon/human/virtual_reality/ghostize()
- stack_trace("Ghostize was called on a virtual reality mob")
-
-/mob/living/carbon/human/virtual_reality/ghost()
- set category = "OOC"
- set name = "Ghost"
- set desc = "Relinquish your life and enter the land of the dead."
- revert_to_reality(FALSE)
-
-/mob/living/carbon/human/virtual_reality/proc/revert_to_reality(deathchecks = TRUE)
- if(real_mind && mind)
- real_mind.current.audiovisual_redirect = null
- real_mind.current.ckey = ckey
- real_mind.current.stop_sound_channel(CHANNEL_HEARTBEAT)
- if(deathchecks && vr_sleeper)
- if(vr_sleeper.you_die_in_the_game_you_die_for_real)
- to_chat(real_mind, "You feel everything fading away...")
- real_mind.current.death(0)
- if(deathchecks && vr_sleeper)
- vr_sleeper.vr_human = null
- vr_sleeper = null
- real_mind = null
-
-/datum/action/quit_vr
- name = "Quit Virtual Reality"
- icon_icon = 'icons/mob/actions/actions_vr.dmi'
- button_icon_state = "logout"
-
-/datum/action/quit_vr/Trigger()
- if(..())
- if(istype(owner, /mob/living/carbon/human/virtual_reality))
- var/mob/living/carbon/human/virtual_reality/VR = owner
- VR.revert_to_reality(FALSE)
- else
- Remove(owner)
diff --git a/code/modules/VR/vr_mob.dm b/code/modules/VR/vr_mob.dm
new file mode 100644
index 0000000000..5c0cea9f60
--- /dev/null
+++ b/code/modules/VR/vr_mob.dm
@@ -0,0 +1,44 @@
+/mob/proc/build_virtual_character(mob/M)
+ mind_initialize()
+ if(!M)
+ return FALSE
+ name = M.name
+ real_name = M.real_name
+ mind.name = M.real_name
+ return TRUE
+
+/mob/living/carbon/build_virtual_character(mob/M)
+ . = ..()
+ if(!.)
+ return
+ if(iscarbon(M))
+ var/mob/living/carbon/C = M
+ C.dna?.transfer_identity(src)
+
+/mob/living/carbon/human/build_virtual_character(mob/M, datum/outfit/outfit)
+ . = ..()
+ if(!.)
+ return
+ var/mob/living/carbon/human/H
+ if(ishuman(M))
+ H = M
+ socks = H ? H.socks : random_socks()
+ socks_color = H ? H.socks_color : random_color()
+ undershirt = H ? H.undershirt : random_undershirt(M.gender)
+ shirt_color = H ? H.shirt_color : random_color()
+ underwear = H ? H.underwear : random_underwear(M.gender)
+ undie_color = H ? H.undie_color : random_color()
+ give_genitals(TRUE)
+ if(outfit)
+ var/datum/outfit/O = new outfit()
+ O.equip(src)
+
+/datum/action/quit_vr
+ name = "Quit Virtual Reality"
+ icon_icon = 'icons/mob/actions/actions_vr.dmi'
+ button_icon_state = "logout"
+
+/datum/action/quit_vr/Trigger() //this merely a trigger for /datum/component/virtual_reality
+ . = ..()
+ if(!.)
+ Remove(owner)
diff --git a/code/modules/VR/vr_sleeper.dm b/code/modules/VR/vr_sleeper.dm
index 4e342f6ced..72cbdc1409 100644
--- a/code/modules/VR/vr_sleeper.dm
+++ b/code/modules/VR/vr_sleeper.dm
@@ -1,5 +1,3 @@
-
-
//Glorified teleporter that puts you in a new human body.
// it's """VR"""
/obj/machinery/vr_sleeper
@@ -12,9 +10,10 @@
circuit = /obj/item/circuitboard/machine/vr_sleeper
var/you_die_in_the_game_you_die_for_real = FALSE
var/datum/effect_system/spark_spread/sparks
- var/mob/living/carbon/human/virtual_reality/vr_human
+ var/mob/living/vr_mob
+ var/virtual_mob_type = /mob/living/carbon/human
var/vr_category = "default" //Specific category of spawn points to pick from
- var/allow_creating_vr_humans = TRUE //So you can have vr_sleepers that always spawn you as a specific person or 1 life/chance vr games
+ var/allow_creating_vr_mobs = TRUE //So you can have vr_sleepers that always spawn you as a specific person or 1 life/chance vr games
var/only_current_user_can_interact = FALSE
/obj/machinery/vr_sleeper/Initialize()
@@ -44,7 +43,7 @@
/obj/machinery/vr_sleeper/Destroy()
open_machine()
- cleanup_vr_human()
+ cleanup_vr_mob()
QDEL_NULL(sparks)
return ..()
@@ -54,23 +53,27 @@
only_current_user_can_interact = TRUE
/obj/machinery/vr_sleeper/hugbox/emag_act(mob/user)
- return
+ return SEND_SIGNAL(src, COMSIG_ATOM_EMAG_ACT)
/obj/machinery/vr_sleeper/emag_act(mob/user)
+ . = ..()
+ if(!(obj_flags & EMAGGED))
+ return
+ obj_flags |= EMAGGED
you_die_in_the_game_you_die_for_real = TRUE
sparks.start()
addtimer(CALLBACK(src, .proc/emagNotify), 150)
+ return TRUE
/obj/machinery/vr_sleeper/update_icon()
icon_state = "[initial(icon_state)][state_open ? "-open" : ""]"
/obj/machinery/vr_sleeper/open_machine()
- if(!state_open)
- if(vr_human)
- vr_human.revert_to_reality(FALSE)
- if(occupant)
- SStgui.close_user_uis(occupant, src)
- ..()
+ if(state_open)
+ return
+ if(occupant)
+ SStgui.close_user_uis(occupant, src)
+ return ..()
/obj/machinery/vr_sleeper/MouseDrop_T(mob/target, mob/user)
if(user.stat || user.lying || !Adjacent(user) || !user.Adjacent(target) || !iscarbon(target) || !user.IsAdvancedToolUser())
@@ -90,22 +93,20 @@
if("vr_connect")
var/mob/living/carbon/human/human_occupant = occupant
if(human_occupant && human_occupant.mind && usr == occupant)
+
to_chat(occupant, "Transferring to virtual reality...")
- if(vr_human && vr_human.stat == CONSCIOUS && !vr_human.real_mind)
- SStgui.close_user_uis(occupant, src)
- human_occupant.audiovisual_redirect = vr_human
- vr_human.real_mind = human_occupant.mind
- vr_human.ckey = human_occupant.ckey
- to_chat(vr_human, "Transfer successful! You are now playing as [vr_human] in VR!")
+ if(vr_mob && (!istype(vr_mob) || !vr_mob.InCritical()) && !vr_mob.GetComponent(/datum/component/virtual_reality))
+ vr_mob.AddComponent(/datum/component/virtual_reality, human_occupant, src, you_die_in_the_game_you_die_for_real)
+ to_chat(vr_mob, "Transfer successful! You are now playing as [vr_mob] in VR!")
else
- if(allow_creating_vr_humans)
+ if(allow_creating_vr_mobs)
to_chat(occupant, "Virtual avatar not found, attempting to create one...")
var/obj/effect/landmark/vr_spawn/V = get_vr_spawnpoint()
var/turf/T = get_turf(V)
if(T)
SStgui.close_user_uis(occupant, src)
- build_virtual_human(occupant, T, V.vr_outfit)
- to_chat(vr_human, "Transfer successful! You are now playing as [vr_human] in VR!")
+ new_player(occupant, T, V.vr_outfit)
+ to_chat(vr_mob, "Transfer successful! You are now playing as [vr_mob] in VR!")
else
to_chat(occupant, "Virtual world misconfigured, aborting transfer")
else
@@ -113,8 +114,8 @@
. = TRUE
if("delete_avatar")
if(!occupant || usr == occupant)
- if(vr_human)
- cleanup_vr_human()
+ if(vr_mob)
+ cleanup_vr_mob()
else
to_chat(usr, "The VR Sleeper's safeties prevent you from doing that.")
. = TRUE
@@ -127,19 +128,22 @@
/obj/machinery/vr_sleeper/ui_data(mob/user)
var/list/data = list()
- if(vr_human && !QDELETED(vr_human))
+ if(vr_mob && !QDELETED(vr_mob))
data["can_delete_avatar"] = TRUE
- var/status
- switch(vr_human.stat)
- if(CONSCIOUS)
- status = "Conscious"
- if(DEAD)
- status = "Dead"
- if(UNCONSCIOUS)
- status = "Unconscious"
- if(SOFT_CRIT)
- status = "Barely Conscious"
- data["vr_avatar"] = list("name" = vr_human.name, "status" = status, "health" = vr_human.health, "maxhealth" = vr_human.maxHealth)
+ data["vr_avatar"] = list("name" = vr_mob.name)
+ data["isliving"] = istype(vr_mob)
+ if(data["isliving"])
+ var/status
+ switch(vr_mob.stat)
+ if(CONSCIOUS)
+ status = "Conscious"
+ if(DEAD)
+ status = "Dead"
+ if(UNCONSCIOUS)
+ status = "Unconscious"
+ if(SOFT_CRIT)
+ status = "Barely Conscious"
+ data["vr_avatar"] += list("status" = status, "health" = vr_mob.health, "maxhealth" = vr_mob.maxHealth)
data["toggle_open"] = state_open
data["emagged"] = you_die_in_the_game_you_die_for_real
data["isoccupant"] = (user == occupant)
@@ -153,37 +157,25 @@
for(var/obj/effect/landmark/vr_spawn/V in GLOB.landmarks_list)
GLOB.vr_spawnpoints[V.vr_category] = V
-/obj/machinery/vr_sleeper/proc/build_virtual_human(mob/living/carbon/human/H, location, var/datum/outfit/outfit, transfer = TRUE)
- if(H)
- cleanup_vr_human()
- vr_human = new /mob/living/carbon/human/virtual_reality(location)
- vr_human.mind_initialize()
- vr_human.vr_sleeper = src
- vr_human.real_mind = H.mind
- H.dna.transfer_identity(vr_human)
- vr_human.name = H.name
- vr_human.real_name = H.real_name
- vr_human.socks = H.socks
- vr_human.undershirt = H.undershirt
- vr_human.underwear = H.underwear
- vr_human.updateappearance(TRUE, TRUE, TRUE)
- vr_human.give_genitals(TRUE) //CITADEL ADD
- if(outfit)
- var/datum/outfit/O = new outfit()
- O.equip(vr_human)
- if(transfer && H.mind)
- SStgui.close_user_uis(H, src)
- H.audiovisual_redirect = vr_human
- vr_human.ckey = H.ckey
+/obj/machinery/vr_sleeper/proc/new_player(mob/living/carbon/human/H, location, datum/outfit/outfit, transfer = TRUE)
+ if(!H)
+ return
+ cleanup_vr_mob()
+ vr_mob = new virtual_mob_type(location)
+ if(vr_mob.build_virtual_character(H, outfit))
+ var/mob/living/carbon/human/vr_H = vr_mob
+ vr_H.updateappearance(TRUE, TRUE, TRUE)
+ if(!transfer || !H.mind)
+ return
+ vr_mob.AddComponent(/datum/component/virtual_reality, H, src, you_die_in_the_game_you_die_for_real)
-/obj/machinery/vr_sleeper/proc/cleanup_vr_human()
- if(vr_human)
- vr_human.vr_sleeper = null // Prevents race condition where a new human could get created out of order and set to null.
- QDEL_NULL(vr_human)
+/obj/machinery/vr_sleeper/proc/cleanup_vr_mob()
+ if(vr_mob)
+ QDEL_NULL(vr_mob)
/obj/machinery/vr_sleeper/proc/emagNotify()
- if(vr_human)
- vr_human.Dizzy(10)
+ if(vr_mob)
+ vr_mob.Dizzy(10)
/obj/effect/landmark/vr_spawn //places you can spawn in VR, auto selected by the vr_sleeper during get_vr_spawnpoint()
var/vr_category = "default" //So we can have specific sleepers, eg: "Basketball VR Sleeper", etc.
@@ -215,6 +207,7 @@
color = "#00FF00"
invisibility = INVISIBILITY_ABSTRACT
var/area/vr_area
+ var/list/corpse_party
/obj/effect/vr_clean_master/Initialize()
. = ..()
@@ -227,7 +220,8 @@
qdel(casing)
for(var/obj/effect/decal/cleanable/C in vr_area)
qdel(C)
- for (var/mob/living/carbon/human/virtual_reality/H in vr_area)
- if (H.stat == DEAD && !H.vr_sleeper && !H.real_mind)
- qdel(H)
+ for (var/A in corpse_party)
+ var/mob/M = A
+ if(get_area(M) == vr_area && M.stat == DEAD)
+ qdel(M)
addtimer(CALLBACK(src, .proc/clean_up), 3 MINUTES)
diff --git a/code/modules/admin/admin.dm b/code/modules/admin/admin.dm
index b400f44b98..555c35980d 100644
--- a/code/modules/admin/admin.dm
+++ b/code/modules/admin/admin.dm
@@ -423,6 +423,25 @@
if(GLOB.master_mode == "secret")
dat += "(Force Secret Mode) "
+ if(GLOB.master_mode == "dynamic")
+ if(SSticker.current_state <= GAME_STATE_PREGAME)
+ dat += "(Force Roundstart Rulesets) "
+ if (GLOB.dynamic_forced_roundstart_ruleset.len > 0)
+ for(var/datum/dynamic_ruleset/roundstart/rule in GLOB.dynamic_forced_roundstart_ruleset)
+ dat += {"-> [rule.name] <- "}
+ dat += "(Clear Rulesets) "
+ dat += "(Dynamic mode options) "
+ else if (SSticker.IsRoundInProgress())
+ dat += "(Force Next Latejoin Ruleset) "
+ if (SSticker && SSticker.mode && istype(SSticker.mode,/datum/game_mode/dynamic))
+ var/datum/game_mode/dynamic/mode = SSticker.mode
+ if (mode.forced_latejoin_rule)
+ dat += {"-> [mode.forced_latejoin_rule.name] <- "}
+ dat += "(Execute Midround Ruleset!) "
+ dat += ""
+ if(SSticker.IsRoundInProgress())
+ dat += "(Game Mode Panel) "
+
dat += {"
Create Object
@@ -839,6 +858,44 @@
browser.set_content(dat.Join())
browser.open()
+/datum/admins/proc/dynamic_mode_options(mob/user)
+ var/dat = {"
+
Dynamic Mode Options
+
+
Common options
+ All these options can be changed midround.
+
+ Force extended: - Option is [GLOB.dynamic_forced_extended ? "ON" : "OFF"].
+ This will force the round to be extended. No rulesets will be drafted.
+
+ No stacking: - Option is [GLOB.dynamic_no_stacking ? "ON" : "OFF"].
+ Unless the threat goes above [GLOB.dynamic_stacking_limit], only one "round-ender" ruleset will be drafted.
+
+ Classic secret mode: - Option is [GLOB.dynamic_classic_secret ? "ON" : "OFF"].
+ Only one roundstart ruleset will be drafted. Only traitors and minor roles will latespawn.
+
+
+ Forced threat level: Current value : [GLOB.dynamic_forced_threat_level].
+ The value threat is set to if it is higher than -1.
+
+ High population limit: Current value : [GLOB.dynamic_high_pop_limit].
+ The threshold at which "high population override" will be in effect.
+
+ Stacking threeshold: Current value : [GLOB.dynamic_stacking_limit].
+ The threshold at which "round-ender" rulesets will stack. A value higher than 100 ensure this never happens.
+
Advanced parameters
+ Curve centre: -> [GLOB.dynamic_curve_centre] <-
+ Curve width: -> [GLOB.dynamic_curve_width] <-
+ Latejoin injection delay:
+ Minimum: -> [GLOB.dynamic_latejoin_delay_min / 60 / 10] <- Minutes
+ Maximum: -> [GLOB.dynamic_latejoin_delay_max / 60 / 10] <- Minutes
+ Midround injection delay:
+ Minimum: -> [GLOB.dynamic_midround_delay_min / 60 / 10] <- Minutes
+ Maximum: -> [GLOB.dynamic_midround_delay_max / 60 / 10] <- Minutes
+ "}
+
+ user << browse(dat, "window=dyn_mode_options;size=900x650")
+
/datum/admins/proc/create_or_modify_area()
set category = "Debug"
set name = "Create or modify area"
diff --git a/code/modules/admin/admin_investigate.dm b/code/modules/admin/admin_investigate.dm
index 8e1926115f..ae6482abbd 100644
--- a/code/modules/admin/admin_investigate.dm
+++ b/code/modules/admin/admin_investigate.dm
@@ -4,7 +4,7 @@
var/F = file("[GLOB.log_directory]/[subject].html")
WRITE_FILE(F, "[TIME_STAMP("hh:mm:ss", FALSE)] [REF(src)] ([x],[y],[z]) || [src] [message] ")
-/client/proc/investigate_show(subject in list("notes, memos, watchlist", INVESTIGATE_RESEARCH, INVESTIGATE_EXONET, INVESTIGATE_PORTAL, INVESTIGATE_SINGULO, INVESTIGATE_WIRES, INVESTIGATE_TELESCI, INVESTIGATE_GRAVITY, INVESTIGATE_RECORDS, INVESTIGATE_CARGO, INVESTIGATE_SUPERMATTER, INVESTIGATE_ATMOS, INVESTIGATE_EXPERIMENTOR, INVESTIGATE_BOTANY, INVESTIGATE_HALLUCINATIONS, INVESTIGATE_RADIATION, INVESTIGATE_CIRCUIT, INVESTIGATE_NANITES) )
+/client/proc/investigate_show(subject in list("notes, memos, watchlist", INVESTIGATE_RCD, INVESTIGATE_RESEARCH, INVESTIGATE_EXONET, INVESTIGATE_PORTAL, INVESTIGATE_SINGULO, INVESTIGATE_WIRES, INVESTIGATE_TELESCI, INVESTIGATE_GRAVITY, INVESTIGATE_RECORDS, INVESTIGATE_CARGO, INVESTIGATE_SUPERMATTER, INVESTIGATE_ATMOS, INVESTIGATE_EXPERIMENTOR, INVESTIGATE_BOTANY, INVESTIGATE_HALLUCINATIONS, INVESTIGATE_RADIATION, INVESTIGATE_CIRCUIT, INVESTIGATE_NANITES) )
set name = "Investigate"
set category = "Admin"
if(!holder)
diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm
index ac1ae51f69..626fa7f66f 100644
--- a/code/modules/admin/admin_verbs.dm
+++ b/code/modules/admin/admin_verbs.dm
@@ -169,6 +169,7 @@ GLOBAL_LIST_INIT(admin_verbs_debug, world.AVerbsDebug())
/client/proc/cmd_display_overlay_log,
/client/proc/reload_configuration,
/datum/admins/proc/create_or_modify_area,
+ /client/proc/generate_wikichem_list //DO NOT PRESS UNLESS YOU WANT SUPERLAG
)
GLOBAL_PROTECT(admin_verbs_possess)
GLOBAL_LIST_INIT(admin_verbs_possess, list(/proc/possess, /proc/release))
diff --git a/code/modules/admin/chat_commands.dm b/code/modules/admin/chat_commands.dm
index a97c0bf116..8b824f8fb0 100644
--- a/code/modules/admin/chat_commands.dm
+++ b/code/modules/admin/chat_commands.dm
@@ -78,13 +78,13 @@ GLOBAL_LIST(round_end_notifiees)
/datum/tgs_chat_command/notify
name = "notify"
help_text = "Pings the invoker when the round ends"
- admin_only = TRUE
+ admin_only = FALSE
/datum/tgs_chat_command/notify/Run(datum/tgs_chat_user/sender, params)
if(!SSticker.IsRoundInProgress() && SSticker.HasRoundStarted())
return "[sender.mention], the round has already ended!"
LAZYINITLIST(GLOB.round_end_notifiees)
- GLOB.round_end_notifiees[sender.mention] = TRUE
+ GLOB.round_end_notifiees["<@[sender.mention]>"] = TRUE
return "I will notify [sender.mention] when the round ends."
/datum/tgs_chat_command/sdql
@@ -140,4 +140,4 @@ GLOBAL_LIST(round_end_notifiees)
log_admin("[sender.friendly_name] has added [params] to the current round's bunker bypass list.")
message_admins("[sender.friendly_name] has added [params] to the current round's bunker bypass list.")
- return "[params] has been added to the current round's bunker bypass list."
\ No newline at end of file
+ return "[params] has been added to the current round's bunker bypass list."
diff --git a/code/modules/admin/create_mob.dm b/code/modules/admin/create_mob.dm
index 73b8fa61f0..cff7faadd8 100644
--- a/code/modules/admin/create_mob.dm
+++ b/code/modules/admin/create_mob.dm
@@ -15,6 +15,9 @@
H.real_name = random_unique_name(H.gender)
H.name = H.real_name
H.underwear = random_underwear(H.gender)
+ H.undie_color = random_short_color()
+ H.undershirt = random_undershirt(H.gender)
+ H.shirt_color = random_short_color()
H.skin_tone = random_skin_tone()
H.hair_style = random_hair_style(H.gender)
H.facial_hair_style = random_facial_hair_style(H.gender)
@@ -26,13 +29,15 @@
// Mutant randomizing, doesn't affect the mob appearance unless it's the specific mutant.
H.dna.features["mcolor"] = random_short_color()
H.dna.features["tail_lizard"] = pick(GLOB.tails_list_lizard)
- H.dna.features["snout"] = pick(GLOB.snouts_list)
- H.dna.features["horns"] = pick(GLOB.horns_list)
+ H.dna.features["snout"] = pick(GLOB.snouts_list)
+ H.dna.features["horns"] = pick(GLOB.horns_list)
H.dna.features["frills"] = pick(GLOB.frills_list)
H.dna.features["spines"] = pick(GLOB.spines_list)
H.dna.features["body_markings"] = pick(GLOB.body_markings_list)
- H.dna.features["moth_wings"] = pick(GLOB.moth_wings_list)
+ H.dna.features["insect_wings"] = pick(GLOB.insect_wings_list)
+ H.dna.features["deco_wings"] = pick(GLOB.deco_wings_list)
+ H.dna.features["insect_fluff"] = pick(GLOB.insect_fluffs_list)
H.update_body()
H.update_hair()
- H.update_body_parts()
\ No newline at end of file
+ H.update_body_parts()
diff --git a/code/modules/admin/fun_balloon.dm b/code/modules/admin/fun_balloon.dm
index da811a1974..81050e6eae 100644
--- a/code/modules/admin/fun_balloon.dm
+++ b/code/modules/admin/fun_balloon.dm
@@ -61,7 +61,7 @@
to_chat(body, "Your mob has been taken over by a ghost!")
message_admins("[key_name_admin(C)] has taken control of ([key_name_admin(body)])")
body.ghostize(0)
- body.key = C.key
+ C.transfer_ckey(body)
new /obj/effect/temp_visual/gravpush(get_turf(body))
/obj/effect/fun_balloon/sentience/emergency_shuttle
diff --git a/code/modules/admin/secrets.dm b/code/modules/admin/secrets.dm
index 66358dc080..58fd627c74 100644
--- a/code/modules/admin/secrets.dm
+++ b/code/modules/admin/secrets.dm
@@ -422,7 +422,7 @@
H.equip_to_slot_or_del(I, SLOT_W_UNIFORM)
qdel(olduniform)
if(droptype == "Yes")
- I.item_flags |= NODROP
+ ADD_TRAIT(I, TRAIT_NODROP, ADMIN_TRAIT)
else
to_chat(H, "You're not kawaii enough for this.")
@@ -458,7 +458,7 @@
SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Mass Braindamage"))
for(var/mob/living/carbon/human/H in GLOB.player_list)
to_chat(H, "You suddenly feel stupid.")
- H.adjustBrainLoss(60, 80)
+ H.adjustOrganLoss(ORGAN_SLOT_BRAIN, 60, 80)
message_admins("[key_name_admin(usr)] made everybody retarded")
if("eagles")//SCRAW
@@ -744,7 +744,7 @@
var/mob/chosen = players[1]
if (chosen.client)
chosen.client.prefs.copy_to(spawnedMob)
- spawnedMob.key = chosen.key
+ chosen.transfer_ckey(spawnedMob)
players -= chosen
if (ishuman(spawnedMob) && ispath(humanoutfit, /datum/outfit))
var/mob/living/carbon/human/H = spawnedMob
diff --git a/code/modules/admin/topic.dm b/code/modules/admin/topic.dm
index 6fa118ab7f..b0b9190556 100644
--- a/code/modules/admin/topic.dm
+++ b/code/modules/admin/topic.dm
@@ -291,6 +291,11 @@
else if(href_list["editrights"])
edit_rights_topic(href_list)
+ else if(href_list["gamemode_panel"])
+ if(!check_rights(R_ADMIN))
+ return
+ SSticker.mode.admin_panel()
+
else if(href_list["call_shuttle"])
if(!check_rights(R_ADMIN))
return
@@ -1342,6 +1347,291 @@
else if(href_list["f_secret"])
return HandleFSecret()
+
+ else if(href_list["f_dynamic_roundstart"])
+ if(!check_rights(R_ADMIN))
+ return
+ if(SSticker && SSticker.mode)
+ return alert(usr, "The game has already started.", null, null, null, null)
+ if(GLOB.master_mode != "dynamic")
+ return alert(usr, "The game mode has to be dynamic mode.", null, null, null, null)
+ var/roundstart_rules = list()
+ for (var/rule in subtypesof(/datum/dynamic_ruleset/roundstart))
+ var/datum/dynamic_ruleset/roundstart/newrule = new rule()
+ roundstart_rules[newrule.name] = newrule
+ var/added_rule = input(usr,"What ruleset do you want to force? This will bypass threat level and population restrictions.", "Rigging Roundstart", null) as null|anything in roundstart_rules
+ if (added_rule)
+ GLOB.dynamic_forced_roundstart_ruleset += roundstart_rules[added_rule]
+ log_admin("[key_name(usr)] set [added_rule] to be a forced roundstart ruleset.")
+ message_admins("[key_name(usr)] set [added_rule] to be a forced roundstart ruleset.", 1)
+ Game()
+
+ else if(href_list["f_dynamic_roundstart_clear"])
+ if(!check_rights(R_ADMIN))
+ return
+ GLOB.dynamic_forced_roundstart_ruleset = list()
+ Game()
+ log_admin("[key_name(usr)] cleared the rigged roundstart rulesets. The mode will pick them as normal.")
+ message_admins("[key_name(usr)] cleared the rigged roundstart rulesets. The mode will pick them as normal.", 1)
+
+ else if(href_list["f_dynamic_roundstart_remove"])
+ if(!check_rights(R_ADMIN))
+ return
+ var/datum/dynamic_ruleset/roundstart/rule = locate(href_list["f_dynamic_roundstart_remove"])
+ GLOB.dynamic_forced_roundstart_ruleset -= rule
+ Game()
+ log_admin("[key_name(usr)] removed [rule] from the forced roundstart rulesets.")
+ message_admins("[key_name(usr)] removed [rule] from the forced roundstart rulesets.", 1)
+
+ else if(href_list["f_dynamic_latejoin"])
+ if(!check_rights(R_ADMIN))
+ return
+ if(!SSticker || !SSticker.mode)
+ return alert(usr, "The game must start first.", null, null, null, null)
+ if(GLOB.master_mode != "dynamic")
+ return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null)
+ var/latejoin_rules = list()
+ for (var/rule in subtypesof(/datum/dynamic_ruleset/latejoin))
+ var/datum/dynamic_ruleset/latejoin/newrule = new rule()
+ latejoin_rules[newrule.name] = newrule
+ var/added_rule = input(usr,"What ruleset do you want to force upon the next latejoiner? This will bypass threat level and population restrictions.", "Rigging Latejoin", null) as null|anything in latejoin_rules
+ if (added_rule)
+ var/datum/game_mode/dynamic/mode = SSticker.mode
+ mode.forced_latejoin_rule = latejoin_rules[added_rule]
+ log_admin("[key_name(usr)] set [added_rule] to proc on the next latejoin.")
+ message_admins("[key_name(usr)] set [added_rule] to proc on the next latejoin.", 1)
+ Game()
+
+ else if(href_list["f_dynamic_latejoin_clear"])
+ if(!check_rights(R_ADMIN))
+ return
+ if (SSticker && SSticker.mode && istype(SSticker.mode,/datum/game_mode/dynamic))
+ var/datum/game_mode/dynamic/mode = SSticker.mode
+ mode.forced_latejoin_rule = null
+ Game()
+ log_admin("[key_name(usr)] cleared the forced latejoin ruleset.")
+ message_admins("[key_name(usr)] cleared the forced latejoin ruleset.", 1)
+
+ else if(href_list["f_dynamic_midround"])
+ if(!check_rights(R_ADMIN))
+ return
+ if(!SSticker || !SSticker.mode)
+ return alert(usr, "The game must start first.", null, null, null, null)
+ if(GLOB.master_mode != "dynamic")
+ return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null)
+ var/midround_rules = list()
+ for (var/rule in subtypesof(/datum/dynamic_ruleset/midround))
+ var/datum/dynamic_ruleset/midround/newrule = new rule()
+ midround_rules[newrule.name] = rule
+ var/added_rule = input(usr,"What ruleset do you want to force right now? This will bypass threat level and population restrictions.", "Execute Ruleset", null) as null|anything in midround_rules
+ if (added_rule)
+ var/datum/game_mode/dynamic/mode = SSticker.mode
+ log_admin("[key_name(usr)] executed the [added_rule] ruleset.")
+ message_admins("[key_name(usr)] executed the [added_rule] ruleset.", 1)
+ mode.picking_specific_rule(midround_rules[added_rule],1)
+
+ else if (href_list["f_dynamic_options"])
+ if(!check_rights(R_ADMIN))
+ return
+
+ if(SSticker && SSticker.mode)
+ return alert(usr, "The game has already started.", null, null, null, null)
+ if(GLOB.master_mode != "dynamic")
+ return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null)
+
+ dynamic_mode_options(usr)
+
+ else if(href_list["f_dynamic_roundstart_centre"])
+ if(!check_rights(R_ADMIN))
+ return
+ if(SSticker && SSticker.mode)
+ return alert(usr, "The game has already started.", null, null, null, null)
+ if(GLOB.master_mode != "dynamic")
+ return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null)
+
+ var/new_centre = input(usr,"Change the centre of the dynamic mode threat curve. A negative value will give a more peaceful round ; a positive value, a round with higher threat. Any number between -5 and +5 is allowed.", "Change curve centre", null) as num
+ if (new_centre < -5 || new_centre > 5)
+ return alert(usr, "Only values between -5 and +5 are allowed.", null, null, null, null)
+
+ log_admin("[key_name(usr)] changed the distribution curve center to [new_centre].")
+ message_admins("[key_name(usr)] changed the distribution curve center to [new_centre]", 1)
+ GLOB.dynamic_curve_centre = new_centre
+ dynamic_mode_options(usr)
+
+ else if(href_list["f_dynamic_roundstart_width"])
+ if(!check_rights(R_ADMIN))
+ return
+ if(SSticker && SSticker.mode)
+ return alert(usr, "The game has already started.", null, null, null, null)
+ if(GLOB.master_mode != "dynamic")
+ return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null)
+
+ var/new_width = input(usr,"Change the width of the dynamic mode threat curve. A higher value will favour extreme rounds ; a lower value, a round closer to the average. Any Number between 0.5 and 4 are allowed.", "Change curve width", null) as num
+ if (new_width < 0.5 || new_width > 4)
+ return alert(usr, "Only values between 0.5 and +2.5 are allowed.", null, null, null, null)
+
+ log_admin("[key_name(usr)] changed the distribution curve width to [new_width].")
+ message_admins("[key_name(usr)] changed the distribution curve width to [new_width]", 1)
+ GLOB.dynamic_curve_width = new_width
+ dynamic_mode_options(usr)
+
+ else if(href_list["f_dynamic_roundstart_latejoin_min"])
+ if(!check_rights(R_ADMIN))
+ return
+ if(SSticker && SSticker.mode)
+ return alert(usr, "The game has already started.", null, null, null, null)
+ if(GLOB.master_mode != "dynamic")
+ return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null)
+ var/new_min = input(usr,"Change the minimum delay of latejoin injection in minutes.", "Change latejoin injection delay minimum", null) as num
+ if(new_min <= 0)
+ return alert(usr, "The minimum can't be zero or lower.", null, null, null, null)
+ if((new_min MINUTES) > GLOB.dynamic_latejoin_delay_max)
+ return alert(usr, "The minimum must be lower than the maximum.", null, null, null, null)
+
+ log_admin("[key_name(usr)] changed the latejoin injection minimum delay to [new_min] minutes.")
+ message_admins("[key_name(usr)] changed the latejoin injection minimum delay to [new_min] minutes", 1)
+ GLOB.dynamic_latejoin_delay_min = (new_min MINUTES)
+ dynamic_mode_options(usr)
+
+ else if(href_list["f_dynamic_roundstart_latejoin_max"])
+ if(!check_rights(R_ADMIN))
+ return
+ if(SSticker && SSticker.mode)
+ return alert(usr, "The game has already started.", null, null, null, null)
+ if(GLOB.master_mode != "dynamic")
+ return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null)
+ var/new_max = input(usr,"Change the maximum delay of latejoin injection in minutes.", "Change latejoin injection delay maximum", null) as num
+ if(new_max <= 0)
+ return alert(usr, "The maximum can't be zero or lower.", null, null, null, null)
+ if((new_max MINUTES) < GLOB.dynamic_latejoin_delay_min)
+ return alert(usr, "The maximum must be higher than the minimum.", null, null, null, null)
+
+ log_admin("[key_name(usr)] changed the latejoin injection maximum delay to [new_max] minutes.")
+ message_admins("[key_name(usr)] changed the latejoin injection maximum delay to [new_max] minutes", 1)
+ GLOB.dynamic_latejoin_delay_max = (new_max MINUTES)
+ dynamic_mode_options(usr)
+
+ else if(href_list["f_dynamic_roundstart_midround_min"])
+ if(!check_rights(R_ADMIN))
+ return
+ if(SSticker && SSticker.mode)
+ return alert(usr, "The game has already started.", null, null, null, null)
+ if(GLOB.master_mode != "dynamic")
+ return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null)
+ var/new_min = input(usr,"Change the minimum delay of midround injection in minutes.", "Change midround injection delay minimum", null) as num
+ if(new_min <= 0)
+ return alert(usr, "The minimum can't be zero or lower.", null, null, null, null)
+ if((new_min MINUTES) > GLOB.dynamic_midround_delay_max)
+ return alert(usr, "The minimum must be lower than the maximum.", null, null, null, null)
+
+ log_admin("[key_name(usr)] changed the midround injection minimum delay to [new_min] minutes.")
+ message_admins("[key_name(usr)] changed the midround injection minimum delay to [new_min] minutes", 1)
+ GLOB.dynamic_midround_delay_min = (new_min MINUTES)
+ dynamic_mode_options(usr)
+
+ else if(href_list["f_dynamic_roundstart_midround_max"])
+ if(!check_rights(R_ADMIN))
+ return
+ if(SSticker && SSticker.mode)
+ return alert(usr, "The game has already started.", null, null, null, null)
+ if(GLOB.master_mode != "dynamic")
+ return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null)
+ var/new_max = input(usr,"Change the maximum delay of midround injection in minutes.", "Change midround injection delay maximum", null) as num
+ if(new_max <= 0)
+ return alert(usr, "The maximum can't be zero or lower.", null, null, null, null)
+ if((new_max MINUTES) > GLOB.dynamic_midround_delay_max)
+ return alert(usr, "The maximum must be higher than the minimum.", null, null, null, null)
+
+ log_admin("[key_name(usr)] changed the midround injection maximum delay to [new_max] minutes.")
+ message_admins("[key_name(usr)] changed the midround injection maximum delay to [new_max] minutes", 1)
+ GLOB.dynamic_midround_delay_max = (new_max MINUTES)
+ dynamic_mode_options(usr)
+
+ else if(href_list["f_dynamic_force_extended"])
+ if(!check_rights(R_ADMIN))
+ return
+
+ if(GLOB.master_mode != "dynamic")
+ return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null)
+
+ GLOB.dynamic_forced_extended = !GLOB.dynamic_forced_extended
+ log_admin("[key_name(usr)] set 'forced_extended' to [GLOB.dynamic_forced_extended].")
+ message_admins("[key_name(usr)] set 'forced_extended' to [GLOB.dynamic_forced_extended].")
+ dynamic_mode_options(usr)
+
+ else if(href_list["f_dynamic_no_stacking"])
+ if(!check_rights(R_ADMIN))
+ return
+
+ if(GLOB.master_mode != "dynamic")
+ return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null)
+
+ GLOB.dynamic_no_stacking = !GLOB.dynamic_no_stacking
+ log_admin("[key_name(usr)] set 'no_stacking' to [GLOB.dynamic_no_stacking].")
+ message_admins("[key_name(usr)] set 'no_stacking' to [GLOB.dynamic_no_stacking].")
+ dynamic_mode_options(usr)
+
+ else if(href_list["f_dynamic_classic_secret"])
+ if(!check_rights(R_ADMIN))
+ return
+
+ if(GLOB.master_mode != "dynamic")
+ return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null)
+
+ GLOB.dynamic_classic_secret = !GLOB.dynamic_classic_secret
+ log_admin("[key_name(usr)] set 'classic_secret' to [GLOB.dynamic_classic_secret].")
+ message_admins("[key_name(usr)] set 'classic_secret' to [GLOB.dynamic_classic_secret].")
+ dynamic_mode_options(usr)
+
+ else if(href_list["f_dynamic_stacking_limit"])
+ if(!check_rights(R_ADMIN))
+ return
+
+ if(GLOB.master_mode != "dynamic")
+ return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null)
+
+ GLOB.dynamic_stacking_limit = input(usr,"Change the threat limit at which round-endings rulesets will start to stack.", "Change stacking limit", null) as num
+ log_admin("[key_name(usr)] set 'stacking_limit' to [GLOB.dynamic_stacking_limit].")
+ message_admins("[key_name(usr)] set 'stacking_limit' to [GLOB.dynamic_stacking_limit].")
+ dynamic_mode_options(usr)
+
+ else if(href_list["f_dynamic_high_pop_limit"])
+ if(!check_rights(R_ADMIN))
+ return
+
+ if(SSticker && SSticker.mode)
+ return alert(usr, "The game has already started.", null, null, null, null)
+
+ if(GLOB.master_mode != "dynamic")
+ return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null)
+
+ var/new_value = input(usr, "Enter the high-pop override threshold for dynamic mode.", "High pop override") as num
+ if (new_value < 0)
+ return alert(usr, "Only positive values allowed!", null, null, null, null)
+ GLOB.dynamic_high_pop_limit = new_value
+
+ log_admin("[key_name(usr)] set 'high_pop_limit' to [GLOB.dynamic_high_pop_limit].")
+ message_admins("[key_name(usr)] set 'high_pop_limit' to [GLOB.dynamic_high_pop_limit].")
+ dynamic_mode_options(usr)
+
+ else if(href_list["f_dynamic_forced_threat"])
+ if(!check_rights(R_ADMIN))
+ return
+
+ if(SSticker && SSticker.mode)
+ return alert(usr, "The game has already started.", null, null, null, null)
+
+ if(GLOB.master_mode != "dynamic")
+ return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null)
+
+ var/new_value = input(usr, "Enter the forced threat level for dynamic mode.", "Forced threat level") as num
+ if (new_value > 100)
+ return alert(usr, "The value must be be under 100.", null, null, null, null)
+ GLOB.dynamic_forced_threat_level = new_value
+
+ log_admin("[key_name(usr)] set 'forced_threat_level' to [GLOB.dynamic_forced_threat_level].")
+ message_admins("[key_name(usr)] set 'forced_threat_level' to [GLOB.dynamic_forced_threat_level].")
+ dynamic_mode_options(usr)
else if(href_list["c_mode2"])
if(!check_rights(R_ADMIN|R_SERVER))
@@ -1752,7 +2042,7 @@
if(DEAD)
status = "Dead"
health_description = "Status = [status]"
- health_description += " Oxy: [L.getOxyLoss()] - Tox: [L.getToxLoss()] - Fire: [L.getFireLoss()] - Brute: [L.getBruteLoss()] - Clone: [L.getCloneLoss()] - Brain: [L.getBrainLoss()] - Stamina: [L.getStaminaLoss()]"
+ health_description += " Oxy: [L.getOxyLoss()] - Tox: [L.getToxLoss()] - Fire: [L.getFireLoss()] - Brute: [L.getBruteLoss()] - Clone: [L.getCloneLoss()] - Brain: [L.getOrganLoss(ORGAN_SLOT_BRAIN)] - Stamina: [L.getStaminaLoss()]"
else
health_description = "This mob type has no health to speak of."
@@ -1882,14 +2172,14 @@
return
var/mob/M = locate(href_list["CentComReply"])
- usr.client.admin_headset_message(M, "CentCom")
+ usr.client.admin_headset_message(M, RADIO_CHANNEL_CENTCOM)
else if(href_list["SyndicateReply"])
if(!check_rights(R_ADMIN))
return
var/mob/M = locate(href_list["SyndicateReply"])
- usr.client.admin_headset_message(M, "Syndicate")
+ usr.client.admin_headset_message(M, RADIO_CHANNEL_SYNDICATE)
else if(href_list["HeadsetMessage"])
if(!check_rights(R_ADMIN))
diff --git a/code/modules/admin/verbs/adminhelp.dm b/code/modules/admin/verbs/adminhelp.dm
index 91fdc78d20..4e58a9cba5 100644
--- a/code/modules/admin/verbs/adminhelp.dm
+++ b/code/modules/admin/verbs/adminhelp.dm
@@ -413,9 +413,9 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new)
dat += "CLOSED"
else
dat += "UNKNOWN"
- dat += "[GLOB.TAB][TicketHref("Refresh", ref_src)][GLOB.TAB][TicketHref("Re-Title", ref_src, "retitle")]"
+ dat += "[FOURSPACES][TicketHref("Refresh", ref_src)][FOURSPACES][TicketHref("Re-Title", ref_src, "retitle")]"
if(state != AHELP_ACTIVE)
- dat += "[GLOB.TAB][TicketHref("Reopen", ref_src, "reopen")]"
+ dat += "[FOURSPACES][TicketHref("Reopen", ref_src, "reopen")]"
dat += "
Opened at: [GAMETIMESTAMP("hh:mm:ss", closed_at)] (Approx [DisplayTimeText(world.time - opened_at)] ago)"
if(closed_at)
dat += " Closed at: [GAMETIMESTAMP("hh:mm:ss", closed_at)] (Approx [DisplayTimeText(world.time - closed_at)] ago)"
@@ -423,7 +423,7 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new)
if(initiator)
dat += "Actions: [FullMonty(ref_src)] "
else
- dat += "DISCONNECTED[GLOB.TAB][ClosureLinks(ref_src)] "
+ dat += "DISCONNECTED[FOURSPACES][ClosureLinks(ref_src)] "
dat += " Log:
"
for(var/I in _interactions)
dat += "[I] "
diff --git a/code/modules/admin/verbs/adminpm.dm b/code/modules/admin/verbs/adminpm.dm
index 84e5ba4f82..5f3153d90f 100644
--- a/code/modules/admin/verbs/adminpm.dm
+++ b/code/modules/admin/verbs/adminpm.dm
@@ -6,7 +6,7 @@
set category = null
set name = "Admin PM Mob"
if(!holder)
- to_chat(src, "Error: Admin-PM-Context: Only administrators may use this command.")
+ to_chat(src, "Error: Admin-PM-Context: Only administrators may use this command.")
return
if( !ismob(M) || !M.client )
return
@@ -18,7 +18,7 @@
set category = "Admin"
set name = "Admin PM"
if(!holder)
- to_chat(src, "Error: Admin-PM-Panel: Only administrators may use this command.")
+ to_chat(src, "Error: Admin-PM-Panel: Only administrators may use this command.")
return
var/list/client/targets[0]
for(var/client/T)
@@ -37,7 +37,7 @@
/client/proc/cmd_ahelp_reply(whom)
if(prefs.muted & MUTE_ADMINHELP)
- to_chat(src, "Error: Admin-PM: You are unable to use admin PM-s (muted).")
+ to_chat(src, "Error: Admin-PM: You are unable to use admin PM-s (muted).")
return
var/client/C
if(istext(whom))
@@ -48,7 +48,7 @@
C = whom
if(!C)
if(holder)
- to_chat(src, "Error: Admin-PM: Client not found.")
+ to_chat(src, "Error: Admin-PM: Client not found.")
return
var/datum/admin_help/AH = C.current_ticket
@@ -65,12 +65,12 @@
//Fetching a message if needed. src is the sender and C is the target client
/client/proc/cmd_admin_pm(whom, msg)
if(prefs.muted & MUTE_ADMINHELP)
- to_chat(src, "Error: Admin-PM: You are unable to use admin PM-s (muted).")
+ to_chat(src, "Error: Admin-PM: You are unable to use admin PM-s (muted).")
return
if(!holder && !current_ticket) //no ticket? https://www.youtube.com/watch?v=iHSPf6x1Fdo
- to_chat(src, "You can no longer reply to this ticket, please open another one by using the Adminhelp verb if need be.")
- to_chat(src, "Message: [msg]")
+ to_chat(src, "You can no longer reply to this ticket, please open another one by using the Adminhelp verb if need be.")
+ to_chat(src, "Message: [msg]")
return
var/client/recipient
@@ -95,14 +95,14 @@
if(!msg)
return
if(holder)
- to_chat(src, "Error: Use the admin IRC channel, nerd.")
+ to_chat(src, "Error: Use the admin IRC channel, nerd.")
return
else
if(!recipient)
if(holder)
- to_chat(src, "Error: Admin-PM: Client not found.")
+ to_chat(src, "Error: Admin-PM: Client not found.")
if(msg)
to_chat(src, msg)
return
@@ -118,12 +118,12 @@
return
if(prefs.muted & MUTE_ADMINHELP)
- to_chat(src, "Error: Admin-PM: You are unable to use admin PM-s (muted).")
+ to_chat(src, "Error: Admin-PM: You are unable to use admin PM-s (muted).")
return
if(!recipient)
if(holder)
- to_chat(src, "Error: Admin-PM: Client not found.")
+ to_chat(src, "Error: Admin-PM: Client not found.")
else
current_ticket.MessageNoRecipient(msg)
return
@@ -145,15 +145,15 @@
var/keywordparsedmsg = keywords_lookup(msg)
if(irc)
- to_chat(src, "PM to-Admins: [rawmsg]")
+ to_chat(src, "PM to-Admins: [rawmsg]")
var/datum/admin_help/AH = admin_ticket_log(src, "Reply PM from-[key_name(src, TRUE, TRUE)] to IRC: [keywordparsedmsg]")
ircreplyamount--
send2irc("[AH ? "#[AH.id] " : ""]Reply: [ckey]", rawmsg)
else
if(recipient.holder)
if(holder) //both are admins
- to_chat(recipient, "Admin PM from-[key_name(src, recipient, 1)]: [keywordparsedmsg]")
- to_chat(src, "Admin PM to-[key_name(recipient, src, 1)]: [keywordparsedmsg]")
+ to_chat(recipient, "Admin PM from-[key_name(src, recipient, 1)]: [keywordparsedmsg]")
+ to_chat(src, "Admin PM to-[key_name(recipient, src, 1)]: [keywordparsedmsg]")
//omg this is dumb, just fill in both their tickets
var/interaction_message = "PM from-[key_name(src, recipient, 1)] to-[key_name(recipient, src, 1)]: [keywordparsedmsg]"
@@ -162,10 +162,10 @@
admin_ticket_log(recipient, interaction_message)
else //recipient is an admin but sender is not
- var/replymsg = "Reply PM from-[key_name(src, recipient, 1)]: [keywordparsedmsg]"
- admin_ticket_log(src, replymsg)
- to_chat(recipient, replymsg)
- to_chat(src, "PM to-Admins: [msg]")
+ var/replymsg = "Reply PM from-[key_name(src, recipient, 1)]: [keywordparsedmsg]"
+ admin_ticket_log(src, "[replymsg]")
+ to_chat(recipient, "[replymsg]")
+ to_chat(src, "PM to-Admins: [msg]")
//play the receiving admin the adminhelp sound (if they have them enabled)
if(recipient.prefs.toggles & SOUND_ADMINHELP)
@@ -177,11 +177,11 @@
new /datum/admin_help(msg, recipient, TRUE)
to_chat(recipient, "-- Administrator private message --")
- to_chat(recipient, "Admin PM from-[key_name(src, recipient, 0)]: [msg]")
- to_chat(recipient, "Click on the administrator's name to reply.")
- to_chat(src, "Admin PM to-[key_name(recipient, src, 1)]: [msg]")
+ to_chat(recipient, "Admin PM from-[key_name(src, recipient, 0)]: [msg]")
+ to_chat(recipient, "Click on the administrator's name to reply.")
+ to_chat(src, "Admin PM to-[key_name(recipient, src, 1)]: [msg]")
- admin_ticket_log(recipient, "PM From [key_name_admin(src)]: [keywordparsedmsg]")
+ admin_ticket_log(recipient, "PM From [key_name_admin(src)]: [keywordparsedmsg]")
//always play non-admin recipients the adminhelp sound
SEND_SOUND(recipient, sound('sound/effects/adminhelp.ogg'))
@@ -200,20 +200,20 @@
return
else //neither are admins
- to_chat(src, "Error: Admin-PM: Non-admin to non-admin PM communication is forbidden.")
+ to_chat(src, "Error: Admin-PM: Non-admin to non-admin PM communication is forbidden.")
return
if(irc)
log_admin_private("PM: [key_name(src)]->IRC: [rawmsg]")
for(var/client/X in GLOB.admins)
- to_chat(X, "PM: [key_name(src, X, 0)]->IRC: [keywordparsedmsg]")
+ to_chat(X, "PM: [key_name(src, X, 0)]->IRC: [keywordparsedmsg]")
else
window_flash(recipient, ignorepref = TRUE)
log_admin_private("PM: [key_name(src)]->[key_name(recipient)]: [rawmsg]")
//we don't use message_admins here because the sender/receiver might get it too
for(var/client/X in GLOB.admins)
if(X.key!=key && X.key!=recipient.key) //check client/X is an admin and isn't the sender or recipient
- to_chat(X, "PM: [key_name(src, X, 0)]->[key_name(recipient, X, 0)]: [keywordparsedmsg]" )
+ to_chat(X, "PM: [key_name(src, X, 0)]->[key_name(recipient, X, 0)]: [keywordparsedmsg]" )
@@ -296,10 +296,10 @@
msg = emoji_parse(msg)
to_chat(C, "-- Administrator private message --")
- to_chat(C, "Admin PM from-[adminname]: [msg]")
- to_chat(C, "Click on the administrator's name to reply.")
+ to_chat(C, "Admin PM from-[adminname]: [msg]")
+ to_chat(C, "Click on the administrator's name to reply.")
- admin_ticket_log(C, "PM From [irc_tagged]: [msg]")
+ admin_ticket_log(C, "PM From [irc_tagged]: [msg]")
window_flash(C, ignorepref = TRUE)
//always play non-admin recipients the adminhelp sound
diff --git a/code/modules/admin/verbs/borgpanel.dm b/code/modules/admin/verbs/borgpanel.dm
index 6295d4be43..c0445d588d 100644
--- a/code/modules/admin/verbs/borgpanel.dm
+++ b/code/modules/admin/verbs/borgpanel.dm
@@ -62,7 +62,7 @@
.["laws"] = borg.laws ? borg.laws.get_law_list(include_zeroth = TRUE) : list()
.["channels"] = list()
for (var/k in GLOB.radiochannels)
- if (k == "Common")
+ if (k == RADIO_CHANNEL_COMMON)
continue
.["channels"] += list(list("name" = k, "installed" = (k in borg.radio.channels)))
.["cell"] = borg.cell ? list("missing" = FALSE, "maxcharge" = borg.cell.maxcharge, "charge" = borg.cell.charge) : list("missing" = TRUE, "maxcharge" = 1, "charge" = 0)
@@ -164,15 +164,15 @@
if (channel in borg.radio.channels) // We're removing a channel
if (!borg.radio.keyslot) // There's no encryption key. This shouldn't happen but we can cope
borg.radio.channels -= channel
- if (channel == "Syndicate")
+ if (channel == RADIO_CHANNEL_SYNDICATE)
borg.radio.syndie = FALSE
- else if (channel == "CentCom")
+ else if (channel == RADIO_CHANNEL_CENTCOM)
borg.radio.independent = FALSE
else
borg.radio.keyslot.channels -= channel
- if (channel == "Syndicate")
+ if (channel == RADIO_CHANNEL_SYNDICATE)
borg.radio.keyslot.syndie = FALSE
- else if (channel == "CentCom")
+ else if (channel == RADIO_CHANNEL_CENTCOM)
borg.radio.keyslot.independent = FALSE
message_admins("[key_name_admin(user)] removed the [channel] radio channel from [ADMIN_LOOKUPFLW(borg)].")
log_admin("[key_name(user)] removed the [channel] radio channel from [key_name(borg)].")
@@ -180,9 +180,9 @@
if (!borg.radio.keyslot) // Assert that an encryption key exists
borg.radio.keyslot = new (borg.radio)
borg.radio.keyslot.channels[channel] = 1
- if (channel == "Syndicate")
+ if (channel == RADIO_CHANNEL_SYNDICATE)
borg.radio.keyslot.syndie = TRUE
- else if (channel == "CentCom")
+ else if (channel == RADIO_CHANNEL_CENTCOM)
borg.radio.keyslot.independent = TRUE
message_admins("[key_name_admin(user)] added the [channel] radio channel to [ADMIN_LOOKUPFLW(borg)].")
log_admin("[key_name(user)] added the [channel] radio channel to [key_name(borg)].")
diff --git a/code/modules/admin/verbs/debug.dm b/code/modules/admin/verbs/debug.dm
index e15613c43d..53fdb315b5 100644
--- a/code/modules/admin/verbs/debug.dm
+++ b/code/modules/admin/verbs/debug.dm
@@ -306,7 +306,7 @@ GLOBAL_PROTECT(AdminProcCallSpamPrevention)
var/mob/living/silicon/pai/pai = new(card)
pai.name = input(choice, "Enter your pAI name:", "pAI Name", "Personal AI") as text
pai.real_name = pai.name
- pai.key = choice.key
+ choice.transfer_ckey(pai)
card.setPersonality(pai)
for(var/datum/paiCandidate/candidate in SSpai.candidates)
if(candidate.key == choice.key)
diff --git a/code/modules/admin/verbs/modifyvariables.dm b/code/modules/admin/verbs/modifyvariables.dm
index 5b74ae7c5b..e70ea1c1b3 100644
--- a/code/modules/admin/verbs/modifyvariables.dm
+++ b/code/modules/admin/verbs/modifyvariables.dm
@@ -534,7 +534,7 @@ GLOBAL_PROTECT(VVpixelmovement)
if (prompt != "Continue")
return FALSE
return TRUE
-
+
/client/proc/modify_variables(atom/O, param_var_name = null, autodetect_class = 0)
if(!check_rights(R_VAREDIT))
@@ -545,7 +545,7 @@ GLOBAL_PROTECT(VVpixelmovement)
var/var_value
if(param_var_name)
- if(!param_var_name in O.vars)
+ if(!(param_var_name in O.vars))
to_chat(src, "A variable with this name ([param_var_name]) doesn't exist in this datum ([O])")
return
variable = param_var_name
diff --git a/code/modules/admin/verbs/one_click_antag.dm b/code/modules/admin/verbs/one_click_antag.dm
index d9732818bd..09a8f692d8 100644
--- a/code/modules/admin/verbs/one_click_antag.dm
+++ b/code/modules/admin/verbs/one_click_antag.dm
@@ -412,7 +412,7 @@
//Spawn the body
var/mob/living/carbon/human/ERTOperative = new ertemplate.mobtype(spawnloc)
chosen_candidate.client.prefs.copy_to(ERTOperative)
- ERTOperative.key = chosen_candidate.key
+ chosen_candidate.transfer_ckey(ERTOperative)
if(ertemplate.enforce_human || ERTOperative.dna.species.dangerous_existence) // Don't want any exploding plasmemes
ERTOperative.set_species(/datum/species/human)
diff --git a/code/modules/admin/verbs/randomverbs.dm b/code/modules/admin/verbs/randomverbs.dm
index f74b31760d..246ccb1d07 100644
--- a/code/modules/admin/verbs/randomverbs.dm
+++ b/code/modules/admin/verbs/randomverbs.dm
@@ -65,7 +65,7 @@
return
if (!sender)
- sender = input("Who is the message from?", "Sender") as null|anything in list("CentCom","Syndicate")
+ sender = input("Who is the message from?", "Sender") as null|anything in list(RADIO_CHANNEL_CENTCOM,RADIO_CHANNEL_SYNDICATE)
if(!sender)
return
@@ -298,7 +298,7 @@
if(candidates.len)
ckey = input("Pick the player you want to respawn as a xeno.", "Suitable Candidates") as null|anything in candidates
else
- to_chat(usr, "Error: create_xeno(): no suitable candidates.")
+ to_chat(usr, "Error: create_xeno(): no suitable candidates.")
if(!istext(ckey))
return 0
@@ -384,7 +384,7 @@ Traitors and the like can also be revived with the previous role mostly intact.
//Now to give them their mind back.
G_found.mind.transfer_to(new_xeno) //be careful when doing stuff like this! I've already checked the mind isn't in use
- new_xeno.key = G_found.key
+ G_found.transfer_ckey(new_xeno, FALSE)
to_chat(new_xeno, "You have been fully respawned. Enjoy the game.")
var/msg = "[key_name_admin(usr)] has respawned [new_xeno.key] as a filthy xeno."
message_admins(msg)
@@ -397,7 +397,7 @@ Traitors and the like can also be revived with the previous role mostly intact.
var/mob/living/carbon/monkey/new_monkey = new
SSjob.SendToLateJoin(new_monkey)
G_found.mind.transfer_to(new_monkey) //be careful when doing stuff like this! I've already checked the mind isn't in use
- new_monkey.key = G_found.key
+ G_found.transfer_ckey(new_monkey, FALSE)
to_chat(new_monkey, "You have been fully respawned. Enjoy the game.")
var/msg = "[key_name_admin(usr)] has respawned [new_monkey.key] as a filthy xeno."
message_admins(msg)
@@ -437,7 +437,7 @@ Traitors and the like can also be revived with the previous role mostly intact.
if(!new_character.mind.assigned_role)
new_character.mind.assigned_role = "Assistant"//If they somehow got a null assigned role.
- new_character.key = G_found.key
+ G_found.transfer_ckey(new_character, FALSE)
/*
The code below functions with the assumption that the mob is already a traitor if they have a special role.
@@ -1264,7 +1264,7 @@ GLOBAL_LIST_EMPTY(custom_outfits) //Admin created outfits
target.electrocution_animation(40)
to_chat(target, "The gods have punished you for your sins!")
if(ADMIN_PUNISHMENT_BRAINDAMAGE)
- target.adjustBrainLoss(199, 199)
+ target.adjustOrganLoss(ORGAN_SLOT_BRAIN, 199, 199)
if(ADMIN_PUNISHMENT_GIB)
target.gib(FALSE)
if(ADMIN_PUNISHMENT_BSA)
diff --git a/code/modules/antagonists/_common/antag_datum.dm b/code/modules/antagonists/_common/antag_datum.dm
index aa91af1dd8..42719e28bd 100644
--- a/code/modules/antagonists/_common/antag_datum.dm
+++ b/code/modules/antagonists/_common/antag_datum.dm
@@ -15,7 +15,7 @@ GLOBAL_LIST_EMPTY(antagonists)
var/antag_memory = ""//These will be removed with antag datum
var/antag_moodlet //typepath of moodlet that the mob will gain with their status
var/can_hijack = HIJACK_NEUTRAL //If these antags are alone on shuttle hijack happens.
-
+
//Antag panel properties
var/show_in_antagpanel = TRUE //This will hide adding this antag type in antag panel, use only for internal subtypes that shouldn't be added directly but still show if possessed by mind
var/antagpanel_category = "Uncategorized" //Antagpanel will display these together, REQUIRED
@@ -87,7 +87,7 @@ GLOBAL_LIST_EMPTY(antagonists)
to_chat(owner, "Your mob has been taken over by a ghost! Appeal your job ban if you want to avoid this in the future!")
message_admins("[key_name_admin(C)] has taken control of ([key_name_admin(owner.current)]) to replace a jobbaned player.")
owner.current.ghostize(0)
- owner.current.key = C.key
+ C.transfer_ckey(owner.current, FALSE)
/datum/antagonist/proc/on_removal()
remove_innate_effects()
diff --git a/code/modules/antagonists/abductor/abductor.dm b/code/modules/antagonists/abductor/abductor.dm
index 8e29b38fe1..80eb007605 100644
--- a/code/modules/antagonists/abductor/abductor.dm
+++ b/code/modules/antagonists/abductor/abductor.dm
@@ -44,6 +44,7 @@
owner.assigned_role = "[name] [sub_role]"
owner.objectives += team.objectives
finalize_abductor()
+ ADD_TRAIT(owner, TRAIT_ABDUCTOR_TRAINING, ABDUCTOR_ANTAGONIST)
return ..()
/datum/antagonist/abductor/on_removal()
@@ -51,11 +52,13 @@
if(owner.current)
to_chat(owner.current,"You are no longer the [owner.special_role]!")
owner.special_role = null
+ REMOVE_TRAIT(owner, TRAIT_ABDUCTOR_TRAINING, ABDUCTOR_ANTAGONIST)
return ..()
/datum/antagonist/abductor/greet()
to_chat(owner.current, "You are the [owner.special_role]!")
to_chat(owner.current, "With the help of your teammate, kidnap and experiment on station crew members!")
+ to_chat(owner.current, "Try not to disturb the habitat, it could lead to dead specimens.")
to_chat(owner.current, "[greet_text]")
owner.announce_objectives()
@@ -74,11 +77,15 @@
update_abductor_icons_added(owner,"abductor")
-/datum/antagonist/abductor/scientist/finalize_abductor()
- ..()
- var/mob/living/carbon/human/H = owner.current
- var/datum/species/abductor/A = H.dna.species
- A.scientist = TRUE
+/datum/antagonist/abductor/scientist/on_gain()
+ ADD_TRAIT(owner, TRAIT_ABDUCTOR_SCIENTIST_TRAINING, ABDUCTOR_ANTAGONIST)
+ ADD_TRAIT(owner, TRAIT_SURGEON, ABDUCTOR_ANTAGONIST)
+ . = ..()
+
+/datum/antagonist/abductor/scientist/on_removal()
+ REMOVE_TRAIT(owner, TRAIT_ABDUCTOR_SCIENTIST_TRAINING, ABDUCTOR_ANTAGONIST)
+ REMOVE_TRAIT(owner, TRAIT_SURGEON, ABDUCTOR_ANTAGONIST)
+ . = ..()
/datum/antagonist/abductor/admin_add(datum/mind/new_owner,mob/admin)
var/list/current_teams = list()
@@ -213,4 +220,4 @@
/datum/antagonist/proc/update_abductor_icons_removed(datum/mind/alien_mind)
var/datum/atom_hud/antag/hud = GLOB.huds[ANTAG_HUD_ABDUCTOR]
hud.leave_hud(alien_mind.current)
- set_antag_hud(alien_mind.current, null)
\ No newline at end of file
+ set_antag_hud(alien_mind.current, null)
diff --git a/code/modules/antagonists/abductor/equipment/abduction_gear.dm b/code/modules/antagonists/abductor/equipment/abduction_gear.dm
index ca491d8cab..7620aa752b 100644
--- a/code/modules/antagonists/abductor/equipment/abduction_gear.dm
+++ b/code/modules/antagonists/abductor/equipment/abduction_gear.dm
@@ -30,9 +30,12 @@
var/combat_armor = list("melee" = 50, "bullet" = 50, "laser" = 50, "energy" = 50, "bomb" = 50, "bio" = 50, "rad" = 50, "fire" = 90, "acid" = 90)
/obj/item/clothing/suit/armor/abductor/vest/proc/toggle_nodrop()
- item_flags ^= NODROP
+ if(HAS_TRAIT_FROM(src, TRAIT_NODROP, ABDUCTOR_VEST_TRAIT))
+ REMOVE_TRAIT(src, TRAIT_NODROP, ABDUCTOR_VEST_TRAIT)
+ else
+ ADD_TRAIT(src, TRAIT_NODROP, ABDUCTOR_VEST_TRAIT)
if(ismob(loc))
- to_chat(loc, "Your vest is now [item_flags & NODROP ? "locked" : "unlocked"].")
+ to_chat(loc, "Your vest is now [HAS_TRAIT_FROM(src, TRAIT_NODROP, ABDUCTOR_VEST_TRAIT) ? "locked" : "unlocked"].")
/obj/item/clothing/suit/armor/abductor/vest/proc/flip_mode()
switch(mode)
@@ -129,22 +132,24 @@
/obj/item/abductor
icon = 'icons/obj/abductor.dmi'
-/obj/item/abductor/proc/AbductorCheck(user)
- if(isabductor(user))
+/obj/item/abductor/proc/AbductorCheck(mob/user)
+ if(HAS_TRAIT(user, TRAIT_ABDUCTOR_TRAINING))
return TRUE
to_chat(user, "You can't figure how this works!")
return FALSE
-/obj/item/abductor/proc/ScientistCheck(user)
- if(!AbductorCheck(user))
- return FALSE
+/obj/item/abductor/proc/ScientistCheck(mob/user)
+ var/training = HAS_TRAIT(user, TRAIT_ABDUCTOR_TRAINING)
+ var/sci_training = HAS_TRAIT(user, TRAIT_ABDUCTOR_SCIENTIST_TRAINING)
- var/mob/living/carbon/human/H = user
- var/datum/species/abductor/S = H.dna.species
- if(S.scientist)
- return TRUE
- to_chat(user, "You're not trained to use this!")
- return FALSE
+ if(training && !sci_training)
+ to_chat(user, "You're not trained to use this!")
+ . = FALSE
+ else if(!training && !sci_training)
+ to_chat(user, "You can't figure how this works!")
+ . = FALSE
+ else
+ . = TRUE
/obj/item/abductor/gizmo
name = "science tool"
@@ -680,7 +685,7 @@ Congratulations! You are now trained for invasive xenobiology research!"}
desc = "Abduct with style - spiky style. Prevents digital tracking."
icon_state = "alienhelmet"
item_state = "alienhelmet"
- blockTracking = 1
+ blockTracking = TRUE
flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDEFACIALHAIR
// Operating Table / Beds / Lockers
diff --git a/code/modules/antagonists/abductor/equipment/abduction_outfits.dm b/code/modules/antagonists/abductor/equipment/abduction_outfits.dm
index e2b881d147..7e03bbf57a 100644
--- a/code/modules/antagonists/abductor/equipment/abduction_outfits.dm
+++ b/code/modules/antagonists/abductor/equipment/abduction_outfits.dm
@@ -17,7 +17,7 @@
var/obj/item/clothing/suit/armor/abductor/vest/V = locate() in H
if(V)
console.AddVest(V)
- V.item_flags |= NODROP
+ ADD_TRAIT(V, TRAIT_NODROP, ABDUCTOR_VEST_TRAIT)
var/obj/item/storage/backpack/B = locate() in H
if(B)
@@ -52,5 +52,5 @@
/datum/outfit/abductor/scientist/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
..()
if(!visualsOnly)
- var/obj/item/implant/abductor/beamplant = new /obj/item/implant/abductor(H)
+ var/obj/item/implant/abductor/beamplant = new
beamplant.implant(H)
diff --git a/code/modules/antagonists/abductor/equipment/abduction_surgery.dm b/code/modules/antagonists/abductor/equipment/abduction_surgery.dm
index 819dbafd6a..98164de099 100644
--- a/code/modules/antagonists/abductor/equipment/abduction_surgery.dm
+++ b/code/modules/antagonists/abductor/equipment/abduction_surgery.dm
@@ -1,5 +1,5 @@
/datum/surgery/organ_extraction
- name = "experimental dissection"
+ name = "experimental organ replacement"
steps = list(/datum/surgery_step/incise, /datum/surgery_step/clamp_bleeders, /datum/surgery_step/retract_skin, /datum/surgery_step/incise, /datum/surgery_step/extract_organ, /datum/surgery_step/gland_insert)
possible_locs = list(BODY_ZONE_CHEST)
ignore_clothes = 1
diff --git a/code/modules/antagonists/abductor/equipment/gland.dm b/code/modules/antagonists/abductor/equipment/gland.dm
index 8a3ff2186a..cc52b3e910 100644
--- a/code/modules/antagonists/abductor/equipment/gland.dm
+++ b/code/modules/antagonists/abductor/equipment/gland.dm
@@ -5,6 +5,7 @@
icon_state = "gland"
status = ORGAN_ROBOTIC
beating = TRUE
+ var/true_name = "baseline placebo referencer"
var/cooldown_low = 300
var/cooldown_high = 300
var/next_activation = 0
@@ -16,6 +17,11 @@
var/mind_control_duration = 1800
var/active_mind_control = FALSE
+/obj/item/organ/heart/gland/examine(mob/user)
+ . = ..()
+ if(HAS_TRAIT(user, TRAIT_ABDUCTOR_SCIENTIST_TRAINING) || isobserver(user))
+ to_chat(user, "It is \a [true_name].")
+
/obj/item/organ/heart/gland/proc/ownerCheck()
if(ishuman(owner))
return TRUE
@@ -95,6 +101,7 @@
return
/obj/item/organ/heart/gland/heals
+ true_name = "coherency harmonizer"
cooldown_low = 200
cooldown_high = 400
uses = -1
@@ -109,6 +116,7 @@
owner.adjustOxyLoss(-20)
/obj/item/organ/heart/gland/slime
+ true_name = "gastric animation galvanizer"
cooldown_low = 600
cooldown_high = 1200
uses = -1
@@ -130,6 +138,7 @@
Slime.Leader = owner
/obj/item/organ/heart/gland/mindshock
+ true_name = "neural crosstalk uninhibitor"
cooldown_low = 400
cooldown_high = 700
uses = -1
@@ -151,11 +160,12 @@
if(2)
to_chat(H, "You hear an annoying buzz in your head.")
H.confused += 15
- H.adjustBrainLoss(10, 160)
+ 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
@@ -167,10 +177,11 @@
/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/moth, /datum/species/fly))
+ 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
@@ -183,6 +194,7 @@
owner.ventcrawler = VENTCRAWLER_ALWAYS
/obj/item/organ/heart/gland/viral
+ true_name = "contamination incubator"
cooldown_low = 1800
cooldown_high = 2400
uses = 1
@@ -217,6 +229,7 @@
return A
/obj/item/organ/heart/gland/trauma
+ true_name = "white matter randomiser"
cooldown_low = 800
cooldown_high = 1200
uses = 5
@@ -235,6 +248,7 @@
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
@@ -249,6 +263,7 @@
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
@@ -264,6 +279,7 @@
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
@@ -289,6 +305,7 @@
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
@@ -315,6 +332,7 @@
..()
/obj/item/organ/heart/gland/plasma
+ true_name = "effluvium sanguine-synonym emitter"
cooldown_low = 1200
cooldown_high = 1800
uses = -1
diff --git a/code/modules/antagonists/abductor/machinery/camera.dm b/code/modules/antagonists/abductor/machinery/camera.dm
index 41cfa6a954..00e48cb1c7 100644
--- a/code/modules/antagonists/abductor/machinery/camera.dm
+++ b/code/modules/antagonists/abductor/machinery/camera.dm
@@ -55,8 +55,7 @@
actions += set_droppoint_action
/obj/machinery/computer/camera_advanced/abductor/proc/IsScientist(mob/living/carbon/human/H)
- var/datum/species/abductor/S = H.dna.species
- return S.scientist
+ return HAS_TRAIT(H, TRAIT_ABDUCTOR_SCIENTIST_TRAINING)
/datum/action/innate/teleport_in
name = "Send To"
diff --git a/code/modules/antagonists/abductor/machinery/console.dm b/code/modules/antagonists/abductor/machinery/console.dm
index 52bda50b86..30b82398ff 100644
--- a/code/modules/antagonists/abductor/machinery/console.dm
+++ b/code/modules/antagonists/abductor/machinery/console.dm
@@ -28,7 +28,7 @@
. = ..()
if(.)
return
- if(!isabductor(user))
+ if(!HAS_TRAIT(user, TRAIT_ABDUCTOR_TRAINING))
to_chat(user, "You start mashing alien buttons at random!")
if(do_after(user,100, target = src))
TeleporterSend()
@@ -75,7 +75,7 @@
dat+=" "
dat += "Select Agent Vest Disguise "
- dat += "[vest.item_flags & NODROP ? "Unlock" : "Lock"] Vest "
+ dat += "[HAS_TRAIT_FROM(vest, TRAIT_NODROP, ABDUCTOR_VEST_TRAIT) ? "Unlock" : "Lock"] Vest "
else
dat += "NO AGENT VEST DETECTED"
var/datum/browser/popup = new(user, "computer", "Abductor Console", 400, 500)
diff --git a/code/modules/antagonists/abductor/machinery/pad.dm b/code/modules/antagonists/abductor/machinery/pad.dm
index 1cb95fbf05..ab636f7d0e 100644
--- a/code/modules/antagonists/abductor/machinery/pad.dm
+++ b/code/modules/antagonists/abductor/machinery/pad.dm
@@ -53,4 +53,4 @@
. = ..()
var/datum/effect_system/spark_spread/S = new
S.set_up(10,0,loc)
- S.start()
\ No newline at end of file
+ S.start()
diff --git a/code/modules/antagonists/blob/blob/blobs/blob_mobs.dm b/code/modules/antagonists/blob/blob/blobs/blob_mobs.dm
index e60209cb7b..3cb90d64bb 100644
--- a/code/modules/antagonists/blob/blob/blobs/blob_mobs.dm
+++ b/code/modules/antagonists/blob/blob/blobs/blob_mobs.dm
@@ -57,7 +57,7 @@
return ..()
/mob/living/simple_animal/hostile/blob/proc/blob_chat(msg)
- var/spanned_message = say_quote(msg, get_spans())
+ var/spanned_message = say_quote(msg)
var/rendered = "\[Blob Telepathy\] [real_name] [spanned_message]"
for(var/M in GLOB.mob_list)
if(isovermind(M) || istype(M, /mob/living/simple_animal/hostile/blob))
diff --git a/code/modules/antagonists/blob/blob/overmind.dm b/code/modules/antagonists/blob/blob/overmind.dm
index ddf745c0f9..d32e38c194 100644
--- a/code/modules/antagonists/blob/blob/overmind.dm
+++ b/code/modules/antagonists/blob/blob/overmind.dm
@@ -210,7 +210,7 @@ GLOBAL_LIST_EMPTY(blob_nodes)
src.log_talk(message, LOG_SAY)
- var/message_a = say_quote(message, get_spans())
+ var/message_a = say_quote(message)
var/rendered = "\[Blob Telepathy\] [name]([blob_reagent_datum.name]) [message_a]"
for(var/mob/M in GLOB.mob_list)
diff --git a/code/modules/antagonists/blob/blob/powers.dm b/code/modules/antagonists/blob/blob/powers.dm
index 9e915ee0fa..e49d186362 100644
--- a/code/modules/antagonists/blob/blob/powers.dm
+++ b/code/modules/antagonists/blob/blob/powers.dm
@@ -172,7 +172,7 @@
blobber.adjustHealth(blobber.maxHealth * 0.5)
blob_mobs += blobber
var/mob/dead/observer/C = pick(candidates)
- blobber.key = C.key
+ C.transfer_ckey(blobber)
SEND_SOUND(blobber, sound('sound/effects/blobattack.ogg'))
SEND_SOUND(blobber, sound('sound/effects/attackblob.ogg'))
to_chat(blobber, "You are a blobbernaut!")
diff --git a/code/modules/antagonists/changeling/changeling.dm b/code/modules/antagonists/changeling/changeling.dm
index 5b0be336ff..b6e3d66c35 100644
--- a/code/modules/antagonists/changeling/changeling.dm
+++ b/code/modules/antagonists/changeling/changeling.dm
@@ -83,7 +83,7 @@
if(istype(C))
var/obj/item/organ/brain/B = C.getorganslot(ORGAN_SLOT_BRAIN)
if(B && (B.decoy_override != initial(B.decoy_override)))
- B.vital = TRUE
+ B.organ_flags |= ORGAN_VITAL
B.decoy_override = FALSE
remove_changeling_powers()
. = ..()
@@ -269,8 +269,11 @@
prof.protected = protect
prof.underwear = H.underwear
+ prof.undie_color = H.undie_color
prof.undershirt = H.undershirt
+ prof.shirt_color = H.shirt_color
prof.socks = H.socks
+ prof.socks_color = H.socks_color
var/list/slots = list("head", "wear_mask", "back", "wear_suit", "w_uniform", "shoes", "belt", "gloves", "glasses", "ears", "wear_id", "s_store")
for(var/slot in slots)
@@ -337,7 +340,7 @@
if(istype(C))
var/obj/item/organ/brain/B = C.getorganslot(ORGAN_SLOT_BRAIN)
if(B)
- B.vital = FALSE
+ B.organ_flags &= ~ORGAN_VITAL
B.decoy_override = TRUE
update_changeling_icons_added()
return
@@ -350,7 +353,7 @@
/datum/antagonist/changeling/greet()
if (you_are_greet)
to_chat(owner.current, "You are [changelingID], a changeling! You have absorbed and taken the form of a human.")
- to_chat(owner.current, "Use say \":g message\" to communicate with your fellow changelings.")
+ to_chat(owner.current, "Use say \"[MODE_TOKEN_CHANGELING] message\" to communicate with your fellow changelings.")
to_chat(owner.current, "You must complete the following tasks:")
owner.current.playsound_local(get_turf(owner.current), 'sound/ambience/antag/ling_aler.ogg', 100, FALSE, pressure_affected = FALSE)
@@ -503,8 +506,11 @@
var/list/item_state_list = list()
var/underwear
+ var/undie_color
var/undershirt
+ var/shirt_color
var/socks
+ var/socks_color
/datum/changelingprofile/Destroy()
qdel(dna)
diff --git a/code/modules/antagonists/changeling/powers/hivemind.dm b/code/modules/antagonists/changeling/powers/hivemind.dm
index 1d7382d947..f7718d7708 100644
--- a/code/modules/antagonists/changeling/powers/hivemind.dm
+++ b/code/modules/antagonists/changeling/powers/hivemind.dm
@@ -1,8 +1,8 @@
-//HIVEMIND COMMUNICATION (:g)
+//HIVEMIND COMMUNICATION //MODE_TOKEN_CHANGELING / :g
/obj/effect/proc_holder/changeling/hivemind_comms
name = "Hivemind Communication"
desc = "We tune our senses to the airwaves to allow us to discreetly communicate and exchange DNA with other changelings."
- helptext = "We will be able to talk with other changelings with :g. Exchanged DNA do not count towards absorb objectives."
+ helptext = "We will be able to talk with other changelings with :g. Exchanged DNA do not count towards absorb objectives." //MODE_TOKEN_CHANGELING needs to be manually updated here.
dna_cost = 1
chemical_cost = -1
action_icon = 'icons/mob/actions/actions_xeno.dmi'
@@ -20,7 +20,7 @@
..()
var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling)
changeling.changeling_speak = 1
- to_chat(user, "Use say \":g message\" to communicate with the other changelings.")
+ to_chat(user, "Use say \"[MODE_TOKEN_CHANGELING] message\" to communicate with the other changelings.")
var/obj/effect/proc_holder/changeling/hivemind_upload/S1 = new
if(!changeling.has_sting(S1))
changeling.purchasedpowers+=S1
diff --git a/code/modules/antagonists/changeling/powers/linglink.dm b/code/modules/antagonists/changeling/powers/linglink.dm
index 70df78e3b4..971c811074 100644
--- a/code/modules/antagonists/changeling/powers/linglink.dm
+++ b/code/modules/antagonists/changeling/powers/linglink.dm
@@ -56,8 +56,8 @@
if(M.lingcheck() == LINGHIVE_LING)
to_chat(M, "We can sense a foreign presence in the hivemind...")
target.mind.linglink = 1
- target.say(":g AAAAARRRRGGGGGHHHHH!!")
- to_chat(target, "You can now communicate in the changeling hivemind, say \":g message\" to communicate!")
+ target.say("[MODE_TOKEN_CHANGELING] AAAAARRRRGGGGGHHHHH!!")
+ to_chat(target, "You can now communicate in the changeling hivemind, say \"[MODE_TOKEN_CHANGELING] message\" to communicate!")
target.reagents.add_reagent("salbutamol", 40) // So they don't choke to death while you interrogate them
sleep(1800)
SSblackbox.record_feedback("nested tally", "changeling_powers", 1, list("[name]", "[i]"))
diff --git a/code/modules/antagonists/changeling/powers/mutations.dm b/code/modules/antagonists/changeling/powers/mutations.dm
index ede3c2fc58..c428c56d45 100644
--- a/code/modules/antagonists/changeling/powers/mutations.dm
+++ b/code/modules/antagonists/changeling/powers/mutations.dm
@@ -155,20 +155,23 @@
item_state = "arm_blade"
lefthand_file = 'icons/mob/inhands/antag/changeling_lefthand.dmi'
righthand_file = 'icons/mob/inhands/antag/changeling_righthand.dmi'
- item_flags = NEEDS_PERMIT | ABSTRACT | NODROP | DROPDEL
+ item_flags = NEEDS_PERMIT | ABSTRACT | DROPDEL
w_class = WEIGHT_CLASS_HUGE
force = 25
throwforce = 0 //Just to be on the safe side
throw_range = 0
throw_speed = 0
+ armour_penetration = 20
hitsound = 'sound/weapons/bladeslice.ogg'
attack_verb = list("attacked", "slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut")
sharpness = IS_SHARP
var/can_drop = FALSE
var/fake = FALSE
+ total_mass = TOTAL_MASS_HAND_REPLACEMENT
/obj/item/melee/arm_blade/Initialize(mapload,silent,synthetic)
. = ..()
+ ADD_TRAIT(src, TRAIT_NODROP, CHANGELING_TRAIT)
if(ismob(loc) && !silent)
loc.visible_message("A grotesque blade forms around [loc.name]\'s arm!", "Our arm twists and mutates, transforming it into a deadly blade.", "You hear organic matter ripping and tearing!")
if(synthetic)
@@ -242,7 +245,8 @@
item_state = "tentacle"
lefthand_file = 'icons/mob/inhands/antag/changeling_lefthand.dmi'
righthand_file = 'icons/mob/inhands/antag/changeling_righthand.dmi'
- item_flags = NEEDS_PERMIT | ABSTRACT | NODROP | DROPDEL | NOBLUDGEON
+ item_flags = NEEDS_PERMIT | ABSTRACT | DROPDEL | NOBLUDGEON
+ slot_flags = NONE
flags_1 = NONE
w_class = WEIGHT_CLASS_HUGE
ammo_type = /obj/item/ammo_casing/magic/tentacle
@@ -256,6 +260,7 @@
/obj/item/gun/magic/tentacle/Initialize(mapload, silent)
. = ..()
+ ADD_TRAIT(src, TRAIT_NODROP, CHANGELING_TRAIT)
if(ismob(loc))
if(!silent)
loc.visible_message("[loc.name]\'s arm starts stretching inhumanly!", "Our arm twists and mutates, transforming it into a tentacle.", "You hear organic matter ripping and tearing!")
@@ -427,7 +432,7 @@
/obj/item/shield/changeling
name = "shield-like mass"
desc = "A mass of tough, boney tissue. You can still see the fingers as a twisted pattern in the shield."
- item_flags = ABSTRACT | NODROP | DROPDEL
+ item_flags = ABSTRACT | DROPDEL
icon = 'icons/obj/items_and_weapons.dmi'
icon_state = "ling_shield"
lefthand_file = 'icons/mob/inhands/antag/changeling_lefthand.dmi'
@@ -438,6 +443,7 @@
/obj/item/shield/changeling/Initialize()
. = ..()
+ ADD_TRAIT(src, TRAIT_NODROP, CHANGELING_TRAIT)
if(ismob(loc))
loc.visible_message("The end of [loc.name]\'s hand inflates rapidly, forming a huge shield-like mass!", "We inflate our hand into a strong shield.", "You hear organic matter ripping and tearing!")
@@ -479,13 +485,14 @@
name = "flesh mass"
icon_state = "lingspacesuit"
desc = "A huge, bulky mass of pressure and temperature-resistant organic tissue, evolved to facilitate space travel."
- item_flags = NODROP | DROPDEL
+ item_flags = DROPDEL
clothing_flags = STOPSPRESSUREDAMAGE //Not THICKMATERIAL because it's organic tissue, so if somebody tries to inject something into it, it still ends up in your blood. (also balance but muh fluff)
allowed = list(/obj/item/flashlight, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/oxygen)
armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 90, "acid" = 90) //No armor at all.
/obj/item/clothing/suit/space/changeling/Initialize()
. = ..()
+ ADD_TRAIT(src, TRAIT_NODROP, CHANGELING_TRAIT)
if(ismob(loc))
loc.visible_message("[loc.name]\'s flesh rapidly inflates, forming a bloated mass around [loc.p_their()] body!", "We inflate our flesh, creating a spaceproof suit!", "You hear organic matter ripping and tearing!")
START_PROCESSING(SSobj, src)
@@ -499,11 +506,15 @@
name = "flesh mass"
icon_state = "lingspacehelmet"
desc = "A covering of pressure and temperature-resistant organic tissue with a glass-like chitin front."
- item_flags = NODROP | DROPDEL
+ item_flags = DROPDEL
clothing_flags = STOPSPRESSUREDAMAGE
armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 90, "acid" = 90)
flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH
+/obj/item/clothing/head/helmet/space/changeling/Initialize()
+ . = ..()
+ ADD_TRAIT(src, TRAIT_NODROP, CHANGELING_TRAIT)
+
/***************************************\
|*****************ARMOR*****************|
\***************************************/
@@ -529,7 +540,7 @@
name = "chitinous mass"
desc = "A tough, hard covering of black chitin."
icon_state = "lingarmor"
- item_flags = NODROP | DROPDEL
+ item_flags = DROPDEL
body_parts_covered = CHEST|GROIN|LEGS|FEET|ARMS|HANDS
armor = list("melee" = 40, "bullet" = 40, "laser" = 40, "energy" = 20, "bomb" = 10, "bio" = 4, "rad" = 0, "fire" = 90, "acid" = 90)
flags_inv = HIDEJUMPSUIT
@@ -538,6 +549,7 @@
/obj/item/clothing/suit/armor/changeling/Initialize()
. = ..()
+ ADD_TRAIT(src, TRAIT_NODROP, CHANGELING_TRAIT)
if(ismob(loc))
loc.visible_message("[loc.name]\'s flesh turns black, quickly transforming into a hard, chitinous mass!", "We harden our flesh, creating a suit of armor!", "You hear organic matter ripping and tearing!")
@@ -545,6 +557,10 @@
name = "chitinous mass"
desc = "A tough, hard covering of black chitin with transparent chitin in front."
icon_state = "lingarmorhelmet"
- item_flags = NODROP | DROPDEL
+ item_flags = DROPDEL
armor = list("melee" = 40, "bullet" = 40, "laser" = 40, "energy" = 20, "bomb" = 10, "bio" = 4, "rad" = 0, "fire" = 90, "acid" = 90)
flags_inv = HIDEEARS|HIDEHAIR|HIDEEYES|HIDEFACIALHAIR|HIDEFACE
+
+/obj/item/clothing/head/helmet/changeling/Initialize()
+ . = ..()
+ ADD_TRAIT(src, TRAIT_NODROP, CHANGELING_TRAIT)
diff --git a/code/modules/antagonists/changeling/powers/regenerate.dm b/code/modules/antagonists/changeling/powers/regenerate.dm
index a1f8e1ef9b..1b27fa9694 100644
--- a/code/modules/antagonists/changeling/powers/regenerate.dm
+++ b/code/modules/antagonists/changeling/powers/regenerate.dm
@@ -35,7 +35,7 @@
B = new C.dna.species.mutant_brain()
else
B = new()
- B.vital = FALSE
+ B.organ_flags &= ~ORGAN_VITAL
B.decoy_override = TRUE
B.Insert(C)
if(ishuman(user))
diff --git a/code/modules/antagonists/changeling/powers/strained_muscles.dm b/code/modules/antagonists/changeling/powers/strained_muscles.dm
index baeed8b0b2..081b1181dc 100644
--- a/code/modules/antagonists/changeling/powers/strained_muscles.dm
+++ b/code/modules/antagonists/changeling/powers/strained_muscles.dm
@@ -5,7 +5,7 @@
name = "Strained Muscles"
desc = "We evolve the ability to reduce the acid buildup in our muscles, allowing us to move much faster."
helptext = "The strain will make us tired, and we will rapidly become fatigued. Standard weight restrictions, like hardsuits, still apply. Cannot be used in lesser form."
- chemical_cost = 0
+ chemical_cost = 15
dna_cost = 1
req_human = 1
var/stacks = 0 //Increments every 5 seconds; damage increases over time
@@ -15,13 +15,16 @@
action_background_icon_state = "bg_ling"
/obj/effect/proc_holder/changeling/strained_muscles/sting_action(mob/living/carbon/user)
+ var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling)
active = !active
if(active)
to_chat(user, "Our muscles tense and strengthen.")
+ changeling.chem_recharge_slowdown += 0.5
else
REMOVE_TRAIT(user, TRAIT_GOTTAGOFAST, "changeling_muscles")
to_chat(user, "Our muscles relax.")
- if(stacks >= 10)
+ changeling.chem_recharge_slowdown -= 0.5
+ if(stacks >= 20)
to_chat(user, "We collapse in exhaustion.")
user.Knockdown(60)
user.emote("gasp")
@@ -31,6 +34,7 @@
return TRUE
/obj/effect/proc_holder/changeling/strained_muscles/proc/muscle_loop(mob/living/carbon/user)
+ var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling)
while(active)
ADD_TRAIT(user, TRAIT_GOTTAGOFAST, "changeling_muscles")
if(user.stat != CONSCIOUS || user.staminaloss >= 90)
@@ -38,13 +42,14 @@
to_chat(user, "Our muscles relax without the energy to strengthen them.")
user.Knockdown(40)
REMOVE_TRAIT(user, TRAIT_GOTTAGOFAST, "changeling_muscles")
+ changeling.chem_recharge_slowdown -= 0.5
break
stacks++
//user.take_bodypart_damage(stacks * 0.03, 0)
- user.staminaloss += stacks * 1.3 //At first the changeling may regenerate stamina fast enough to nullify fatigue, but it will stack
+ user.adjustStaminaLoss(stacks*1.3) //At first the changeling may regenerate stamina fast enough to nullify fatigue, but it will stack
- if(stacks == 11) //Warning message that the stacks are getting too high
+ if(stacks == 10) //Warning message that the stacks are getting too high
to_chat(user, "Our legs are really starting to hurt...")
sleep(40)
diff --git a/code/modules/antagonists/changeling/powers/tiny_prick.dm b/code/modules/antagonists/changeling/powers/tiny_prick.dm
index 5a701d8a96..c58d934d6d 100644
--- a/code/modules/antagonists/changeling/powers/tiny_prick.dm
+++ b/code/modules/antagonists/changeling/powers/tiny_prick.dm
@@ -62,13 +62,13 @@
/obj/effect/proc_holder/changeling/sting/transformation
- name = "Transformation Sting"
- desc = "We silently sting a human, injecting a retrovirus that forces them to transform."
- helptext = "The victim will transform much like a changeling would. Does not provide a warning to others. Mutations will not be transferred, and monkeys will become human. This ability is loud, and might cause our blood to react violently to heat."
+ name = "Temporary Transformation Sting"
+ desc = "We silently sting a human, injecting a chemical that forces them to transform into a chosen being for a limited time. Additional stings extend the duration."
+ helptext = "The victim will transform much like a changeling would for a limited time. Does not provide a warning to others. Mutations will not be transferred, and monkeys will become human. This ability is loud, and might cause our blood to react violently to heat."
sting_icon = "sting_transform"
- chemical_cost = 50
- dna_cost = 3
- loudness = 2
+ chemical_cost = 10
+ dna_cost = 2
+ loudness = 1
var/datum/changelingprofile/selected_dna = null
action_icon = 'icons/mob/actions/actions_changeling.dmi'
action_icon_state = "ling_sting_transform"
@@ -97,19 +97,19 @@
return 1
/obj/effect/proc_holder/changeling/sting/transformation/sting_action(mob/user, mob/target)
- log_combat(user, target, "stung", "transformation sting", " new identity is '[selected_dna.dna.real_name]'")
- var/datum/dna/NewDNA = selected_dna.dna
+
if(ismonkey(target))
to_chat(user, "Our genes cry out as we sting [target.name]!")
var/mob/living/carbon/C = target
. = TRUE
if(istype(C))
- C.real_name = NewDNA.real_name
- NewDNA.transfer_identity(C)
- if(ismonkey(C))
- C.humanize(TR_KEEPITEMS | TR_KEEPIMPLANTS | TR_KEEPORGANS | TR_KEEPDAMAGE | TR_KEEPVIRUS | TR_DEFAULTMSG)
- C.updateappearance(mutcolor_update=1)
+ if(C.reagents.has_reagent("changeling_sting_real"))
+ C.reagents.add_reagent("changeling_sting_real",120)
+ log_combat(user, target, "stung", "transformation sting", ", extending the duration.")
+ else
+ C.reagents.add_reagent("changeling_sting_real",120,list("desired_dna" = selected_dna.dna))
+ log_combat(user, target, "stung", "transformation sting", " new identity is '[selected_dna.dna.real_name]'")
/obj/effect/proc_holder/changeling/sting/false_armblade
@@ -230,24 +230,23 @@
/obj/effect/proc_holder/changeling/sting/LSD
name = "Hallucination Sting"
- desc = "Causes terror in the target."
- helptext = "We evolve the ability to sting a target with a powerful hallucinogenic chemical. The target does not notice they have been stung, and the effect begins after a few seconds."
+ desc = "Causes terror in the target and deals a minor amount of toxin damage."
+ helptext = "We evolve the ability to sting a target with a powerful toxic hallucinogenic chemical. The target does not notice they have been stung, and the effect begins instantaneously. This ability is somewhat loud, and carries a small risk of our blood gaining violent sensitivity to heat."
sting_icon = "sting_lsd"
chemical_cost = 10
dna_cost = 1
+ loudness = 1
action_icon = 'icons/mob/actions/actions_changeling.dmi'
action_icon_state = "ling_sting_lsd"
action_background_icon_state = "bg_ling"
-/obj/effect/proc_holder/changeling/sting/LSD/sting_action(mob/user, mob/living/carbon/target)
+/obj/effect/proc_holder/changeling/sting/LSD/sting_action(mob/user, mob/target)
log_combat(user, target, "stung", "LSD sting")
- addtimer(CALLBACK(src, .proc/hallucination_time, target), rand(100,200))
+ if(target.reagents)
+ target.reagents.add_reagent("regenerative_materia", 5)
+ target.reagents.add_reagent("mindbreaker", 5)
return TRUE
-/obj/effect/proc_holder/changeling/sting/LSD/proc/hallucination_time(mob/living/carbon/target)
- if(target)
- target.hallucination = max(90, target.hallucination)
-
/obj/effect/proc_holder/changeling/sting/cryo
name = "Cryogenic Sting"
desc = "We silently sting a human with a cocktail of chemicals that freeze them."
diff --git a/code/modules/antagonists/changeling/powers/transform.dm b/code/modules/antagonists/changeling/powers/transform.dm
index 767c7d2621..795ba772d6 100644
--- a/code/modules/antagonists/changeling/powers/transform.dm
+++ b/code/modules/antagonists/changeling/powers/transform.dm
@@ -11,7 +11,11 @@
/obj/item/clothing/glasses/changeling
name = "flesh"
- item_flags = NODROP
+
+/obj/item/clothing/glasses/changeling/Initialize()
+ . = ..()
+ ADD_TRAIT(src, TRAIT_NODROP, CHANGELING_TRAIT)
+
//ATTACK HAND IGNORING PARENT RETURN VALUE
/obj/item/clothing/glasses/changeling/attack_hand(mob/user)
@@ -23,7 +27,11 @@
/obj/item/clothing/under/changeling
name = "flesh"
- item_flags = NODROP
+
+/obj/item/clothing/under/changeling/Initialize()
+ . = ..()
+ ADD_TRAIT(src, TRAIT_NODROP, CHANGELING_TRAIT)
+
//ATTACK HAND IGNORING PARENT RETURN VALUE
/obj/item/clothing/under/changeling/attack_hand(mob/user)
@@ -35,9 +43,13 @@
/obj/item/clothing/suit/changeling
name = "flesh"
- item_flags = NODROP
allowed = list(/obj/item/changeling)
+/obj/item/clothing/suit/changeling/Initialize()
+ . = ..()
+ ADD_TRAIT(src, TRAIT_NODROP, CHANGELING_TRAIT)
+
+
//ATTACK HAND IGNORING PARENT RETURN VALUE
/obj/item/clothing/suit/changeling/attack_hand(mob/user)
if(loc == user && user.mind && user.mind.has_antag_datum(/datum/antagonist/changeling))
@@ -48,7 +60,10 @@
/obj/item/clothing/head/changeling
name = "flesh"
- item_flags = NODROP
+
+/obj/item/clothing/head/changeling/Initialize()
+ . = ..()
+ ADD_TRAIT(src, TRAIT_NODROP, CHANGELING_TRAIT)
//ATTACK HAND IGNORING PARENT RETURN VALUE
/obj/item/clothing/head/changeling/attack_hand(mob/user)
@@ -60,7 +75,11 @@
/obj/item/clothing/shoes/changeling
name = "flesh"
- item_flags = NODROP
+
+/obj/item/clothing/shoes/changeling/Initialize()
+ . = ..()
+ ADD_TRAIT(src, TRAIT_NODROP, CHANGELING_TRAIT)
+
//ATTACK HAND IGNORING PARENT RETURN VALUE
/obj/item/clothing/shoes/changeling/attack_hand(mob/user)
@@ -72,7 +91,11 @@
/obj/item/clothing/gloves/changeling
name = "flesh"
- item_flags = NODROP
+
+/obj/item/clothing/gloves/changeling/Initialize()
+ . = ..()
+ ADD_TRAIT(src, TRAIT_NODROP, CHANGELING_TRAIT)
+
//ATTACK HAND IGNORING PARENT RETURN VALUE
/obj/item/clothing/gloves/changeling/attack_hand(mob/user)
@@ -84,7 +107,11 @@
/obj/item/clothing/mask/changeling
name = "flesh"
- item_flags = NODROP
+
+/obj/item/clothing/mask/changeling/Initialize()
+ . = ..()
+ ADD_TRAIT(src, TRAIT_NODROP, CHANGELING_TRAIT)
+
//ATTACK HAND IGNORING PARENT RETURN VALUE
/obj/item/clothing/mask/changeling/attack_hand(mob/user)
@@ -96,10 +123,14 @@
/obj/item/changeling
name = "flesh"
- item_flags = NODROP
slot_flags = ALL
allowed = list(/obj/item/changeling)
+/obj/item/changeling/Initialize()
+ . = ..()
+ ADD_TRAIT(src, TRAIT_NODROP, CHANGELING_TRAIT)
+
+
//ATTACK HAND IGNORING PARENT RETURN VALUE
/obj/item/changeling/attack_hand(mob/user)
if(loc == user && user.mind && user.mind.has_antag_datum(/datum/antagonist/changeling))
diff --git a/code/modules/antagonists/clockcult/clock_effects/clock_sigils.dm b/code/modules/antagonists/clockcult/clock_effects/clock_sigils.dm
index 97d7f0c128..2018067b77 100644
--- a/code/modules/antagonists/clockcult/clock_effects/clock_sigils.dm
+++ b/code/modules/antagonists/clockcult/clock_effects/clock_sigils.dm
@@ -349,7 +349,7 @@
to_chat(L, "Your physical form has been taken over by another soul due to your inactivity! Ahelp if you wish to regain your form!")
message_admins("[key_name_admin(C)] has taken control of ([key_name_admin(L)]) to replace an inactive clock cultist.")
L.ghostize(0)
- L.key = C.key
+ C.transfer_ckey(L, FALSE)
var/obj/effect/temp_visual/ratvar/sigil/vitality/V = new /obj/effect/temp_visual/ratvar/sigil/vitality(get_turf(src))
animate(V, alpha = 0, transform = matrix()*2, time = 8)
playsound(L, 'sound/magic/staff_healing.ogg', 50, 1)
diff --git a/code/modules/antagonists/clockcult/clock_effects/spatial_gateway.dm b/code/modules/antagonists/clockcult/clock_effects/spatial_gateway.dm
index ea2ec4d6ef..5cf7ab7923 100644
--- a/code/modules/antagonists/clockcult/clock_effects/spatial_gateway.dm
+++ b/code/modules/antagonists/clockcult/clock_effects/spatial_gateway.dm
@@ -133,6 +133,9 @@
return FALSE
if(!uses)
return FALSE
+ if(!do_teleport(A, get_turf(linked_gateway), channel = TELEPORT_CHANNEL_CULT, forced = TRUE))
+ visible_message("[A] bounces off [src]!")
+ return FALSE
if(isliving(A))
var/mob/living/user = A
to_chat(user, "You pass through [src] and appear elsewhere!")
@@ -141,7 +144,6 @@
playsound(linked_gateway, 'sound/effects/empulse.ogg', 50, 1)
transform = matrix() * 1.5
linked_gateway.transform = matrix() * 1.5
- A.forceMove(get_turf(linked_gateway))
if(!no_cost)
uses = max(0, uses - 1)
linked_gateway.uses = max(0, linked_gateway.uses - 1)
diff --git a/code/modules/antagonists/clockcult/clock_helpers/slab_abilities.dm b/code/modules/antagonists/clockcult/clock_helpers/slab_abilities.dm
index 530c4c5662..23caa788d4 100644
--- a/code/modules/antagonists/clockcult/clock_helpers/slab_abilities.dm
+++ b/code/modules/antagonists/clockcult/clock_helpers/slab_abilities.dm
@@ -67,6 +67,7 @@
name = "replicant manacles"
desc = "Heavy manacles made out of freezing-cold metal. It looks like brass, but feels much more solid."
icon_state = "brass_manacles"
+ item_state = "brass_manacles"
item_flags = DROPDEL
/obj/item/restraints/handcuffs/clockwork/dropped(mob/user)
diff --git a/code/modules/antagonists/clockcult/clock_items/clockwork_armor.dm b/code/modules/antagonists/clockcult/clock_items/clockwork_armor.dm
index 277aeca48d..88cf420420 100644
--- a/code/modules/antagonists/clockcult/clock_items/clockwork_armor.dm
+++ b/code/modules/antagonists/clockcult/clock_items/clockwork_armor.dm
@@ -21,17 +21,17 @@
/obj/item/clothing/head/helmet/clockwork/ratvar_act()
if(GLOB.ratvar_awakens)
- armor = list("melee" = 100, "bullet" = 100, "laser" = 100, "energy" = 100, "bomb" = 100, "bio" = 100, "rad" = 100, "fire" = 100, "acid" = 100)
+ armor = getArmor(melee = 100, bullet = 100, laser = 100, energy = 100, bomb = 100, bio = 100, rad = 100, fire = 100, acid = 100)
clothing_flags |= STOPSPRESSUREDAMAGE
max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT
min_cold_protection_temperature = SPACE_HELM_MIN_TEMP_PROTECT
else if(GLOB.ratvar_approaches)
- armor = list("melee" = 70, "bullet" = 80, "laser" = -15, "energy" = 25, "bomb" = 70, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100)
+ armor = getArmor(melee = 70, bullet = 80, laser = -15, energy = 25, bomb = 70, bio = 0, rad = 0, fire = 100, acid = 100)
clothing_flags |= STOPSPRESSUREDAMAGE
max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT
min_cold_protection_temperature = SPACE_HELM_MIN_TEMP_PROTECT
else
- armor = list("melee" = 60, "bullet" = 70, "laser" = -25, "energy" = 0, "bomb" = 60, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100)
+ armor = getArmor(melee = 60, bullet = 70, laser = -25, energy = 0, bomb = 60, bio = 0, rad = 0, fire = 100, acid = 100)
clothing_flags &= ~STOPSPRESSUREDAMAGE
max_heat_protection_temperature = initial(max_heat_protection_temperature)
min_cold_protection_temperature = initial(min_cold_protection_temperature)
@@ -50,7 +50,7 @@
to_chat(user, "The helmet tries to drive a spike through your head as you scramble to remove it!")
user.emote("scream")
user.apply_damage(30, BRUTE, BODY_ZONE_HEAD)
- user.adjustBrainLoss(30)
+ user.adjustOrganLoss(ORGAN_SLOT_BRAIN, 30)
addtimer(CALLBACK(user, /mob/living.proc/dropItemToGround, src, TRUE), 1) //equipped happens before putting stuff on(but not before picking items up), 1). thus, we need to wait for it to be on before forcing it off.
/obj/item/clothing/head/helmet/clockwork/mob_can_equip(mob/M, mob/equipper, slot, disable_warning = 0)
@@ -69,7 +69,7 @@
heat_protection = CHEST|GROIN|LEGS
resistance_flags = FIRE_PROOF | ACID_PROOF
armor = list("melee" = 60, "bullet" = 70, "laser" = -25, "energy" = 0, "bomb" = 60, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100)
- allowed = list(/obj/item/clockwork, /obj/item/clothing/glasses/wraith_spectacles, /obj/item/clothing/glasses/judicial_visor, /obj/item/mmi/posibrain/soul_vessel)
+ allowed = list(/obj/item/clockwork, /obj/item/clothing/glasses/wraith_spectacles, /obj/item/clothing/glasses/judicial_visor, /obj/item/mmi/posibrain/soul_vessel, /obj/item/reagent_containers/food/drinks/bottle/holyoil)
/obj/item/clothing/suit/armor/clockwork/Initialize()
. = ..()
@@ -82,17 +82,17 @@
/obj/item/clothing/suit/armor/clockwork/ratvar_act()
if(GLOB.ratvar_awakens)
- armor = list("melee" = 100, "bullet" = 100, "laser" = 100, "energy" = 100, "bomb" = 100, "bio" = 100, "rad" = 100, "fire" = 100, "acid" = 100)
+ armor = getArmor(melee = 100, bullet = 100, laser = 100, energy = 100, bomb = 100, bio = 100, rad = 100, fire = 100, acid = 100)
clothing_flags |= STOPSPRESSUREDAMAGE
max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT
min_cold_protection_temperature = SPACE_HELM_MIN_TEMP_PROTECT
else if(GLOB.ratvar_approaches)
- armor = list("melee" = 70, "bullet" = 80, "laser" = -15, "energy" = 25, "bomb" = 70, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100)
+ armor = getArmor(melee = 70, bullet = 80, laser = -15, energy = 25, bomb = 70, bio = 0, rad = 0, fire = 100, acid = 100)
clothing_flags |= STOPSPRESSUREDAMAGE
max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT
min_cold_protection_temperature = SPACE_HELM_MIN_TEMP_PROTECT
else
- armor = list("melee" = 60, "bullet" = 70, "laser" = -25, "energy" = 0, "bomb" = 60, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100)
+ armor = getArmor(melee = 60, bullet = 70, laser = -25, energy = 0, bomb = 60, bio = 0, rad = 0, fire = 100, acid = 100)
clothing_flags &= ~STOPSPRESSUREDAMAGE
max_heat_protection_temperature = initial(max_heat_protection_temperature)
min_cold_protection_temperature = initial(min_cold_protection_temperature)
@@ -148,12 +148,12 @@
/obj/item/clothing/gloves/clockwork/ratvar_act()
if(GLOB.ratvar_awakens)
- armor = list("melee" = 100, "bullet" = 100, "laser" = 100, "energy" = 100, "bomb" = 100, "bio" = 100, "rad" = 100, "fire" = 100, "acid" = 100)
+ armor = getArmor(melee = 100, bullet = 100, laser = 100, energy = 100, bomb = 100, bio = 100, rad = 100, fire = 100, acid = 100)
clothing_flags |= STOPSPRESSUREDAMAGE
max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT
min_cold_protection_temperature = SPACE_HELM_MIN_TEMP_PROTECT
else
- armor = list("melee" = 80, "bullet" = 70, "laser" = -25, "energy" = 0, "bomb" = 60, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100)
+ armor = getArmor(melee = 80, bullet = 70, laser = -25, energy = 0, bomb = 60, bio = 0, rad = 0, fire = 100, acid = 100)
clothing_flags &= ~STOPSPRESSUREDAMAGE
max_heat_protection_temperature = initial(max_heat_protection_temperature)
min_cold_protection_temperature = initial(min_cold_protection_temperature)
diff --git a/code/modules/antagonists/clockcult/clock_items/construct_chassis.dm b/code/modules/antagonists/clockcult/clock_items/construct_chassis.dm
index 12af249bee..2be0fdde11 100644
--- a/code/modules/antagonists/clockcult/clock_items/construct_chassis.dm
+++ b/code/modules/antagonists/clockcult/clock_items/construct_chassis.dm
@@ -55,7 +55,7 @@
pre_spawn()
visible_message(creation_message)
var/mob/living/construct = new construct_type(get_turf(src))
- construct.key = user.key
+ user.transfer_ckey(construct, FALSE)
post_spawn(construct)
qdel(user)
qdel(src)
diff --git a/code/modules/antagonists/clockcult/clock_mobs.dm b/code/modules/antagonists/clockcult/clock_mobs.dm
index bd9c52b19f..6268d15d44 100644
--- a/code/modules/antagonists/clockcult/clock_mobs.dm
+++ b/code/modules/antagonists/clockcult/clock_mobs.dm
@@ -19,6 +19,7 @@
bubble_icon = "clock"
light_color = "#E42742"
death_sound = 'sound/magic/clockwork/anima_fragment_death.ogg'
+ speech_span = SPAN_ROBOT
var/playstyle_string = "You are a bug, yell at whoever spawned you!"
var/empower_string = "You have nothing to empower, yell at the coders!" //Shown to the mob when the herald beacon activates
@@ -26,9 +27,6 @@
. = ..()
update_values()
-/mob/living/simple_animal/hostile/clockwork/get_spans()
- return ..() | SPAN_ROBOT
-
/mob/living/simple_animal/hostile/clockwork/Login()
..()
add_servant_of_ratvar(src, TRUE)
diff --git a/code/modules/antagonists/clockcult/clock_scriptures/scripture_drivers.dm b/code/modules/antagonists/clockcult/clock_scriptures/scripture_drivers.dm
index 552a747651..82c1291433 100644
--- a/code/modules/antagonists/clockcult/clock_scriptures/scripture_drivers.dm
+++ b/code/modules/antagonists/clockcult/clock_scriptures/scripture_drivers.dm
@@ -27,7 +27,7 @@
to_chat(invoker, "Stargazers can't be built off-station.")
return
return ..()
-
+
//Integration Cog: Creates an integration cog that can be inserted into APCs to passively siphon power.
/datum/clockwork_scripture/create_object/integration_cog
@@ -216,6 +216,9 @@
if(is_reebe(invoker.z))
to_chat(invoker, "You're already at Reebe.")
return
+ if(!isturf(invoker.loc))
+ to_chat(invoker, "You must be visible to return!")
+ return
return TRUE
/datum/clockwork_scripture/abscond/recital()
@@ -224,12 +227,14 @@
. = ..()
/datum/clockwork_scripture/abscond/scripture_effects()
- var/take_pulling = invoker.pulling && isliving(invoker.pulling) && get_clockwork_power(ABSCOND_ABDUCTION_COST)
+ var/mob/living/pulled_mob = (invoker.pulling && isliving(invoker.pulling) && get_clockwork_power(ABSCOND_ABDUCTION_COST)) ? invoker.pulling : null
var/turf/T
if(GLOB.ark_of_the_clockwork_justiciar)
T = get_step(GLOB.ark_of_the_clockwork_justiciar, SOUTH)
else
T = get_turf(pick(GLOB.servant_spawns))
+ if(!do_teleport(invoker, T, channel = TELEPORT_CHANNEL_CULT, forced = TRUE))
+ return
invoker.visible_message("[invoker] flickers and phases out of existence!", \
"You feel a dizzying sense of vertigo as you're yanked back to Reebe!")
T.visible_message("[invoker] flickers and phases into existence!")
@@ -237,10 +242,9 @@
playsound(T, 'sound/magic/magic_missile.ogg', 50, TRUE)
do_sparks(5, TRUE, invoker)
do_sparks(5, TRUE, T)
- if(take_pulling)
+ if(pulled_mob && do_teleport(pulled_mob, T, channel = TELEPORT_CHANNEL_CULT, forced = TRUE))
adjust_clockwork_power(-special_power_cost)
- invoker.pulling.forceMove(T)
- invoker.forceMove(T)
+ invoker.start_pulling(pulled_mob) //forcemove resets pulls, so we need to re-pull
if(invoker.client)
animate(invoker.client, color = client_color, time = 25)
diff --git a/code/modules/antagonists/clockcult/clock_structures/ark_of_the_clockwork_justicar.dm b/code/modules/antagonists/clockcult/clock_structures/ark_of_the_clockwork_justicar.dm
index 2b1d9d5f02..f735d6bb29 100644
--- a/code/modules/antagonists/clockcult/clock_structures/ark_of_the_clockwork_justicar.dm
+++ b/code/modules/antagonists/clockcult/clock_structures/ark_of_the_clockwork_justicar.dm
@@ -131,12 +131,10 @@
if(!M || !M.current)
continue
if(isliving(M.current) && M.current.stat != DEAD)
- if(isAI(M.current))
- M.current.forceMove(get_step(get_step(src, NORTH),NORTH)) // AI too fat, must make sure it always ends up a 2 tiles north instead of on the ark.
- else
- M.current.forceMove(get_turf(src))
- M.current.overlay_fullscreen("flash", /obj/screen/fullscreen/flash)
- M.current.clear_fullscreen("flash", 5)
+ var/turf/t_turf = isAI(M.current) ? get_step(get_step(src, NORTH),NORTH) : get_turf(src) // AI too fat, must make sure it always ends up a 2 tiles north instead of on the ark.
+ do_teleport(M.current, t_turf, channel = TELEPORT_CHANNEL_CULT, forced = TRUE)
+ M.current.overlay_fullscreen("flash", /obj/screen/fullscreen/flash)
+ M.current.clear_fullscreen("flash", 5)
playsound(src, 'sound/magic/clockwork/invoke_general.ogg', 50, FALSE)
recalls_remaining--
recalling = FALSE
diff --git a/code/modules/antagonists/clockcult/clock_structures/eminence_spire.dm b/code/modules/antagonists/clockcult/clock_structures/eminence_spire.dm
index 05f3ca5917..c01c7f0f57 100644
--- a/code/modules/antagonists/clockcult/clock_structures/eminence_spire.dm
+++ b/code/modules/antagonists/clockcult/clock_structures/eminence_spire.dm
@@ -64,7 +64,7 @@
message_admins("Admin [key_name_admin(user)] directly became the Eminence of the cult!")
log_admin("Admin [key_name(user)] made themselves the Eminence.")
var/mob/camera/eminence/eminence = new(get_turf(src))
- eminence.key = user.key
+ user.transfer_ckey(eminence, FALSE)
hierophant_message("Ratvar has directly assigned the Eminence!")
for(var/mob/M in servants_and_ghosts())
M.playsound_local(M, 'sound/machines/clockcult/eminence_selected.ogg', 50, FALSE)
@@ -138,7 +138,7 @@
playsound(src, 'sound/machines/clockcult/ark_damage.ogg', 50, FALSE)
var/mob/camera/eminence/eminence = new(get_turf(src))
eminence_nominee = pick(candidates)
- eminence.key = eminence_nominee.key
+ eminence_nominee.transfer_ckey(eminence)
hierophant_message("A ghost has ascended into the Eminence!")
for(var/mob/M in servants_and_ghosts())
M.playsound_local(M, 'sound/machines/clockcult/eminence_selected.ogg', 50, FALSE)
diff --git a/code/modules/antagonists/clockcult/clock_structures/heralds_beacon.dm b/code/modules/antagonists/clockcult/clock_structures/heralds_beacon.dm
index 7654c0203b..3a461b7745 100644
--- a/code/modules/antagonists/clockcult/clock_structures/heralds_beacon.dm
+++ b/code/modules/antagonists/clockcult/clock_structures/heralds_beacon.dm
@@ -78,7 +78,7 @@
return
voters += user.key
else
- if(!user.key in voters)
+ if(!(user.key in voters))
return
voters -= user.key
var/votes_left = votes_needed - voters.len
diff --git a/code/modules/antagonists/clockcult/clock_structures/ratvar_the_clockwork_justicar.dm b/code/modules/antagonists/clockcult/clock_structures/ratvar_the_clockwork_justicar.dm
index 9341a7ee6a..c17885315f 100644
--- a/code/modules/antagonists/clockcult/clock_structures/ratvar_the_clockwork_justicar.dm
+++ b/code/modules/antagonists/clockcult/clock_structures/ratvar_the_clockwork_justicar.dm
@@ -45,7 +45,7 @@
return FALSE
var/mob/living/simple_animal/drone/cogscarab/ratvar/R = new/mob/living/simple_animal/drone/cogscarab/ratvar(get_turf(src))
R.visible_message("[R] forms, and its eyes blink open, glowing bright red!")
- R.key = O.key
+ O.transfer_ckey(R, FALSE)
/obj/structure/destructible/clockwork/massive/ratvar/Bump(atom/A)
var/turf/T = get_turf(A)
diff --git a/code/modules/antagonists/clockcult/clock_structures/traps/steam_vent.dm b/code/modules/antagonists/clockcult/clock_structures/traps/steam_vent.dm
index 8d65574987..6aede1592e 100644
--- a/code/modules/antagonists/clockcult/clock_structures/traps/steam_vent.dm
+++ b/code/modules/antagonists/clockcult/clock_structures/traps/steam_vent.dm
@@ -7,7 +7,6 @@
break_message = "The vent snaps and collapses!"
max_integrity = 100
density = FALSE
- layer = BELOW_OBJ_LAYER
/obj/structure/destructible/clockwork/trap/steam_vent/activate()
opacity = !opacity
diff --git a/code/modules/antagonists/clockcult/clock_structures/wall_gear.dm b/code/modules/antagonists/clockcult/clock_structures/wall_gear.dm
index fb8397eed7..32b1b61dd1 100644
--- a/code/modules/antagonists/clockcult/clock_structures/wall_gear.dm
+++ b/code/modules/antagonists/clockcult/clock_structures/wall_gear.dm
@@ -5,7 +5,6 @@
unanchored_icon = "wall_gear"
climbable = TRUE
max_integrity = 100
- layer = BELOW_OBJ_LAYER
construction_value = 3
desc = "A massive brass gear. You could probably secure or unsecure it with a wrench, or just climb over it."
break_message = "The gear breaks apart into shards of alloy!"
diff --git a/code/modules/antagonists/cult/blood_magic.dm b/code/modules/antagonists/cult/blood_magic.dm
index 845c66fb33..fc4d945d51 100644
--- a/code/modules/antagonists/cult/blood_magic.dm
+++ b/code/modules/antagonists/cult/blood_magic.dm
@@ -4,6 +4,7 @@
desc = "Prepare blood magic by carving runes into your flesh. This rite is most effective with an empowering rune"
var/list/spells = list()
var/channeling = FALSE
+ var/holy_dispel = FALSE
/datum/action/innate/cult/blood_magic/Grant()
..()
@@ -33,6 +34,9 @@
B.button.moved = B.button.screen_loc
/datum/action/innate/cult/blood_magic/Activate()
+ if(holy_dispel)
+ to_chat(owner, "Holy water currently scours your body, nullifying the power of the rites!")
+ return
var/rune = FALSE
var/limit = RUNELESS_MAX_BLOODCHARGE
for(var/obj/effect/rune/empower/R in range(1, owner))
@@ -64,7 +68,7 @@
qdel(nullify_spell)
return
BS = possible_spells[entered_spell_name]
- if(QDELETED(src) || owner.incapacitated() || !BS || (rune && !(locate(/obj/effect/rune/empower) in range(1, owner))) || (spells.len >= limit))
+ if(QDELETED(src) || owner.incapacitated() || !BS || holy_dispel || (rune && !(locate(/obj/effect/rune/empower) in range(1, owner))) || (spells.len >= limit))
return
to_chat(owner,"You begin to carve unnatural symbols into your flesh!")
SEND_SOUND(owner, sound('sound/weapons/slice.ogg',0,1,10))
@@ -73,7 +77,7 @@
else
to_chat(owner, "You are already invoking blood magic!")
return
- if(do_after(owner, 100 - rune*60, target = owner))
+ if(do_after(owner, 100 - rune*60, target = owner) && !holy_dispel)
if(ishuman(owner))
var/mob/living/carbon/human/H = owner
H.bleed(40 - rune*32)
@@ -339,7 +343,7 @@
icon = 'icons/obj/items_and_weapons.dmi'
icon_state = "disintegrate"
item_state = null
- item_flags = NEEDS_PERMIT | ABSTRACT | NODROP | DROPDEL
+ item_flags = NEEDS_PERMIT | ABSTRACT | DROPDEL
w_class = WEIGHT_CLASS_HUGE
throwforce = 0
@@ -350,11 +354,13 @@
var/health_cost = 0 //The amount of health taken from the user when invoking the spell
var/datum/action/innate/cult/blood_spell/source
-/obj/item/melee/blood_magic/New(loc, spell)
+/obj/item/melee/blood_magic/Initialize(mapload, spell)
+ . = ..()
+ ADD_TRAIT(src, TRAIT_NODROP, CULT_TRAIT)
source = spell
uses = source.charges
health_cost = source.health_cost
- ..()
+
/obj/item/melee/blood_magic/Destroy()
if(!QDELETED(source))
@@ -488,11 +494,12 @@
to_chat(user, "The target rune is blocked. Attempting to teleport to it would be massively unwise.")
return
uses--
- user.visible_message("Dust flows from [user]'s hand, and [user.p_they()] disappear[user.p_s()] with a sharp crack!", \
- "You speak the words of the talisman and find yourself somewhere else!", "You hear a sharp crack.")
+ var/turf/origin = get_turf(user)
var/mob/living/L = target
- L.forceMove(dest)
- dest.visible_message("There is a boom of outrushing air as something appears above the rune!", null, "You hear a boom.")
+ if(do_teleport(L, dest, channel = TELEPORT_CHANNEL_CULT))
+ origin.visible_message("Dust flows from [user]'s hand, and [user.p_they()] disappear[user.p_s()] with a sharp crack!", \
+ "You speak the words of the talisman and find yourself somewhere else!", "You hear a sharp crack.")
+ dest.visible_message("There is a boom of outrushing air as something appears above the rune!", null, "You hear a boom.")
..()
//Shackles
@@ -641,6 +648,11 @@
desc = "A spell that will absorb blood from anything you touch. Touching cultists and constructs can heal them. Clicking the hand will potentially let you focus the spell into something stronger."
color = "#7D1717"
+/obj/item/melee/blood_magic/manipulator/examine(mob/user)
+ . = ..()
+ if(iscultist(user))
+ to_chat(user, "The [name] currently has [uses] blood charges left.")
+
/obj/item/melee/blood_magic/manipulator/afterattack(atom/target, mob/living/carbon/human/user, proximity)
if(proximity)
if(ishuman(target))
@@ -652,15 +664,15 @@
if(H.stat == DEAD)
to_chat(user,"Only a revive rune can bring back the dead!")
return
- if(H.blood_volume < BLOOD_VOLUME_SAFE)
- var/restore_blood = BLOOD_VOLUME_SAFE - H.blood_volume
+ if(H.blood_volume < (BLOOD_VOLUME_SAFE*H.blood_ratio))
+ var/restore_blood = (BLOOD_VOLUME_SAFE*H.blood_ratio) - H.blood_volume
if(uses*2 < restore_blood)
H.blood_volume += uses*2
to_chat(user,"You use the last of your blood rites to restore what blood you could!")
uses = 0
return ..()
else
- H.blood_volume = BLOOD_VOLUME_SAFE
+ H.blood_volume = (BLOOD_VOLUME_SAFE*H.blood_ratio)
uses -= round(restore_blood/2)
to_chat(user,"Your blood rites have restored [H == user ? "your" : "[H.p_their()]"] blood to safe levels!")
var/overall_damage = H.getBruteLoss() + H.getFireLoss() + H.getToxLoss() + H.getOxyLoss()
@@ -675,9 +687,9 @@
if(ratio>1)
ratio = 1
uses -= round(overall_damage)
- H.visible_message("[H] is fully healed by [H==user ? "[H.p_their()]":"[H]'s"]'s blood magic!")
+ H.visible_message("[H] is fully healed by [H==user ? "[H.p_their()]":"[user]'s"] blood magic!")
else
- H.visible_message("[H] is partially healed by [H==user ? "[H.p_their()]":"[H]'s"] blood magic.")
+ H.visible_message("[H] is partially healed by [H==user ? "[H.p_their()]":"[user]'s"] blood magic.")
uses = 0
ratio *= -1
H.adjustOxyLoss((overall_damage*ratio) * (H.getOxyLoss() / overall_damage), 0)
@@ -695,7 +707,7 @@
if(H.cultslurring)
to_chat(user,"[H.p_their(TRUE)] blood has been tainted by an even stronger form of blood magic, it's no use to us like this!")
return
- if(H.blood_volume > BLOOD_VOLUME_SAFE)
+ if(H.blood_volume > (BLOOD_VOLUME_SAFE*H.blood_ratio))
H.blood_volume -= 100
uses += 50
user.Beam(H,icon_state="drainbeam",time=10)
@@ -759,7 +771,7 @@
switch(choice)
if("Blood Spear (150)")
if(uses < 150)
- to_chat(user, "You need 200 charges to perform this rite.")
+ to_chat(user, "You need 150 charges to perform this rite.")
else
uses -= 150
var/turf/T = get_turf(user)
@@ -775,7 +787,7 @@
"A [rite.name] materializes at your feet.")
if("Blood Bolt Barrage (300)")
if(uses < 300)
- to_chat(user, "You need 400 charges to perform this rite.")
+ to_chat(user, "You need 300 charges to perform this rite.")
else
var/obj/rite = new /obj/item/gun/ballistic/shotgun/boltaction/enchanted/arcane_barrage/blood()
uses -= 300
@@ -787,7 +799,7 @@
qdel(rite)
if("Blood Beam (500)")
if(uses < 500)
- to_chat(user, "You need 600 charges to perform this rite.")
+ to_chat(user, "You need 500 charges to perform this rite.")
else
var/obj/rite = new /obj/item/blood_beam()
uses -= 500
@@ -796,4 +808,4 @@
to_chat(user, "Your hands glow with POWER OVERWHELMING!!!")
else
to_chat(user, "You need a free hand for this rite!")
- qdel(rite)
\ No newline at end of file
+ qdel(rite)
diff --git a/code/modules/antagonists/cult/cult_items.dm b/code/modules/antagonists/cult/cult_items.dm
index 6bef6fd021..10759afcd0 100644
--- a/code/modules/antagonists/cult/cult_items.dm
+++ b/code/modules/antagonists/cult/cult_items.dm
@@ -64,9 +64,14 @@
/obj/item/melee/cultblade/ghost
name = "eldritch sword"
force = 19 //can't break normal airlocks
- item_flags = NEEDS_PERMIT | NODROP | DROPDEL
+ item_flags = NEEDS_PERMIT | DROPDEL
flags_1 = NONE
+/obj/item/melee/cultblade/ghost/Initialize()
+ . = ..()
+ ADD_TRAIT(src, TRAIT_NODROP, CULT_TRAIT)
+
+
/obj/item/melee/cultblade/pickup(mob/living/user)
..()
if(!iscultist(user))
@@ -100,7 +105,6 @@
inhand_x_dimension = 64
inhand_y_dimension = 64
actions_types = list()
- item_flags = SLOWS_WHILE_IN_HAND
var/datum/action/innate/dash/cult/jaunt
var/datum/action/innate/cult/spin2win/linked_action
var/spinning = FALSE
@@ -298,7 +302,12 @@
item_state = "cult_hoodalt"
/obj/item/clothing/head/culthood/alt/ghost
- item_flags = NODROP | DROPDEL
+ item_flags = DROPDEL
+
+/obj/item/clothing/head/culthood/alt/ghost/Initialize()
+ . = ..()
+ ADD_TRAIT(src, TRAIT_NODROP, CULT_TRAIT)
+
/obj/item/clothing/suit/cultrobes/alt
name = "cultist robes"
@@ -307,7 +316,11 @@
item_state = "cultrobesalt"
/obj/item/clothing/suit/cultrobes/alt/ghost
- item_flags = NODROP | DROPDEL
+ item_flags = DROPDEL
+
+/obj/item/clothing/suit/cultrobes/alt/ghost/Initialize()
+ . = ..()
+ ADD_TRAIT(src, TRAIT_NODROP, CULT_TRAIT)
/obj/item/clothing/head/magus
name = "magus helm"
@@ -554,7 +567,7 @@
var/mob/living/carbon/C = user
if(C.pulling)
var/atom/movable/pulled = C.pulling
- pulled.forceMove(T)
+ do_teleport(pulled, T, channel = TELEPORT_CHANNEL_CULT)
. = pulled
/obj/item/cult_shift/attack_self(mob/user)
@@ -579,13 +592,12 @@
new /obj/effect/temp_visual/dir_setting/cult/phase/out(mobloc, C.dir)
var/atom/movable/pulled = handle_teleport_grab(destination, C)
- C.forceMove(destination)
- if(pulled)
- C.start_pulling(pulled) //forcemove resets pulls, so we need to re-pull
-
- new /obj/effect/temp_visual/dir_setting/cult/phase(destination, C.dir)
- playsound(destination, 'sound/effects/phasein.ogg', 25, 1)
- playsound(destination, "sparks", 50, 1)
+ if(do_teleport(C, destination, channel = TELEPORT_CHANNEL_CULT))
+ if(pulled)
+ C.start_pulling(pulled) //forcemove resets pulls, so we need to re-pull
+ new /obj/effect/temp_visual/dir_setting/cult/phase(destination, C.dir)
+ playsound(destination, 'sound/effects/phasein.ogg', 25, 1)
+ playsound(destination, "sparks", 50, 1)
else
to_chat(C, "The veil cannot be torn here!")
@@ -654,6 +666,7 @@
righthand_file = 'icons/mob/inhands/weapons/polearms_righthand.dmi'
slot_flags = 0
force = 17
+ force_unwielded = 17
force_wielded = 24
throwforce = 40
throw_speed = 2
@@ -796,7 +809,7 @@
icon = 'icons/obj/items_and_weapons.dmi'
icon_state = "disintegrate"
item_state = null
- item_flags = ABSTRACT | NODROP | DROPDEL
+ item_flags = ABSTRACT | DROPDEL
w_class = WEIGHT_CLASS_HUGE
throwforce = 0
throw_range = 0
@@ -805,6 +818,9 @@
var/firing = FALSE
var/angle
+/obj/item/blood_beam/Initialize()
+ . = ..()
+ ADD_TRAIT(src, TRAIT_NODROP, CULT_TRAIT)
/obj/item/blood_beam/afterattack(atom/A, mob/living/user, flag, params)
. = ..()
diff --git a/code/modules/antagonists/cult/cult_structures.dm b/code/modules/antagonists/cult/cult_structures.dm
index 64d57c2f94..0dd6b08c4d 100644
--- a/code/modules/antagonists/cult/cult_structures.dm
+++ b/code/modules/antagonists/cult/cult_structures.dm
@@ -73,6 +73,10 @@
animate(src, color = previouscolor, time = 8)
addtimer(CALLBACK(src, /atom/proc/update_atom_colour), 8)
+/obj/structure/destructible/cult/proc/check_menu(mob/living/user)
+ if(!user || user.incapacitated() || !iscultist(user) || !anchored || cooldowntime > world.time)
+ return FALSE
+ return TRUE
/obj/structure/destructible/cult/talisman
name = "altar"
@@ -80,9 +84,18 @@
icon_state = "talismanaltar"
break_message = "The altar shatters, leaving only the wailing of the damned!"
-/obj/structure/destructible/cult/talisman/attack_hand(mob/living/user)
+ var/static/image/radial_whetstone = image(icon = 'icons/obj/kitchen.dmi', icon_state = "cult_sharpener")
+ var/static/image/radial_shell = image(icon = 'icons/obj/wizard.dmi', icon_state = "construct-cult")
+ var/static/image/radial_unholy_water = image(icon = 'icons/obj/drinks.dmi', icon_state = "holyflask")
+
+/obj/structure/destructible/cult/talisman/Initialize()
. = ..()
- if(.)
+ radial_unholy_water.color = "#333333"
+
+/obj/structure/destructible/cult/talisman/ui_interact(mob/user)
+ . = ..()
+
+ if(!user.canUseTopic(src, TRUE))
return
if(!iscultist(user))
to_chat(user, "You're pretty sure you know exactly what this is used for and you can't seem to touch it.")
@@ -91,22 +104,27 @@
to_chat(user, "You need to anchor [src] to the floor with your dagger first.")
return
if(cooldowntime > world.time)
- to_chat(user, "The magic in [src] is weak, it will be ready to use again in [DisplayTimeText(cooldowntime - world.time)].")
+ to_chat(user, "The magic in [src] is weak, it will be ready to use again in [DisplayTimeText(cooldowntime - world.time)].")
return
- var/choice = alert(user,"You study the schematics etched into the altar...",,"Eldritch Whetstone","Construct Shell","Flask of Unholy Water")
- var/list/pickedtype = list()
+
+ to_chat(user, "You study the schematics etched into the altar...")
+
+ var/list/options = list("Eldritch Whetstone" = radial_whetstone, "Construct Shell" = radial_shell, "Flask of Unholy Water" = radial_unholy_water)
+ var/choice = show_radial_menu(user, src, options, custom_check = CALLBACK(src, .proc/check_menu, user), require_near = TRUE, tooltips = TRUE)
+
+ var/reward
switch(choice)
if("Eldritch Whetstone")
- pickedtype += /obj/item/sharpener/cult
+ reward = /obj/item/sharpener/cult
if("Construct Shell")
- pickedtype += /obj/structure/constructshell
+ reward = /obj/structure/constructshell
if("Flask of Unholy Water")
- pickedtype += /obj/item/reagent_containers/glass/beaker/unholywater
- if(src && !QDELETED(src) && anchored && pickedtype && Adjacent(user) && !user.incapacitated() && iscultist(user) && cooldowntime <= world.time)
+ reward = /obj/item/reagent_containers/glass/beaker/unholywater
+
+ if(!QDELETED(src) && reward && check_menu(user))
cooldowntime = world.time + 2400
- for(var/N in pickedtype)
- new N(get_turf(src))
- to_chat(user, "You kneel before the altar and your faith is rewarded with the [choice]!")
+ new reward(get_turf(src))
+ to_chat(user, "You kneel before the altar and your faith is rewarded with the [choice]!")
/obj/structure/destructible/cult/forge
name = "daemon forge"
@@ -116,9 +134,14 @@
light_color = LIGHT_COLOR_LAVA
break_message = "The force breaks apart into shards with a howling scream!"
-/obj/structure/destructible/cult/forge/attack_hand(mob/living/user)
+ var/static/image/radial_flagellant = image(icon = 'icons/obj/clothing/suits.dmi', icon_state = "cultrobes")
+ var/static/image/radial_shielded = image(icon = 'icons/obj/clothing/suits.dmi', icon_state = "cult_armor")
+ var/static/image/radial_mirror = image(icon = 'icons/obj/items_and_weapons.dmi', icon_state = "mirror_shield")
+
+/obj/structure/destructible/cult/forge/ui_interact(mob/user)
. = ..()
- if(.)
+
+ if(!user.canUseTopic(src, TRUE))
return
if(!iscultist(user))
to_chat(user, "The heat radiating from [src] pushes you back.")
@@ -129,24 +152,26 @@
if(cooldowntime > world.time)
to_chat(user, "The magic in [src] is weak, it will be ready to use again in [DisplayTimeText(cooldowntime - world.time)].")
return
- var/choice
- if(user.mind.has_antag_datum(/datum/antagonist/cult/master))
- choice = alert(user,"You study the schematics etched into the forge...",,"Shielded Robe","Flagellant's Robe","Mirror Shield")
- else
- choice = alert(user,"You study the schematics etched into the forge...",,"Shielded Robe","Flagellant's Robe","Mirror Shield")
- var/list/pickedtype = list()
+
+ to_chat(user, "You study the schematics etched into the forge...")
+
+
+ var/list/options = list("Shielded Robe" = radial_shielded, "Flagellant's Robe" = radial_flagellant, "Mirror Shield" = radial_mirror)
+ var/choice = show_radial_menu(user, src, options, custom_check = CALLBACK(src, .proc/check_menu, user), require_near = TRUE, tooltips = TRUE)
+
+ var/reward
switch(choice)
if("Shielded Robe")
- pickedtype += /obj/item/clothing/suit/hooded/cultrobes/cult_shield
+ reward = /obj/item/clothing/suit/hooded/cultrobes/cult_shield
if("Flagellant's Robe")
- pickedtype += /obj/item/clothing/suit/hooded/cultrobes/berserker
+ reward = /obj/item/clothing/suit/hooded/cultrobes/berserker
if("Mirror Shield")
- pickedtype += /obj/item/shield/mirror
- if(src && !QDELETED(src) && anchored && pickedtype && Adjacent(user) && !user.incapacitated() && iscultist(user) && cooldowntime <= world.time)
+ reward = /obj/item/shield/mirror
+
+ if(!QDELETED(src) && reward && check_menu(user))
cooldowntime = world.time + 2400
- for(var/N in pickedtype)
- new N(get_turf(src))
- to_chat(user, "You work the forge as dark knowledge guides your hands, creating the [choice]!")
+ new reward(get_turf(src))
+ to_chat(user, "You work the forge as dark knowledge guides your hands, creating the [choice]!")
@@ -188,7 +213,7 @@
var/mob/living/simple_animal/M = L
if(M.health < M.maxHealth)
M.adjustHealth(-3)
- if(ishuman(L) && L.blood_volume < BLOOD_VOLUME_NORMAL)
+ if(ishuman(L) && L.blood_volume < (BLOOD_VOLUME_NORMAL * L.blood_ratio))
L.blood_volume += 1.0
CHECK_TICK
if(last_corrupt <= world.time)
@@ -234,9 +259,14 @@
light_color = LIGHT_COLOR_FIRE
break_message = "The books and tomes of the archives burn into ash as the desk shatters!"
-/obj/structure/destructible/cult/tome/attack_hand(mob/living/user)
+ var/static/image/radial_blindfold = image(icon = 'icons/obj/clothing/glasses.dmi', icon_state = "blindfold")
+ var/static/image/radial_curse = image(icon = 'icons/obj/cult.dmi', icon_state ="shuttlecurse")
+ var/static/image/radial_veilwalker = image(icon = 'icons/obj/cult.dmi', icon_state ="shifter")
+
+/obj/structure/destructible/cult/tome/ui_interact(mob/user)
. = ..()
- if(.)
+
+ if(!user.canUseTopic(src, TRUE))
return
if(!iscultist(user))
to_chat(user, "These books won't open and it hurts to even try and read the covers.")
@@ -247,21 +277,27 @@
if(cooldowntime > world.time)
to_chat(user, "The magic in [src] is weak, it will be ready to use again in [DisplayTimeText(cooldowntime - world.time)].")
return
- var/choice = alert(user,"You flip through the black pages of the archives...",,"Zealot's Blindfold","Shuttle Curse","Veil Walker Set")
- var/list/pickedtype = list()
+
+ to_chat(user, "You flip through the black pages of the archives...")
+
+ var/list/options = list("Zealot's Blindfold" = radial_blindfold, "Shuttle Curse" = radial_curse, "Veil Walker Set" = radial_veilwalker)
+ var/choice = show_radial_menu(user, src, options, custom_check = CALLBACK(src, .proc/check_menu, user), require_near = TRUE, tooltips = TRUE)
+
+ var/reward
switch(choice)
if("Zealot's Blindfold")
- pickedtype += /obj/item/clothing/glasses/hud/health/night/cultblind
+ reward = /obj/item/clothing/glasses/hud/health/night/cultblind
if("Shuttle Curse")
- pickedtype += /obj/item/shuttle_curse
+ reward = /obj/item/shuttle_curse
if("Veil Walker Set")
- pickedtype += /obj/item/cult_shift
- pickedtype += /obj/item/flashlight/flare/culttorch
- if(src && !QDELETED(src) && anchored && pickedtype.len && Adjacent(user) && !user.incapacitated() && iscultist(user) && cooldowntime <= world.time)
+ reward = /obj/effect/spawner/bundle/veil_walker
+ if(!QDELETED(src) && reward && check_menu(user))
cooldowntime = world.time + 2400
- for(var/N in pickedtype)
- new N(get_turf(src))
- to_chat(user, "You summon the [choice] from the archives!")
+ new reward(get_turf(src))
+ to_chat(user, "You summon the [choice] from the archives!")
+
+/obj/effect/spawner/bundle/veil_walker
+ items = list(/obj/item/cult_shift, /obj/item/flashlight/flare/culttorch)
/obj/effect/gateway
name = "gateway"
diff --git a/code/modules/antagonists/cult/runes.dm b/code/modules/antagonists/cult/runes.dm
index 2f3a039e70..2e233d26e4 100644
--- a/code/modules/antagonists/cult/runes.dm
+++ b/code/modules/antagonists/cult/runes.dm
@@ -61,8 +61,8 @@ Runes can either be invoked by one's self or with many different cultists. Each
if(do_after(user, 15, target = src))
to_chat(user, "You carefully erase the [lowertext(cultist_name)] rune.")
qdel(src)
- else if(istype(I, /obj/item/nullrod))
- user.say("BEGONE FOUL MAGIKS!!", forced = "nullrod")
+ else if(istype(I, /obj/item/storage/book/bible) || istype(I, /obj/item/nullrod))
+ user.say("BEGONE FOUL MAGICKS!!", forced = "bible")
to_chat(user, "You disrupt the magic of [src] with [I].")
qdel(src)
@@ -185,9 +185,6 @@ structure_check() searches for nearby cultist structures required for the invoca
color = RUNE_COLOR_OFFER
req_cultists = 1
rune_in_use = FALSE
- var/mob/living/currentconversionman
- var/conversiontimeout
- var/conversionresult
/obj/effect/rune/convert/do_invoke_glow()
return
@@ -233,36 +230,18 @@ structure_check() searches for nearby cultist structures required for the invoca
addtimer(CALLBACK(src, /atom/proc/update_atom_colour), 5)
Cult_team.check_size() // Triggers the eye glow or aura effects if the cult has grown large enough relative to the crew
rune_in_use = FALSE
+
/obj/effect/rune/convert/proc/do_convert(mob/living/convertee, list/invokers)
if(invokers.len < 2)
for(var/M in invokers)
- to_chat(M, "You need at least two invokers to convert [convertee]!")
+ to_chat(M, "You need at least two invokers to convert [convertee]!")
log_game("Offer rune failed - tried conversion with one invoker")
return 0
- if(convertee.anti_magic_check(TRUE, TRUE))
+ if(convertee.anti_magic_check(TRUE, TRUE, FALSE, 0)) //Not chargecost because it can be spammed
for(var/M in invokers)
to_chat(M, "Something is shielding [convertee]'s mind!")
log_game("Offer rune failed - convertee had anti-magic")
return 0
- to_chat(convertee, "Your blood pulses. Your head throbs. The world goes red. All at once you are aware of a horrible, horrible, truth. The veil of reality has been ripped away \
- and something evil takes root.")
- to_chat(convertee, "Do you wish to embrace the Geometer of Blood? Click here to become a follower of Nar'sie. Or you could choose to continue resisting and suffer a fate worse than death...")
- currentconversionman = convertee
- conversiontimeout = world.time + (10 SECONDS)
- convertee.Stun(100)
- ADD_TRAIT(convertee, TRAIT_MUTE, "conversionrune")
- conversionresult = FALSE
- while(world.time < conversiontimeout && convertee && !conversionresult)
- stoplag(1)
- currentconversionman = null
- if(!convertee)
- return FALSE
- REMOVE_TRAIT(convertee, TRAIT_MUTE, "conversionrune")
- if(get_turf(convertee) != get_turf(src))
- return FALSE
- if(!conversionresult)
- do_sacrifice(convertee, invokers, TRUE)
- return FALSE
var/brutedamage = convertee.getBruteLoss()
var/burndamage = convertee.getFireLoss()
if(brutedamage || burndamage)
@@ -274,6 +253,8 @@ structure_check() searches for nearby cultist structures required for the invoca
SSticker.mode.add_cultist(convertee.mind, 1)
new /obj/item/melee/cultblade/dagger(get_turf(src))
convertee.mind.special_role = ROLE_CULTIST
+ to_chat(convertee, "Your blood pulses. Your head throbs. The world goes red. All at once you are aware of a horrible, horrible, truth. The veil of reality has been ripped away \
+ and something evil takes root.")
to_chat(convertee, "Assist your new compatriots in their dark dealings. Your goal is theirs, and theirs is yours. You serve the Geometer above all else. Bring it back.\
")
if(ishuman(convertee))
@@ -283,7 +264,7 @@ structure_check() searches for nearby cultist structures required for the invoca
H.cultslurring = 0
return 1
-/obj/effect/rune/convert/proc/do_sacrifice(mob/living/sacrificial, list/invokers, force_a_sac)
+/obj/effect/rune/convert/proc/do_sacrifice(mob/living/sacrificial, list/invokers)
var/mob/living/first_invoker = invokers[1]
if(!first_invoker)
return FALSE
@@ -293,7 +274,7 @@ structure_check() searches for nearby cultist structures required for the invoca
var/big_sac = FALSE
- if(!force_a_sac && (((ishuman(sacrificial) || iscyborg(sacrificial)) && sacrificial.stat != DEAD) || C.cult_team.is_sacrifice_target(sacrificial.mind)) && invokers.len < 3)
+ if((((ishuman(sacrificial) || iscyborg(sacrificial)) && sacrificial.stat != DEAD) || C.cult_team.is_sacrifice_target(sacrificial.mind)) && invokers.len < 3)
for(var/M in invokers)
to_chat(M, "[sacrificial] is too greatly linked to the world! You need three acolytes!")
log_game("Offer rune failed - not enough acolytes and target is living or sac target")
@@ -333,14 +314,6 @@ structure_check() searches for nearby cultist structures required for the invoca
sacrificial.gib()
return TRUE
-/obj/effect/rune/convert/Topic(href, href_list)
- if(href_list["signmeup"])
- if(currentconversionman == usr)
- conversionresult = TRUE
- else
- to_chat(usr, "Your fate has already been set in stone.")
-
-
/obj/effect/rune/empower
cultist_name = "Empower"
cultist_desc = "allows cultists to prepare greater amounts of blood magic at far less of a cost."
@@ -414,6 +387,7 @@ structure_check() searches for nearby cultist structures required for the invoca
return
var/movedsomething = FALSE
var/moveuserlater = FALSE
+ var/movesuccess = FALSE
for(var/atom/movable/A in T)
if(ishuman(A))
new /obj/effect/temp_visual/dir_setting/cult/phase/out(T, A.dir)
@@ -424,20 +398,26 @@ structure_check() searches for nearby cultist structures required for the invoca
continue
if(!A.anchored)
movedsomething = TRUE
- A.forceMove(target)
+ if(do_teleport(A, target, forceMove = TRUE, channel = TELEPORT_CHANNEL_CULT))
+ movesuccess = TRUE
if(movedsomething)
..()
- visible_message("There is a sharp crack of inrushing air, and everything above the rune disappears!", null, "You hear a sharp crack.")
- to_chat(user, "You[moveuserlater ? "r vision blurs, and you suddenly appear somewhere else":" send everything above the rune away"].")
if(moveuserlater)
- user.forceMove(target)
+ if(do_teleport(user, target, channel = TELEPORT_CHANNEL_CULT))
+ movesuccess = TRUE
+ if(movesuccess)
+ visible_message("There is a sharp crack of inrushing air, and everything above the rune disappears!", null, "You hear a sharp crack.")
+ to_chat(user, "You[moveuserlater ? "r vision blurs, and you suddenly appear somewhere else":" send everything above the rune away"].")
+ else
+ to_chat(user, "You[moveuserlater ? "r vision blurs briefly, but nothing happens":" try send everything above the rune away, but the teleportation fails"].")
if(is_mining_level(z) && !is_mining_level(target.z)) //No effect if you stay on lavaland
actual_selected_rune.handle_portal("lava")
else
var/area/A = get_area(T)
if(A.map_name == "Space")
actual_selected_rune.handle_portal("space", T)
- target.visible_message("There is a boom of outrushing air as something appears above the rune!", null, "You hear a boom.")
+ if(movesuccess)
+ target.visible_message("There is a boom of outrushing air as something appears above the rune!", null, "You hear a boom.")
else
fail_invoke()
@@ -595,7 +575,7 @@ structure_check() searches for nearby cultist structures required for the invoca
to_chat(mob_to_revive.mind, "Your physical form has been taken over by another soul due to your inactivity! Ahelp if you wish to regain your form.")
message_admins("[key_name_admin(C)] has taken control of ([key_name_admin(mob_to_revive)]) to replace an AFK player.")
mob_to_revive.ghostize(0)
- mob_to_revive.key = C.key
+ C.transfer_ckey(mob_to_revive, FALSE)
else
fail_invoke()
return
@@ -890,7 +870,7 @@ structure_check() searches for nearby cultist structures required for the invoca
visible_message("A cloud of red mist forms above [src], and from within steps... a [new_human.gender == FEMALE ? "wo":""]man.")
to_chat(user, "Your blood begins flowing into [src]. You must remain in place and conscious to maintain the forms of those summoned. This will hurt you slowly but surely...")
var/obj/structure/emergency_shield/invoker/N = new(T)
- new_human.key = ghost_to_spawn.key
+ ghost_to_spawn.transfer_ckey(new_human, FALSE)
SSticker.mode.add_cultist(new_human.mind, 0)
to_chat(new_human, "You are a servant of the Geometer. You have been made semi-corporeal by the cult of Nar'Sie, and you are to serve them at all costs.")
diff --git a/code/modules/antagonists/devil/devil.dm b/code/modules/antagonists/devil/devil.dm
index 3f2bd003a3..dc649025d2 100644
--- a/code/modules/antagonists/devil/devil.dm
+++ b/code/modules/antagonists/devil/devil.dm
@@ -539,10 +539,10 @@ GLOBAL_LIST_INIT(devil_suffix, list(" the Red", " the Soulless", " the Master",
var/list/parts = list()
parts += "The devil's true name is: [truename]"
parts += "The devil's bans were:"
- parts += "[GLOB.TAB][GLOB.lawlorify[LORE][ban]]"
- parts += "[GLOB.TAB][GLOB.lawlorify[LORE][bane]]"
- parts += "[GLOB.TAB][GLOB.lawlorify[LORE][obligation]]"
- parts += "[GLOB.TAB][GLOB.lawlorify[LORE][banish]]"
+ parts += "[FOURSPACES][GLOB.lawlorify[LORE][ban]]"
+ parts += "[FOURSPACES][GLOB.lawlorify[LORE][bane]]"
+ parts += "[FOURSPACES][GLOB.lawlorify[LORE][obligation]]"
+ parts += "[FOURSPACES][GLOB.lawlorify[LORE][banish]]"
return parts.Join(" ")
/datum/antagonist/devil/roundend_report()
diff --git a/code/modules/antagonists/devil/true_devil/_true_devil.dm b/code/modules/antagonists/devil/true_devil/_true_devil.dm
index 1df81a797b..afe439f02c 100644
--- a/code/modules/antagonists/devil/true_devil/_true_devil.dm
+++ b/code/modules/antagonists/devil/true_devil/_true_devil.dm
@@ -146,7 +146,7 @@
/mob/living/carbon/true_devil/attack_ghost(mob/dead/observer/user as mob)
if(ascended || user.mind.soulOwner == src.mind)
var/mob/living/simple_animal/imp/S = new(get_turf(loc))
- S.key = user.key
+ user.transfer_ckey(S, FALSE)
S.mind.assigned_role = "Imp"
S.mind.special_role = "Imp"
var/datum/objective/newobjective = new
diff --git a/code/modules/antagonists/disease/disease_abilities.dm b/code/modules/antagonists/disease/disease_abilities.dm
index 07cd3030b1..dc2249006c 100644
--- a/code/modules/antagonists/disease/disease_abilities.dm
+++ b/code/modules/antagonists/disease/disease_abilities.dm
@@ -5,25 +5,48 @@ is currently following.
*/
GLOBAL_LIST_INIT(disease_ability_singletons, list(
- new /datum/disease_ability/action/cough(),
- new /datum/disease_ability/action/sneeze(),
- new /datum/disease_ability/action/infect(),
- new /datum/disease_ability/symptom/cough(),
- new /datum/disease_ability/symptom/sneeze(),\
- new /datum/disease_ability/symptom/hallucigen(),
- new /datum/disease_ability/symptom/choking(),
- new /datum/disease_ability/symptom/confusion(),
- new /datum/disease_ability/symptom/youth(),
- new /datum/disease_ability/symptom/vomit(),
- new /datum/disease_ability/symptom/voice_change(),
- new /datum/disease_ability/symptom/visionloss(),
- new /datum/disease_ability/symptom/viraladaptation(),
- new /datum/disease_ability/symptom/vitiligo(),
- new /datum/disease_ability/symptom/sensory_restoration(),
- new /datum/disease_ability/symptom/itching(),
- new /datum/disease_ability/symptom/weight_loss(),
- new /datum/disease_ability/symptom/metabolism_heal(),
- new /datum/disease_ability/symptom/coma_heal()
+ new /datum/disease_ability/action/cough,
+ new /datum/disease_ability/action/sneeze,
+ new /datum/disease_ability/action/infect,
+ new /datum/disease_ability/symptom/mild/cough,
+ new /datum/disease_ability/symptom/mild/sneeze,
+ new /datum/disease_ability/symptom/medium/shedding,
+ new /datum/disease_ability/symptom/medium/beard,
+ new /datum/disease_ability/symptom/medium/hallucigen,
+ new /datum/disease_ability/symptom/medium/choking,
+ new /datum/disease_ability/symptom/medium/confusion,
+ new /datum/disease_ability/symptom/medium/vomit,
+ new /datum/disease_ability/symptom/medium/voice_change,
+ new /datum/disease_ability/symptom/medium/visionloss,
+ new /datum/disease_ability/symptom/medium/deafness,
+ new /datum/disease_ability/symptom/powerful/narcolepsy,
+ new /datum/disease_ability/symptom/medium/fever,
+ new /datum/disease_ability/symptom/medium/shivering,
+ new /datum/disease_ability/symptom/medium/headache,
+ new /datum/disease_ability/symptom/medium/nano_boost,
+ new /datum/disease_ability/symptom/medium/nano_destroy,
+ new /datum/disease_ability/symptom/medium/viraladaptation,
+ new /datum/disease_ability/symptom/medium/viralevolution,
+ new /datum/disease_ability/symptom/medium/vitiligo,
+ new /datum/disease_ability/symptom/medium/revitiligo,
+ new /datum/disease_ability/symptom/medium/itching,
+ new /datum/disease_ability/symptom/medium/heal/weight_loss,
+ new /datum/disease_ability/symptom/medium/heal/sensory_restoration,
+ new /datum/disease_ability/symptom/medium/heal/mind_restoration,
+ new /datum/disease_ability/symptom/powerful/fire,
+ new /datum/disease_ability/symptom/powerful/flesh_eating,
+// new /datum/disease_ability/symptom/powerful/genetic_mutation,
+ new /datum/disease_ability/symptom/powerful/inorganic_adaptation,
+ new /datum/disease_ability/symptom/powerful/heal/starlight,
+ new /datum/disease_ability/symptom/powerful/heal/oxygen,
+ new /datum/disease_ability/symptom/powerful/heal/chem,
+ new /datum/disease_ability/symptom/powerful/heal/metabolism,
+ new /datum/disease_ability/symptom/powerful/heal/dark,
+ new /datum/disease_ability/symptom/powerful/heal/water,
+ new /datum/disease_ability/symptom/powerful/heal/plasma,
+ new /datum/disease_ability/symptom/powerful/heal/radiation,
+ new /datum/disease_ability/symptom/powerful/heal/coma,
+ new /datum/disease_ability/symptom/powerful/youth
))
/datum/disease_ability
@@ -54,7 +77,13 @@ GLOBAL_LIST_INIT(disease_ability_singletons, list(
stage_speed += initial(S.stage_speed)
transmittable += initial(S.transmittable)
threshold_block += "
"
+ mutant_category = 0
+
+ if("meat_type" in pref_species.default_features)
+ if(!mutant_category)
+ dat += APPEARANCE_CATEGORY_COLUMN
+
+ dat += "
Meat Type
"
+
+ dat += "[features["meat_type"]]"
+
mutant_category++
if(mutant_category >= MAX_MUTANT_ROWS)
dat += ""
@@ -465,6 +478,8 @@ GLOBAL_LIST_EMPTY(preferences_datums)
dat += "
Horns
"
dat += "[features["horns"]]"
+ dat += "Change "
+
mutant_category++
if(mutant_category >= MAX_MUTANT_ROWS)
@@ -531,6 +546,7 @@ GLOBAL_LIST_EMPTY(preferences_datums)
if(mutant_category >= MAX_MUTANT_ROWS)
dat += ""
mutant_category = 0
+
if("ears" in pref_species.default_features)
if(!mutant_category)
dat += APPEARANCE_CATEGORY_COLUMN
@@ -543,6 +559,7 @@ GLOBAL_LIST_EMPTY(preferences_datums)
if(mutant_category >= MAX_MUTANT_ROWS)
dat += ""
mutant_category = 0
+
if("mam_snouts" in pref_species.default_features)
if(!mutant_category)
dat += APPEARANCE_CATEGORY_COLUMN
@@ -567,14 +584,31 @@ GLOBAL_LIST_EMPTY(preferences_datums)
if(mutant_category >= MAX_MUTANT_ROWS)
dat += ""
mutant_category = 0
- if("moth_wings" in pref_species.default_features)
+ if("deco_wings" in pref_species.default_features)
if(!mutant_category)
dat += APPEARANCE_CATEGORY_COLUMN
- dat += "
Moth wings
"
+ dat += "
Decorative wings
"
- dat += "[features["moth_wings"]]"
+ dat += "[features["deco_wings"]]"
+ if("insect_wings" in pref_species.default_features)
+ if(!mutant_category)
+ dat += APPEARANCE_CATEGORY_COLUMN
+ dat += "
Insect wings
"
+
+ dat += "[features["insect_wings"]]"
+ mutant_category++
+ if(mutant_category >= MAX_MUTANT_ROWS)
+ dat += ""
+ mutant_category = 0
+ if("insect_fluff" in pref_species.default_features)
+ if(!mutant_category)
+ dat += APPEARANCE_CATEGORY_COLUMN
+
+ dat += "
Insect Fluff
"
+
+ dat += "[features["insect_fluff"]]"
mutant_category++
if(mutant_category >= MAX_MUTANT_ROWS)
dat += ""
@@ -674,9 +708,16 @@ GLOBAL_LIST_EMPTY(preferences_datums)
dat += "
"
dat += "
Clothing & Equipment
"
dat += "Underwear:[underwear]"
+ if(UNDIE_COLORABLE(GLOB.underwear_list[underwear]))
+ dat += "Underwear Color:Change "
dat += "Undershirt:[undershirt]"
+ if(UNDIE_COLORABLE(GLOB.undershirt_list[undershirt]))
+ dat += "Undershirt Color:Change "
dat += "Socks:[socks]"
+ if(UNDIE_COLORABLE(GLOB.socks_list[socks]))
+ dat += "Socks Color:Change "
dat += "Backpack:[backbag]"
+ dat += "Jumpsuit: [jumpsuit_style] "
dat += "Uplink Location:[uplink_spawn_loc]"
dat += "
diff --git a/code/modules/goonchat/browserassets/js/browserOutput.js b/code/modules/goonchat/browserassets/js/browserOutput.js
index 33553d765e..abd05d29ed 100644
--- a/code/modules/goonchat/browserassets/js/browserOutput.js
+++ b/code/modules/goonchat/browserassets/js/browserOutput.js
@@ -35,6 +35,7 @@ var opts = {
'wasd': false, //Is the user in wasd mode?
'priorChatHeight': 0, //Thing for height-resizing detection
'restarting': false, //Is the round restarting?
+ 'colorPreset': 0, // index in the color presets list.
//Options menu
'selectedSubLoop': null, //Contains the interval loop for closing the selected sub menu
@@ -72,6 +73,14 @@ var opts = {
};
+// Array of names for chat display color presets.
+// If not set to normal, a CSS file `browserOutput_${name}.css` will be added to the head.
+var colorPresets = [
+ 'normal',
+ 'light',
+ 'dark'
+]
+
function clamp(val, min, max) {
return Math.max(min, Math.min(val, max))
}
@@ -95,6 +104,12 @@ if (typeof String.prototype.trim !== 'function') {
};
}
+function updateColorPreset() {
+ var el = $("#colorPresetLink")[0];
+ el.href = "browserOutput_"+colorPresets[opts.colorPreset]+".css";
+ runByond('?_src_=chat&proc=colorPresetPost&preset='+colorPresets[opts.colorPreset]);
+}
+
// Linkify the contents of a node, within its parent.
function linkify(parent, insertBefore, text) {
var start = 0;
@@ -601,6 +616,7 @@ $(function() {
'shighlightColor': getCookie('highlightcolor'),
'smusicVolume': getCookie('musicVolume'),
'smessagecombining': getCookie('messagecombining'),
+ 'scolorPreset': getCookie('colorpreset'),
};
if (savedConfig.sfontSize) {
@@ -636,6 +652,13 @@ $(function() {
opts.highlightColor = savedConfig.shighlightColor;
internalOutput('Loaded highlight color of: '+savedConfig.shighlightColor+'', 'internal');
}
+
+ if (savedConfig.scolorPreset) {
+ opts.colorPreset = Number(savedConfig.scolorPreset);
+ updateColorPreset();
+ internalOutput('Loaded color preset of: '+colorPresets[opts.colorPreset]+'', 'internal');
+ }
+
if (savedConfig.smusicVolume) {
var newVolume = clamp(savedConfig.smusicVolume, 0, 100);
$('#adminMusic').prop('volume', newVolume / 100);
@@ -655,8 +678,6 @@ $(function() {
opts.messageCombining = true;
}
}
-
-
(function() {
var dataCookie = getCookie('connData');
if (dataCookie) {
@@ -823,7 +844,6 @@ $(function() {
$('#toggleOptions').click(function(e) {
handleToggleClick($subOptions, $(this));
});
-
$('#toggleAudio').click(function(e) {
handleToggleClick($subAudio, $(this));
});
@@ -974,6 +994,13 @@ $(function() {
opts.messageCount = 0;
});
+ $('#changeColorPreset').click(function() {
+ opts.colorPreset = (opts.colorPreset+1) % colorPresets.length;
+ updateColorPreset();
+ setCookie('colorpreset', opts.colorPreset, 365);
+ internalOutput('Changed color preset to: '+colorPresets[opts.colorPreset]);
+ });
+
$('#musicVolumeSpan').hover(function() {
$('#musicVolumeText').addClass('hidden');
$('#musicVolume').removeClass('hidden');
diff --git a/code/modules/holiday/halloween/bartholomew.dm b/code/modules/holiday/halloween/bartholomew.dm
new file mode 100644
index 0000000000..129f4e29b6
--- /dev/null
+++ b/code/modules/holiday/halloween/bartholomew.dm
@@ -0,0 +1,148 @@
+/obj/effect/landmark/barthpot
+ name = "barthpot"
+
+/obj/item/barthpot
+ name = "Bartholomew"
+ icon = 'icons/obj/halloween_items.dmi'
+ icon_state = "barthpot"
+ anchored = TRUE
+ var/items_list = list()
+ speech_span = "spooky"
+ var/active = TRUE
+
+/obj/item/barthpot/Destroy()
+ var/obj/item/barthpot/n = new src(loc)
+ n.items_list = items_list
+ ..()
+
+
+/obj/item/barthpot/attackby(obj/item/I, mob/user, params)
+ if(!active)
+ say("Meow!")
+ return
+ for(var/I2 in items_list)
+ if(istype(I, I2))
+ qdel(I)
+ new /obj/item/reagent_containers/food/snacks/special_candy(loc)
+ to_chat(user, "You add the [I.name] to the pot and watch as it melts into the mixture, a candy crystalising in it's wake.")
+ say("Hooray! Thank you!")
+ items_list -= I2
+ return
+ say("It doesn't seem like that's magical enough!")
+
+/obj/item/barthpot/attack_hand(mob/user)
+ if(!active)
+ say("Meow!")
+ return
+ say("Hello there, I'm Bartholomew, Jacqueline's Familiar.")
+ sleep(20)
+ say("I'm currently seeking items to put into my pot, if we get the right items, it should crystalise into a magic candy!")
+ if(!iscarbon(user))
+ say("Though... I'm not sure you can help me.")
+
+ var/message = "From what I can tell, "
+ if(LAZYLEN(items_list) < 5)
+ generate_items()
+ for(var/I2 in items_list)
+ if(!I2)
+ items_list -= I2
+ continue
+ var/obj/item/I3 = new I2
+ message += "a [I3.name], "
+ message += "currently seem to have the most magic potential."
+ sleep(15)
+ say("[message]")
+
+/obj/item/barthpot/proc/generate_items()
+ var/length = LAZYLEN(items_list)
+ var/rand_items = list(/obj/item/bodybag = 1,
+ /obj/item/clothing/glasses/meson = 2,
+ /obj/item/clothing/glasses/sunglasses = 1,
+ /obj/item/clothing/gloves/color/fyellow = 1,
+ /obj/item/clothing/head/hardhat = 1,
+ /obj/item/clothing/head/hardhat/red = 1,
+ /obj/item/clothing/head/that = 1,
+ /obj/item/clothing/head/ushanka = 1,
+ /obj/item/clothing/head/welding = 1,
+ /obj/item/clothing/mask/gas = 15,
+ /obj/item/clothing/suit/hazardvest = 1,
+ /obj/item/clothing/under/rank/vice = 1,
+ /obj/item/clothing/suit/hooded/flashsuit = 2,
+ /obj/item/clothing/accessory/medal/greytide = 1,
+ /obj/item/assembly/prox_sensor = 4,
+ /obj/item/assembly/timer = 3,
+ /obj/item/flashlight = 4,
+ /obj/item/flashlight/pen = 1,
+ /obj/effect/spawner/lootdrop/glowstick = 4,
+ /obj/effect/spawner/lootdrop/mre = 3,
+ /obj/item/multitool = 2,
+ /obj/item/radio/off = 2,
+ /obj/item/t_scanner = 5,
+ /obj/item/airlock_painter = 1,
+ /obj/item/stack/cable_coil/ = 4,
+ /obj/item/stack/medical/bruise_pack = 1,
+ /obj/item/stack/rods = 3,
+ /obj/item/stack/sheet/cardboard = 2,
+ /obj/item/stack/sheet/metal = 1,
+ /obj/item/stack/sheet/mineral/plasma = 1,
+ /obj/item/stack/sheet/rglass = 1,
+ /obj/item/book/manual/wiki/engineering_construction = 1,
+ /obj/item/book/manual/wiki/engineering_hacking = 1,
+ /obj/item/clothing/head/cone = 1,
+ /obj/item/coin/silver = 1,
+ /obj/item/coin/twoheaded = 1,
+ /obj/item/poster/random_contraband = 1,
+ /obj/item/poster/random_official = 1,
+ /obj/item/crowbar = 1,
+ /obj/item/crowbar/red = 1,
+ /obj/item/extinguisher = 11,
+ /obj/item/hand_labeler = 1,
+ /obj/item/paper/crumpled = 1,
+ /obj/item/pen = 1,
+ /obj/item/reagent_containers/spray/pestspray = 1,
+ /obj/item/reagent_containers/rag = 3,
+ /obj/item/stock_parts/cell = 3,
+ /obj/item/storage/belt/utility = 2,
+ /obj/item/storage/box = 2,
+ /obj/item/storage/box/cups = 1,
+ /obj/item/storage/box/donkpockets = 1,
+ /obj/item/storage/box/lights/mixed = 3,
+ /obj/item/storage/box/hug/medical = 1,
+ /obj/item/storage/fancy/cigarettes/dromedaryco = 1,
+ /obj/item/storage/toolbox/mechanical = 1,
+ /obj/item/screwdriver = 3,
+ /obj/item/tank/internals/emergency_oxygen = 2,
+ /obj/item/vending_refill/cola = 1,
+ /obj/item/weldingtool = 3,
+ /obj/item/wirecutters = 1,
+ /obj/item/wrench = 4,
+ /obj/item/relic = 3,
+ /obj/item/weaponcrafting/receiver = 2,
+ /obj/item/clothing/head/cone = 2,
+ /obj/item/grenade/smokebomb = 2,
+ /obj/item/geiger_counter = 3,
+ /obj/item/reagent_containers/food/snacks/grown/citrus/orange = 1,
+ /obj/item/radio/headset = 1,
+ /obj/item/assembly/infra = 1,
+ /obj/item/assembly/igniter = 2,
+ /obj/item/assembly/signaler = 2,
+ /obj/item/assembly/mousetrap = 2,
+ /obj/item/reagent_containers/syringe = 2,
+ /obj/item/clothing/gloves = 8,
+ /obj/item/clothing/shoes/laceup = 1,
+ /obj/item/storage/secure/briefcase = 3,
+ /obj/item/storage/toolbox/artistic = 2,
+ /obj/item/toy/eightball = 1,
+ /obj/item/reagent_containers/pill/floorpill = 1,
+ /obj/item/reagent_containers/food/snacks/cannedpeaches/maint = 2,
+ /obj/item/clothing/shoes = 2)
+ if(length == 5)
+ return TRUE
+ //var/metalist = pickweight(GLOB.maintenance_loot)
+ for(var/i = length, i <= 5, i+=1)
+ var/obj/item = pickweight(rand_items)
+ if(!item)
+ i-=1
+ continue
+ items_list += item
+ return TRUE
diff --git a/code/modules/holiday/halloween.dm b/code/modules/holiday/halloween/halloween.dm
similarity index 92%
rename from code/modules/holiday/halloween.dm
rename to code/modules/holiday/halloween/halloween.dm
index a27db8dd38..5635994a7a 100644
--- a/code/modules/holiday/halloween.dm
+++ b/code/modules/holiday/halloween/halloween.dm
@@ -5,19 +5,27 @@
//spooky recipes
-/datum/recipe/sugarcookie/spookyskull
- reagents = list("flour" = 5, "sugar" = 5, "milk" = 5)
- items = list(
- /obj/item/reagent_containers/food/snacks/egg,
+/datum/crafting_recipe/food/sugarcookie/spookyskull
+ time = 15
+ name = "Sugar cookie"
+ reqs = list(
+ /datum/reagent/consumable/sugar = 5,
+ /obj/item/reagent_containers/food/snacks/pastrybase = 1
)
result = /obj/item/reagent_containers/food/snacks/sugarcookie/spookyskull
+ subcategory = CAT_PASTRY
-/datum/recipe/sugarcookie/spookycoffin
- reagents = list("flour" = 5, "sugar" = 5, "coffee" = 5)
- items = list(
- /obj/item/reagent_containers/food/snacks/egg,
+/datum/crafting_recipe/food/sugarcookie/spookycoffin
+ time = 15
+ name = "Sugar cookie"
+ reqs = list(
+ /datum/reagent/consumable/sugar = 5,
+ /datum/reagent/consumable/coffee = 5,
+ /obj/item/reagent_containers/food/snacks/pastrybase = 1
)
result = /obj/item/reagent_containers/food/snacks/sugarcookie/spookycoffin
+ subcategory = CAT_PASTRY
+
//////////////////////////////
//Spookoween trapped closets//
@@ -34,12 +42,12 @@
var/trapped = 0
var/mob/trapped_mob
-/obj/structure/closet/initialize()
+/obj/structure/closet/Initialize()
..()
if(prob(30))
set_spooky_trap()
-/obj/structure/closet/dump_contents()
+/obj/structure/closet/dump_contents(var/override = TRUE)
..()
trigger_spooky_trap()
@@ -256,6 +264,7 @@
desc = "A standard miniature energy crossbow that uses a hard-light projector to transform bolts into candy corn. Happy Halloween!"
category = "Holiday"
item = /obj/item/gun/energy/kinetic_accelerator/crossbow/halloween
+ cost = 12
surplus = 0
/datum/uplink_item/device_tools/emag/hack_o_lantern
diff --git a/code/modules/holiday/halloween/jacqueen.dm b/code/modules/holiday/halloween/jacqueen.dm
new file mode 100644
index 0000000000..b2f69df2e4
--- /dev/null
+++ b/code/modules/holiday/halloween/jacqueen.dm
@@ -0,0 +1,442 @@
+//Conversation
+#define JACQ_HELLO (1<<0)
+#define JACQ_CANDIES (1<<1)
+#define JACQ_HEAD (1<<2)
+#define JACQ_FAR (1<<3)
+#define JACQ_WITCH (1<<4)
+#define JACQ_EXPELL (1<<5)
+#define JACQ_DATE (1<<6)
+
+/////// EVENT
+/datum/round_event_control/jacqueen
+ name = "Jacqueline's visit"
+ holidayID = "jacqueen"
+ typepath = /datum/round_event/jacqueen
+ weight = -1 //forces it to be called, regardless of weight
+ max_occurrences = 1
+ earliest_start = 0 MINUTES
+
+/datum/round_event/jacqueen/start()
+ ..()
+
+ for(var/mob/living/carbon/human/H in GLOB.carbon_list)
+ playsound(H, 'sound/spookoween/ahaha.ogg', 100, 0.25)
+
+ for(var/obj/effect/landmark/barthpot/bp in GLOB.landmarks_list)
+ new /obj/item/barthpot(bp.loc)
+ new /mob/living/simple_animal/jacq(bp.loc)
+
+/////// MOBS
+
+//Whacha doing in here like? Yae wan tae ruin ta magicks?
+/mob/living/simple_animal/jacq
+ name = "Jacqueline the Pumpqueen"
+ real_name = "Jacqueline"
+ icon = 'icons/obj/halloween_items.dmi'
+ icon_state = "jacqueline"
+ maxHealth = 25
+ health = 25
+ density = FALSE
+ speech_span = "spooky"
+ friendly = "pets"
+ response_help = "chats with"
+ var/last_poof
+ var/progression = list() //Keep track of where people are in the story.
+ var/active = TRUE //Turn this to false to keep normal mob behavour
+
+/mob/living/simple_animal/jacq/Initialize()
+ ..()
+ poof()
+
+/mob/living/simple_animal/jacq/Life()
+ ..()
+ if(!ckey)
+ if((last_poof+4 MINUTES) < world.realtime)
+ poof()
+
+/mob/living/simple_animal/jacq/Destroy() //I.e invincible
+ visible_message("[src] cackles, \"You'll nae get rid a me that easily!\"")
+ playsound(loc, 'sound/spookoween/ahaha.ogg', 100, 0.25)
+ var/mob/living/simple_animal/jacq/Jacq = new src.type(loc)
+ Jacq.progression = progression
+ ..()
+
+/mob/living/simple_animal/jacq/death() //What is alive may never die
+ visible_message("[src] cackles, \"You'll nae get rid a me that easily!\"")
+ playsound(loc, 'sound/spookoween/ahaha.ogg', 100, 0.25)
+ health = 20
+ poof()
+
+/mob/living/simple_animal/jacq/attack_hand(mob/living/carbon/human/M)
+ if(!active)
+ say("Hello there [gender_check(M)]!")
+ return ..()
+ if(!ckey)
+ canmove = FALSE
+ chit_chat(M)
+ canmove = TRUE
+ ..()
+
+/mob/living/simple_animal/jacq/attack_paw(mob/living/carbon/monkey/M)
+ if(!active)
+ say("Hello there [gender_check(M)]!")
+ return ..()
+ if(!ckey)
+ canmove = FALSE
+ chit_chat(M)
+ canmove = TRUE
+ ..()
+
+/mob/living/simple_animal/jacq/proc/poof()
+ last_poof = world.realtime
+ var/datum/reagents/R = new/datum/reagents(100)//Hey, just in case.
+ var/datum/effect_system/smoke_spread/chem/s = new()
+ R.add_reagent("secretcatchem", (10))
+ s.set_up(R, 0, loc)
+ s.start()
+ visible_message("[src] disappears in a puff of smoke!")
+ canmove = TRUE
+
+ var/hp_list = list()
+ for(var/obj/machinery/holopad/hp in world)
+ hp_list += hp
+
+ var/obj/machinery/holopad/hp = pick(hp_list)
+ if(forceMove(pick(hp.loc)))
+ return TRUE
+
+ return FALSE
+
+/mob/living/simple_animal/jacq/proc/gender_check(mob/living/carbon/C)
+ var/gender = "lamb"
+ if(C)
+ if(C.gender == MALE)
+ gender = "laddie"
+ if(C.gender == FEMALE)
+ gender = "lassie"
+ return gender
+
+//Ye wee bugger, gerrout of it. Ye've nae tae enjoy reading the code fer mae secrets like.
+/mob/living/simple_animal/jacq/proc/chit_chat(mob/living/carbon/C)
+ //Very important
+ var/gender = gender_check(C)
+ if(C)
+ if(C.gender == MALE)
+ gender = "laddie"
+ if(C.gender == FEMALE)
+ gender = "lassie"
+
+ if(!progression["[C.real_name]"] || !(progression["[C.real_name]"] & JACQ_HELLO))
+ visible_message("[src] smiles ominously at [C], \"Well halo there [gender]! Ah'm Jacqueline, tae great Pumpqueen, great tae meet ye.\"")
+ sleep(20)
+ visible_message("[src] continues, says, \"Ah'm sure yae well stunned, but ah've got nae taem fer that. Ah'm after the candies around this station. If yae get mae enoof o the wee buggers, Ah'll give ye a treat, or if yae feeling bold, Ah ken trick ye instead.\" giving [C] a wide grin.")
+ if(!progression["[C.real_name]"])
+ progression["[C.real_name]"] = NONE //TO MAKE SURE THAT THE LIST ENTRY EXISTS.
+
+ progression["[C.real_name]"] = progression["[C.real_name]"] | JACQ_HELLO
+
+ var/choices = list("Trick", "Treat", "How do I get candies?")
+ var/choice = input(C, "Trick or Treat?", "Trick or Treat?") in choices
+ switch(choice)
+ if("Trick")
+ trick(C)
+ return
+ if("Treat")
+ if(check_candies(C))
+ treat(C, gender)
+ else
+ visible_message("[src] raises an eyebrow, \"You've nae got any candies Ah want! They're the orange round ones, now bugger off an go get em first.\"")
+ return
+ if("How do I get candies?")
+ visible_message("[src] says, \"Gae find my familiar; Bartholomew. Ee's tendin the cauldron which ken bring oot t' magic energy in items scattered aroond. Knowing him, ee's probably gone tae somewhere with books.\"")
+ return
+
+/mob/living/simple_animal/jacq/proc/treat(mob/living/carbon/C, gender)
+ visible_message("[src] gives off a glowing smile, \"What ken Ah offer ye? I can magic up an object, a potion or a plushie fer ye.\"")
+ var/choices_reward = list("Object - 3 candies", "Potion - 2 candies", "Plushie - 1 candy", "Can I ask you a question instead?")
+ var/choice_reward = input(usr, "Trick or Treat?", "Trick or Treat?") in choices_reward
+
+ //rewards
+ switch(choice_reward)
+ if("Object - 3 candies")
+ if(!take_candies(C, 3))
+ visible_message("[src] raises an eyebrown, \"It's 3 candies per trinket [gender]! Thems the rules!\"")
+ return
+
+ var/new_obj = pick(subtypesof(/obj))
+ //for(var/item in blacklist)
+ // if(new_obj == item)
+ // panic()
+ var/reward = new new_obj(C.loc)
+ C.put_in_hands(reward)
+ visible_message("[src] waves her hands, magicing up a [reward] from thin air, \"There ye are [gender], enjoy! \"")
+ sleep(20)
+ poof()
+ return
+ if("Potion - 2 candies")
+ if(!take_candies(C, 2))
+ visible_message("[src] raises an eyebrow, \"It's 2 candies per potion [gender]! Thems the rules!\"")
+ return
+
+ var/reward = new /obj/item/reagent_containers/potion_container(C.loc)
+ C.put_in_hands(reward)
+ visible_message("[src] waves her hands, magicing up a [reward] from thin air, \"There ye are [gender], enjoy! \"")
+ sleep(20)
+ poof()
+ return
+ if("Plushie - 1 candy")
+ if(!take_candies(C, 1))
+ visible_message("[src] raises an eyebrow, \"It's 1 candy per plushie [gender]! Thems the rules!\"")
+ return
+
+ new /obj/item/toy/plush/random(C.loc)
+ visible_message("[src] waves her hands, magicing up a plushie from thin air, \"There ye are [gender], enjoy! \"")
+ sleep(20)
+ poof()
+ return
+
+ //chitchats!
+ if("Can I ask you a question instead?")
+ var/choices = list()
+ //Figure out where the C is in the story
+ if(!progression["[C.real_name]"]) //I really don't want to get here withoot a hello, but just to be safe
+ progression["[C.real_name]"] = NONE
+ if(!(progression["[C.real_name]"] & JACQ_FAR))
+ if(progression["[C.real_name]"] & JACQ_CANDIES)
+ choices += "You really came all this way for candy?"
+ else
+ choices += "Why do you want the candies?"
+ if(!(progression["[C.real_name]"] & JACQ_HEAD))
+ choices += "What is that on your head?"
+ if(!(progression["[C.real_name]"] & JACQ_EXPELL))
+ if(progression["[C.real_name]"] & JACQ_WITCH)
+ choices += "So you got ex-spell-ed?"
+ else
+ choices += "Are you a witch?"
+
+ //for Kepler, delete this, or just delete the whole story aspect if you want.
+ //If fully completed
+ /*
+ if(progression["[C.real_name]"] & JACQ_FAR)//Damnit this is a pain
+ if(progression["[C.real_name]"] & JACQ_EXPELL) //I give up
+ if(progression["[C.real_name]"] & JACQ_HEAD) //This is only an event thing
+ choices += "Can I take you out on a date?"
+ */
+ if(progression["[C.real_name]"] == 63)//Damnit this is a pain
+ choices += "Can I take you out on a date?"
+
+ //If you've nothing to ask
+ if(!LAZYLEN(choices))
+ visible_message("[src] sighs, \"Ah'm all questioned oot fer noo, [gender].\"")
+ return
+ //Otherwise, lets go!
+ visible_message("[src] says, \"A question? Sure, it'll cost you a candy though!\"")
+ choices += "Nevermind"
+ //Candies for chitchats
+ var/choice = input(C, "What do you want to ask?", "What do you want to ask?") in choices
+ if(!take_candies(C, 1))
+ visible_message("[src] raises an eyebrow, \"It's a candy per question [gender]! Thems the rules!\"")
+ return
+ //Talking
+ switch(choice)
+ if("Why do you want the candies?")
+ visible_message("[src] says, \"Ave ye tried them? They're full of all sorts of reagents. Ah'm after them so ah ken magic em up an hopefully find rare stuff fer me brews. Honestly it's a lot easier magicking up tatt fer ye lot than runnin aroond on me own like. I'd ask me familiars but most a my familiars are funny fellows 'n constantly bugger off on adventures when given simple objectives like; Go grab me a tea cake or watch over me cauldron. Ah mean, ye might run into Bartholomew my cat. Ee's supposed tae be tending my cauldron, but I've nae idea where ee's got tae.\"")
+ progression["[C.real_name]"] = progression["[C.real_name]"] | JACQ_CANDIES
+ sleep(30)
+ poof()
+
+ if("You really came all this way for candy?")
+ visible_message("[src] looks tae the side sheepishly, \"Aye, well, tae be honest, Ah'm here tae see me sis, but dunnae let her knew that. She's an alchemist too like, but she dunnae use a caldron like mae, she buggered off like tae her posh ivory tower tae learn bloody chemistry instead!\"[src] scowls, \"She's tae black sheep o' the family too, so we dunnae see eye tae eye sometimes on alchemy. Ah mean, she puts moles in her brews! Ye dunnae put moles in yer brews! Yae threw your brews at tae wee bastards an blew em up!\"[src] sighs, \"But she's a heart o gold so.. Ah wanted tae see her an check up oon her, make sure she's okay.\"")
+ progression["[C.real_name]"] = progression["[C.real_name]"] | JACQ_FAR
+ sleep(30)
+ poof()
+
+ if("What is that on your head?")
+ visible_message("[src] pats the pumpkin atop her head, \"This thing? This ain't nae ordinary pumpkin! Me Ma grew this monster ooer a year o love, dedication an hard work. Honestly it felt like she loved this thing more than any of us, which Ah knew ain't true an it's not like she was hartless or anything but.. well, we had a falling oot when Ah got back home with all me stuff in tow. An all she had done is sent me owl after owl over t' last year aboot this bloody pumpkin and ah had enough. So ah took it, an put it on me head. You know, as ye do. Ah am the great Pumpqueen after all, Ah deserve this.\"")
+ progression["[C.real_name]"] = progression["[C.real_name]"] | JACQ_HEAD
+ sleep(30)
+ poof()
+
+ if("Are you a witch?")
+ visible_message("[src] grumbles, \"If ye must know, Ah got kicked oot of the witch academy fer being too much of a \"loose cannon\". A bloody loose cannon? Nae they were just pissed off Ah had the brass tae proclaim myself as the Pumpqueen! And also maybe the time Ah went and blew up one of the towers by trying tae make a huge batch of astrogen might've had something tae do with it. Ah mean it would've worked fine if the cauldrons weren't so shite and were actually upgraded by the faculty. So technically no, I'm not a witch.\"")
+ progression["[C.real_name]"] = progression["[C.real_name]"] | JACQ_WITCH
+ sleep(30)
+ poof()
+
+ if("So you got ex-spell-ed?")
+ visible_message("[src] Gives you a blank look at the pun, before continuing, \"Not quite, Ah know Ah ken get back into the academy, it's only an explosion, they happen all the time, but, tae be fair it's my fault that things came tae their explosive climax. You don't know what it's like when you're after a witch doctorate, everyone else is doing well, everyone's making new spells and the like, and I'm just good at making explosions really, or fireworks. So, Ah did something Ah knew was dangerous, because Ah had tae do something tae stand oot, but Ah know this life ain't fer me, Ah don't want tae be locked up in dusty towers, grinding reagent after reagent together, trying tae find new reactions, some of the wizards in there haven't left fer years. Ah want tae live, Ah want tae fly around on a broom, turn people into cats fer a day and disappear cackling! That's what got me into witchcraft!\" she throws her arms up in the arm, spinning the pumpkin upon her head slightly. She carefully spins it back to face you, giving oot a soft sigh, \"Ah know my mother's obsession with this dumb thing on my head is just her trying tae fill the void of me and my sis moving oot, and it really shouldn't be on my head. And Ah know that I'm really here tae get help from my sis.. She's the sensible one, and she gives good hugs.\"")
+ sleep(30)
+ visible_message("[src] says, \"Thanks [C], Ah guess Ah didn't realise Ah needed someone tae talk tae but, I'm glad ye spent all your candies talking tae me. Funny how things seem much worse in yer head.\"")
+ progression["[C.real_name]"] = progression["[C.real_name]"] | JACQ_EXPELL
+ sleep(30)
+ poof()
+
+ if("Can I take you out on a date?")
+ visible_message("[src] blushes, \"...You want tae ask me oot on a date? Me? After all that nonsense Ah just said? It seems a waste of a candy honestly.\"")
+ progression["[C.real_name]"] = progression["[C.real_name]"] | JACQ_DATE
+ visible_message("[src] looks to the side, deep in thought.")
+ dating_start(C, gender)
+
+ if("Nevermind")
+ visible_message("[src] shrugs, \"Suit yerself then, here's your candy back.\"")
+ new /obj/item/reagent_containers/food/snacks/special_candy(loc)
+
+
+/mob/living/simple_animal/jacq/proc/trick(mob/living/carbon/C, gender)
+ var/option
+ if(ishuman(C))
+ option = rand(1,7)
+ else
+ option = rand(1,6)
+ switch(option)
+ if(1)
+ visible_message("[src] waves their arms around, \"Hocus pocus, making friends is now your focus!\"")
+ var/datum/objective/brainwashing/objective = pick("Make a tasty sandwich for", "Compose a poem for", "Aquire a nice outfit to give to", "Strike up a conversation about pumpkins with", "Write a letter and deliver it to", "Give a nice hat to")
+ var/mob/living/L2 = pick(GLOB.player_list)
+ objective += " [L2.name]."
+ brainwash(C, objective)
+ if(2)
+ visible_message("[src] waves their arms around, \"Off comes your head, a pumpkin taking it's stead!\"")
+ C.reagents.add_reagent("pumpkinmutationtoxin", 5)
+ if(3)
+ visible_message("[src] waves their arms around, \"If only you had a better upbringing, your ears are now full of my singing!\"")
+ var/client/C2 = C.client
+ C2.chatOutput.sendMusic("https://a.uguu.se/rQ8FxxUQ1Xzc_SpOwOkyOwOkyPumpkinSong-PFrPrIxluWk.mp4", 1)//I hope this works!
+ if(4)
+ visible_message("[src] waves their arms around, \"You're cute little bumpkin, On your head is a pumpkin!\"")
+ if(C.head)
+ var/obj/item/W = C.head
+ C.dropItemToGround(W, TRUE)
+ var/jaqc_latern = new /obj/item/clothing/head/hardhat/pumpkinhead/jaqc
+ C.equip_to_slot(jaqc_latern, SLOT_HEAD, 1, 1)
+ if(5)
+ visible_message("[src] waves their arms around, \"In your body there's something amiss, you'll find it's a chem made by my sis!\"")
+ C.reagents.add_reagent("eigenstate", 30)
+ if(6)
+ visible_message("[src] waves their arms around, \"A new familiar for me, and you'll see it's thee!\"")
+ C.reagents.add_reagent("secretcatchem", 30)
+ if(7)
+ visible_message("[src] waves their arms around, \"While you may not be a ghost, for this sheet you'll always be it's host.\"")
+ var/mob/living/carbon/human/H = C
+ if(H.wear_suit)
+ var/obj/item/W = H.wear_suit
+ H.dropItemToGround(W, TRUE)
+ var/ghost = new /obj/item/clothing/suit/ghost_sheet/sticky
+ H.equip_to_slot(ghost, SLOT_WEAR_SUIT, 1, 1)
+ poof()
+
+//Blame Fel
+/mob/living/simple_animal/jacq/proc/dating_start(mob/living/carbon/C, gender)
+ var/candies = pollGhostCandidates("Do you want to go on a date with [C] as Jacqueline the great pumpqueen?")
+ //sleep(30) //If the poll doesn't autopause.
+ if(candies)
+ candies = shuffle(candies)//Shake those ghosts up!
+ for(var/mob/dead/observer/C2 in candies)
+ if(C2.key && C2)
+ key = C2.key
+ message_admins("[C2]/[C2.key] has agreed to go on a date with [C] as Jacqueline.")
+ log_game("HALLOWEEN: [C2]/[C2.key] has agreed to go on a date with [C] as Jacqueline")
+ to_chat(src, "You are Jacqueline the great pumpqueen, witch Extraordinaire! You're a very Scottish lass with a kind heart, but also a little crazy. You also blew up the wizarding school and you're suspended for a while, so you visited the station before heading home. On your head lies the prize pumpkin of your Mother's pumpkin patch. You're currently on a date with [C] and well, I didn't think anyone would get this far. Please be good so I can do events like this in the future. ")
+ return
+ else
+ candies =- C2
+ visible_message("[src] looks to the side, \"Look, Ah like ye but, Ah don't think Ah can right now. If ye can't tell, the stations covered in volatile candies, I've a few other laddies and lassies running after me treats, and tae top it all off, I've the gods breathing down me neck, watching every treat Ah make fer the lot of yous.\" she sighs, \"But that's not a no, right? That's.. just a nae right noo.\"")
+ sleep(20)
+ visible_message("[src] takes off the pumpkin on her head, a rich blush on her cheeks. She leans over planting a kiss upon your forehead quickly befere popping the pumpkin back on her head.")
+ sleep(10)
+ visible_message("[src] waves their arms around, \"There, that aught tae be worth a candy.\"")
+ sleep(20)
+ poof()
+
+/obj/item/clothing/head/hardhat/pumpkinhead/jaqc
+ name = "Jacq o' latern"
+ desc = "A jacqueline o' lantern! You can't seem to get rid of it."
+ icon_state = "hardhat0_pumpkin_j"
+ item_state = "hardhat0_pumpkin_j"
+ item_color = "pumpkin_j"
+ brightness_on = 4
+
+/obj/item/clothing/head/hardhat/pumpkinhead/jaqc/Initialize()
+ . = ..()
+ ADD_TRAIT(src, TRAIT_NODROP, GLUED_ITEM_TRAIT)
+
+/obj/item/clothing/suit/ghost_sheet/sticky
+
+/obj/item/clothing/suit/ghost_sheet/sticky/Initialize()
+ . = ..()
+ ADD_TRAIT(src, TRAIT_NODROP, GLUED_ITEM_TRAIT)
+
+/obj/item/clothing/suit/ghost_sheet/sticky/attack_hand(mob/user)
+ if(iscarbon(user))
+ to_chat(user, "Boooooo~!")
+ return
+ else
+ ..()
+
+/obj/item/clothing/suit/ghost_sheet/sticky/attack_hand(mob/user)
+ if(iscarbon(user))
+ to_chat(user, "Boooooo~!")
+ return
+ else
+ ..()
+
+/datum/reagent/mutationtoxin/pumpkinhead
+ name = "Pumpkin head mutation toxin"
+ id = "pumpkinmutationtoxin"
+ race = /datum/species/dullahan/pumpkin
+ mutationtext = "The pain subsides. You feel your head roll off your shoulders... and you smell pumpkin."
+ //I couldn't get the replace head sprite with a pumpkin to work so, it is what it is.
+
+/mob/living/simple_animal/jacq/proc/check_candies(mob/living/carbon/C)
+ var/invs = C.get_contents()
+ var/candy_count = 0
+ for(var/item in invs)
+ if(istype(item, /obj/item/reagent_containers/food/snacks/special_candy))
+ candy_count++
+ return candy_count
+
+/mob/living/simple_animal/jacq/proc/take_candies(mob/living/carbon/C, candy_amount = 1)
+ var/inv = C.get_contents()
+ var/candies = list()
+ for(var/item in inv)
+ if(istype(item, /obj/item/reagent_containers/food/snacks/special_candy))
+ candies += item
+ if(LAZYLEN(candies) == candy_amount)
+ break
+ if(LAZYLEN(candies) == candy_amount) //I know it's a double check but eh, to be safe.
+ for(var/candy in candies)
+ qdel(candy)
+ return TRUE
+ return FALSE
+
+//Potions
+/obj/item/reagent_containers/potion_container
+ name = "potion"
+ icon = 'icons/obj/halloween_items.dmi'
+ icon_state = "jacq_potion"
+ desc = "A potion with a strange concoction within. Be careful, as if it's thrown it explodes in a puff of smoke like Jacqueline."
+
+/obj/item/reagent_containers/potion_container/Initialize()
+ .=..()
+ var/R = get_random_reagent_id()
+ reagents.add_reagent(R, 30)
+ name = "[R] Potion"
+
+/obj/item/reagent_containers/potion_container/throw_impact(atom/target)
+ ..()
+ sleep(20)
+ var/datum/effect_system/smoke_spread/chem/s = new()
+ s.set_up(src.reagents, 3, src.loc)
+ s.start()
+ qdel(src)
+
+//Candies
+/obj/item/reagent_containers/food/snacks/special_candy
+ name = "Magic candy"
+ icon = 'icons/obj/halloween_items.dmi'
+ icon_state = "jacq_candy"
+ desc = "A candy with strange magic within. Be careful, as the magic isn't always helpful."
+
+/obj/item/reagent_containers/food/snacks/special_candy/Initialize()
+ .=..()
+ reagents.add_reagent(get_random_reagent_id(), 5)
diff --git a/code/modules/holiday/holidays.dm b/code/modules/holiday/holidays.dm
index 7d1e25235d..9d075857a7 100644
--- a/code/modules/holiday/holidays.dm
+++ b/code/modules/holiday/holidays.dm
@@ -333,6 +333,16 @@
/datum/holiday/halloween/getStationPrefix()
return pick("Bone-Rattling","Mr. Bones' Own","2SPOOKY","Spooky","Scary","Skeletons")
+/datum/holiday/jacqueen //Subset of halloween
+ name = "jacqueen"
+ begin_day = 27
+ begin_month = OCTOBER
+ end_day = 2
+ end_month = NOVEMBER
+
+/datum/holiday/jacqueen/greet()
+ return "Jacqueline the great Pumpqueen has come to visit!"
+
/datum/holiday/vegan
name = "Vegan Day"
begin_day = 1
diff --git a/code/modules/holodeck/computer.dm b/code/modules/holodeck/computer.dm
index 4ffc10f9c2..889706744a 100644
--- a/code/modules/holodeck/computer.dm
+++ b/code/modules/holodeck/computer.dm
@@ -111,6 +111,11 @@
if(A)
load_program(A)
if("safety")
+ if(!issilicon(usr) && !IsAdminGhost(usr))
+ var/msg = "[key_name(usr)] attempted to emag the holodeck using a href they shouldn't have!"
+ message_admins(msg)
+ log_admin(msg)
+ return
obj_flags ^= EMAGGED
if((obj_flags & EMAGGED) && program && emag_programs[program.name])
emergency_shutdown()
@@ -149,6 +154,7 @@
active_power_usage = 50 + spawned.len * 3 + effects.len * 5
/obj/machinery/computer/holodeck/emag_act(mob/user)
+ . = ..()
if(obj_flags & EMAGGED)
return
if(!LAZYLEN(emag_programs))
@@ -160,6 +166,7 @@
to_chat(user, "Warning. Automatic shutoff and derezing protocols have been corrupted. Please call Nanotrasen maintenance and do not use the simulator.")
log_game("[key_name(user)] emagged the Holodeck Control Console")
nerf(!(obj_flags & EMAGGED))
+ return TRUE
/obj/machinery/computer/holodeck/emp_act(severity)
. = ..()
diff --git a/code/modules/holodeck/holo_effect.dm b/code/modules/holodeck/holo_effect.dm
index fee0b2b7c9..308cec1cbd 100644
--- a/code/modules/holodeck/holo_effect.dm
+++ b/code/modules/holodeck/holo_effect.dm
@@ -78,6 +78,12 @@
// these vars are not really standardized but all would theoretically create stuff on death
for(var/v in list("butcher_results","corpse","weapon1","weapon2","blood_volume") & mob.vars)
mob.vars[v] = null
+ ENABLE_BITFIELD(mob.flags_1, HOLOGRAM_1)
+ if(isliving(mob))
+ var/mob/living/L = mob
+ L.feeding = FALSE
+ L.devourable = FALSE
+ L.digestable = FALSE
return mob
/obj/effect/holodeck_effect/mobspawner/deactivate(var/obj/machinery/computer/holodeck/HC)
@@ -100,7 +106,7 @@
/obj/effect/holodeck_effect/mobspawner/penguin
mobtype = /mob/living/simple_animal/pet/penguin/emperor
-
+
/obj/effect/holodeck_effect/mobspawner/penguin/Initialize()
if(prob(1))
mobtype = /mob/living/simple_animal/pet/penguin/emperor/shamebrero
diff --git a/code/modules/hydroponics/gene_modder.dm b/code/modules/hydroponics/gene_modder.dm
index d6eacfe0e1..79a23014b0 100644
--- a/code/modules/hydroponics/gene_modder.dm
+++ b/code/modules/hydroponics/gene_modder.dm
@@ -69,6 +69,8 @@
if(default_deconstruction_screwdriver(user, "dnamod", "dnamod", I))
update_icon()
return
+ else if(default_unfasten_wrench(user, I))
+ return
if(default_deconstruction_crowbar(I))
return
if(iscyborg(user))
diff --git a/code/modules/hydroponics/grown/berries.dm b/code/modules/hydroponics/grown/berries.dm
index 19abdacf3a..ef019387e8 100644
--- a/code/modules/hydroponics/grown/berries.dm
+++ b/code/modules/hydroponics/grown/berries.dm
@@ -215,3 +215,26 @@
filling_color = "#7FFF00"
tastes = list("green grape" = 1)
distill_reagent = "cognac"
+
+// Strawberry
+/obj/item/seeds/strawberry
+ name = "pack of strawberry seeds"
+ desc = "These seeds grow into strawberry vines."
+ icon_state = "seed-strawberry"
+ species = "strawberry"
+ plantname = "Strawberry Vine"
+ product = /obj/item/reagent_containers/food/snacks/grown/strawberry
+ growthstages = 6
+ growing_icon = 'icons/obj/hydroponics/growing_fruits.dmi'
+ icon_grow = "strawberry-grow"
+ icon_dead = "berry-dead"
+ reagents_add = list("vitamin" = 0.07, "nutriment" = 0.1, "sugar" = 0.2)
+ mutatelist = list()
+
+/obj/item/reagent_containers/food/snacks/grown/strawberry
+ seed = /obj/item/seeds/strawberry
+ name = "strawberry"
+ icon_state = "strawberry"
+ filling_color = "#7FFF00"
+ tastes = list("strawberries" = 1)
+ wine_power = 20
diff --git a/code/modules/hydroponics/grown/cotton.dm b/code/modules/hydroponics/grown/cotton.dm
new file mode 100644
index 0000000000..045839d0de
--- /dev/null
+++ b/code/modules/hydroponics/grown/cotton.dm
@@ -0,0 +1,79 @@
+/obj/item/seeds/cotton
+ name = "pack of cotton seeds"
+ desc = "A pack of seeds that'll grow into a cotton plant. Assistants make good free labor if neccesary."
+ icon_state = "seed-cotton"
+ species = "cotton"
+ plantname = "Cotton"
+ icon_harvest = "cotton-harvest"
+ product = /obj/item/grown/cotton
+ lifespan = 35
+ endurance = 25
+ maturation = 15
+ production = 1
+ yield = 2
+ potency = 50
+ growthstages = 3
+ growing_icon = 'icons/obj/hydroponics/growing.dmi'
+ icon_dead = "cotton-dead"
+ mutatelist = list(/obj/item/seeds/cotton/durathread)
+
+/obj/item/grown/cotton
+ seed = /obj/item/seeds/cotton
+ name = "cotton bundle"
+ desc = "A fluffy bundle of cotton."
+ icon_state = "cotton"
+ force = 0
+ throwforce = 0
+ w_class = WEIGHT_CLASS_TINY
+ throw_speed = 2
+ throw_range = 3
+ attack_verb = list("pomfed")
+ var/cotton_type = /obj/item/stack/sheet/cotton
+ var/cotton_name = "raw cotton"
+
+/obj/item/grown/cotton/attack_self(mob/user)
+ user.show_message("You pull some [cotton_name] out of the [name]!", 1)
+ var/seed_modifier = 0
+ if(seed)
+ seed_modifier = round(seed.potency / 25)
+ var/obj/item/stack/cotton = new cotton_type(user.loc, 1 + seed_modifier)
+ var/old_cotton_amount = cotton.amount
+ for(var/obj/item/stack/ST in user.loc)
+ if(ST != cotton && istype(ST, cotton_type) && ST.amount < ST.max_amount)
+ ST.attackby(cotton, user)
+ if(cotton.amount > old_cotton_amount)
+ to_chat(user, "You add the newly-formed [cotton_name] to the stack. It now contains [cotton.amount] [cotton_name].")
+ qdel(src)
+
+//reinforced mutated variant
+/obj/item/seeds/cotton/durathread
+ name = "pack of durathread seeds"
+ desc = "A pack of seeds that'll grow into an extremely durable thread that could easily rival plasteel if woven properly."
+ icon_state = "seed-durathread"
+ species = "durathread"
+ plantname = "Durathread"
+ icon_harvest = "durathread-harvest"
+ product = /obj/item/grown/cotton/durathread
+ lifespan = 80
+ endurance = 50
+ maturation = 15
+ production = 1
+ yield = 2
+ potency = 50
+ growthstages = 3
+ growing_icon = 'icons/obj/hydroponics/growing.dmi'
+ icon_dead = "cotton-dead"
+
+/obj/item/grown/cotton/durathread
+ seed = /obj/item/seeds/cotton/durathread
+ name = "durathread bundle"
+ desc = "A tough bundle of durathread, good luck unraveling this."
+ icon_state = "durathread"
+ force = 5
+ throwforce = 5
+ w_class = WEIGHT_CLASS_NORMAL
+ throw_speed = 2
+ throw_range = 3
+ attack_verb = list("bashed", "battered", "bludgeoned", "whacked")
+ cotton_type = /obj/item/stack/sheet/cotton/durathread
+ cotton_name = "raw durathread"
\ No newline at end of file
diff --git a/code/modules/hydroponics/grown/flowers.dm b/code/modules/hydroponics/grown/flowers.dm
index 4e1718f853..af5919969c 100644
--- a/code/modules/hydroponics/grown/flowers.dm
+++ b/code/modules/hydroponics/grown/flowers.dm
@@ -25,6 +25,7 @@
slot_flags = ITEM_SLOT_HEAD
filling_color = "#FF6347"
bitesize_mod = 3
+ tastes = list("sesame seeds" = 1)
foodtype = VEGETABLES | GROSS
distill_reagent = "vermouth"
@@ -36,13 +37,14 @@
species = "lily"
plantname = "Lily Plants"
product = /obj/item/reagent_containers/food/snacks/grown/poppy/lily
- mutatelist = list()
+ mutatelist = list(/obj/item/seeds/bee_balm)
/obj/item/reagent_containers/food/snacks/grown/poppy/lily
seed = /obj/item/seeds/poppy/lily
name = "lily"
desc = "A beautiful orange flower."
icon_state = "lily"
+ tastes = list("pelts " = 1)
filling_color = "#FFA500"
// Geranium
@@ -61,6 +63,7 @@
desc = "A beautiful blue flower."
icon_state = "geranium"
filling_color = "#008B8B"
+ tastes = list("pelts " = 1)
// Harebell
/obj/item/seeds/harebell
@@ -86,6 +89,7 @@
name = "harebell"
desc = "\"I'll sweeten thy sad grave: thou shalt not lack the flower that's like thy face, pale primrose, nor the azured hare-bell, like thy veins; no, nor the leaf of eglantine, whom not to slander, out-sweeten'd not thy breath.\""
icon_state = "harebell"
+ tastes = list("salt" = 1)
slot_flags = ITEM_SLOT_HEAD
filling_color = "#E6E6FA"
bitesize_mod = 3
@@ -123,6 +127,7 @@
w_class = WEIGHT_CLASS_TINY
throw_speed = 1
throw_range = 3
+ tastes = list("seeds" = 1)
/obj/item/grown/sunflower/attack(mob/M, mob/user)
to_chat(M, " [user] smacks you with a sunflower!FLOWER POWER")
@@ -153,6 +158,7 @@
filling_color = "#E6E6FA"
bitesize_mod = 2
distill_reagent = "absinthe" //It's made from flowers.
+ tastes = list("glowbugs" = 1)
// Novaflower
/obj/item/seeds/sunflower/novaflower
@@ -184,6 +190,7 @@
throw_range = 3
attack_verb = list("roasted", "scorched", "burned")
grind_results = list("capsaicin" = 0, "condensedcapsaicin" = 0)
+ tastes = list("cooked sunflower" = 1)
/obj/item/grown/novaflower/add_juice()
..()
@@ -214,3 +221,61 @@
if(!user.gloves)
to_chat(user, "The [name] burns your bare hand!")
user.adjustFireLoss(rand(1, 5))
+
+// Beebalm
+/obj/item/seeds/bee_balm
+ name = "pack of Bee Balm seeds"
+ desc = "These seeds grow into Bee Balms."
+ icon_state = "seed-bee_balm"
+ species = "bee_balm"
+ plantname = "Bee Balm Buds"
+ product = /obj/item/reagent_containers/food/snacks/grown/bee_balm
+ endurance = 10
+ maturation = 8
+ yield = 3
+ potency = 30
+ growthstages = 3
+ growing_icon = 'icons/obj/hydroponics/growing_flowers.dmi'
+ icon_grow = "bee_balm-grow"
+ icon_dead = "bee_balm-dead"
+ mutatelist = list(/obj/item/seeds/poppy/geranium, /obj/item/seeds/bee_balm/honey) //Lower odds of becoming honey
+ reagents_add = list("spaceacillin" = 0.1, "sterilizine" = 0.05)
+
+/obj/item/reagent_containers/food/snacks/grown/bee_balm
+ seed = /obj/item/seeds/bee_balm
+ name = "bee balm"
+ desc = "A flower used for medical antiseptic in history."
+ icon_state = "bee_balm"
+ filling_color = "#FF6347"
+ bitesize_mod = 8
+ tastes = list("strong antiseptic " = 1)
+ foodtype = GROSS
+
+// Beebalm
+/obj/item/seeds/bee_balm/honey
+ name = "pack of Honey Balm seeds"
+ desc = "These seeds grow into Honey Balms."
+ icon_state = "seed-bee_balmalt"
+ species = "seed-bee_balm_alt"
+ plantname = "Honey Balm Pods"
+ product = /obj/item/reagent_containers/food/snacks/grown/bee_balm/honey
+ endurance = 1
+ maturation = 10
+ yield = 1
+ potency = 1
+ growthstages = 3
+ growing_icon = 'icons/obj/hydroponics/growing_flowers.dmi'
+ icon_grow = "bee_balmalt-grow"
+ icon_dead = "bee_balmalt-dead"
+ reagents_add = list("honey" = 0.1, "lye" = 0.3) //To make wax
+ rarity = 30
+
+/obj/item/reagent_containers/food/snacks/grown/bee_balm/honey
+ seed = /obj/item/seeds/bee_balm/honey
+ name = "honey balm"
+ desc = "A large honey filled pod of a flower."
+ icon_state = "bee_balmalt"
+ filling_color = "#FF6347"
+ bitesize_mod = 8
+ tastes = list("wax" = 1)
+ foodtype = SUGAR
\ No newline at end of file
diff --git a/code/modules/hydroponics/grown/melon.dm b/code/modules/hydroponics/grown/melon.dm
index 29691c3289..eb463e5f6d 100644
--- a/code/modules/hydroponics/grown/melon.dm
+++ b/code/modules/hydroponics/grown/melon.dm
@@ -58,6 +58,7 @@
wine_power = 70 //Water to wine, baby.
wine_flavor = "divinity"
-/obj/item/reagent_containers/food/snacks/grown/holymelon/Initialize()
- . = ..()
- AddComponent(/datum/component/anti_magic, TRUE, TRUE) //deliver us from evil o melon god
+
+// /obj/item/reagent_containers/food/snacks/grown/holymelon/Initialize()
+// . = ..()
+// AddComponent(/datum/component/anti_magic, TRUE, TRUE) //deliver us from evil o melon god
diff --git a/code/modules/hydroponics/grown/misc.dm b/code/modules/hydroponics/grown/misc.dm
index 107a6a94f9..0902052a11 100644
--- a/code/modules/hydroponics/grown/misc.dm
+++ b/code/modules/hydroponics/grown/misc.dm
@@ -1,7 +1,7 @@
// Starthistle
/obj/item/seeds/starthistle
name = "pack of starthistle seeds"
- desc = "A robust species of weed that often springs up in-between the cracks of spaceship parking lots."
+ desc = "A robust species of weed that often springs up in-between the cracks of spaceship parking lots. Grind down these seeds for a substitution for mustardgrind."
icon_state = "seed-starthistle"
species = "starthistle"
plantname = "Starthistle"
@@ -9,9 +9,10 @@
endurance = 50 // damm pesky weeds
maturation = 5
production = 1
- yield = 2
+ yield = 6
potency = 10
growthstages = 3
+ grind_results = list("mustardgrind" = 1)
growing_icon = 'icons/obj/hydroponics/growing_flowers.dmi'
genes = list(/datum/plant_gene/trait/plant_type/weed_hardy)
mutatelist = list(/obj/item/seeds/harebell)
diff --git a/code/modules/hydroponics/grown/peach.dm b/code/modules/hydroponics/grown/peach.dm
new file mode 100644
index 0000000000..6fbf933bd1
--- /dev/null
+++ b/code/modules/hydroponics/grown/peach.dm
@@ -0,0 +1,27 @@
+// Peach
+/obj/item/seeds/peach
+ name = "pack of peach seeds"
+ desc = "These seeds grow into peach trees."
+ icon_state = "seed-peach"
+ species = "peach"
+ plantname = "Peach Tree"
+ product = /obj/item/reagent_containers/food/snacks/grown/peach
+ lifespan = 65
+ endurance = 40
+ yield = 3
+ growing_icon = 'icons/obj/hydroponics/growing_fruits.dmi'
+ icon_grow = "peach-grow"
+ icon_dead = "peach-dead"
+ genes = list(/datum/plant_gene/trait/repeated_harvest)
+ reagents_add = list("vitamin" = 0.04, "nutriment" = 0.1)
+
+/obj/item/reagent_containers/food/snacks/grown/peach
+ seed = /obj/item/seeds/peach
+ name = "peach"
+ desc = "It's fuzzy!"
+ icon_state = "peach"
+ filling_color = "#FF4500"
+ bitesize = 25
+ foodtype = FRUIT
+ juice_results = list("peachjuice" = 0)
+ tastes = list("peach" = 1)
diff --git a/code/modules/hydroponics/grown/tea_coffee.dm b/code/modules/hydroponics/grown/tea_coffee.dm
index fc84617ed8..06cbb1df0c 100644
--- a/code/modules/hydroponics/grown/tea_coffee.dm
+++ b/code/modules/hydroponics/grown/tea_coffee.dm
@@ -14,6 +14,7 @@
icon_dead = "tea-dead"
genes = list(/datum/plant_gene/trait/repeated_harvest)
mutatelist = list(/obj/item/seeds/tea/astra)
+ reagents_add = list("teapowder" = 0.1)
/obj/item/reagent_containers/food/snacks/grown/tea
seed = /obj/item/seeds/tea
@@ -32,7 +33,7 @@
species = "teaastra"
plantname = "Tea Astra Plant"
product = /obj/item/reagent_containers/food/snacks/grown/tea/astra
- mutatelist = list()
+ mutatelist = list(/obj/item/seeds/tea/catnip)
reagents_add = list("synaptizine" = 0.1, "vitamin" = 0.04, "teapowder" = 0.1)
rarity = 20
@@ -43,6 +44,24 @@
filling_color = "#4582B4"
grind_results = list("teapowder" = 0, "salglu_solution" = 0)
+// Kitty drugs
+/obj/item/seeds/tea/catnip
+ name = "pack of catnip seeds"
+ icon_state = "seed-catnip"
+ desc = "Long stocks with flowering tips that has a chemical to make feline attracted to it."
+ species = "catnip"
+ plantname = "Catnip Plant"
+ growthstages = 3
+ product = /obj/item/reagent_containers/food/snacks/grown/tea/catnip
+ reagents_add = list("catnip" = 0.1, "vitamin" = 0.06, "teapowder" = 0.3)
+ rarity = 50
+
+/obj/item/reagent_containers/food/snacks/grown/tea/catnip
+ seed = /obj/item/seeds/tea/catnip
+ name = "Catnip buds"
+ icon_state = "catnip_leaves"
+ filling_color = "#4582B4"
+ grind_results = list("catnp" = 2, "water" = 1)
// Coffee
/obj/item/seeds/coffee
diff --git a/code/modules/hydroponics/growninedible.dm b/code/modules/hydroponics/growninedible.dm
index 5aeef19b1a..c0e3beaf79 100644
--- a/code/modules/hydroponics/growninedible.dm
+++ b/code/modules/hydroponics/growninedible.dm
@@ -7,6 +7,7 @@
icon = 'icons/obj/hydroponics/harvest.dmi'
resistance_flags = FLAMMABLE
var/obj/item/seeds/seed = null // type path, gets converted to item on New(). It's safe to assume it's always a seed item.
+ var/tastes = list("indescribable" = 1) //Stops runtimes. Grown are un-eatable anyways so if you do then its a bug
/obj/item/grown/Initialize(newloc, obj/item/seeds/new_seed)
. = ..()
diff --git a/code/modules/hydroponics/plant_genes.dm b/code/modules/hydroponics/plant_genes.dm
index 17462c0626..db529e8ffb 100644
--- a/code/modules/hydroponics/plant_genes.dm
+++ b/code/modules/hydroponics/plant_genes.dm
@@ -290,15 +290,15 @@
var/teleport_radius = max(round(G.seed.potency / 10), 1)
var/turf/T = get_turf(target)
new /obj/effect/decal/cleanable/molten_object(T) //Leave a pile of goo behind for dramatic effect...
- do_teleport(target, T, teleport_radius)
+ do_teleport(target, T, teleport_radius, channel = TELEPORT_CHANNEL_BLUESPACE)
/datum/plant_gene/trait/teleport/on_slip(obj/item/reagent_containers/food/snacks/grown/G, mob/living/carbon/C)
var/teleport_radius = max(round(G.seed.potency / 10), 1)
var/turf/T = get_turf(C)
to_chat(C, "You slip through spacetime!")
- do_teleport(C, T, teleport_radius)
+ do_teleport(C, T, teleport_radius, channel = TELEPORT_CHANNEL_BLUESPACE)
if(prob(50))
- do_teleport(G, T, teleport_radius)
+ do_teleport(G, T, teleport_radius, channel = TELEPORT_CHANNEL_BLUESPACE)
else
new /obj/effect/decal/cleanable/molten_object(T) //Leave a pile of goo behind for dramatic effect...
qdel(G)
diff --git a/code/modules/integrated_electronics/core/printer.dm b/code/modules/integrated_electronics/core/printer.dm
index de3ade389f..f0aa10f2da 100644
--- a/code/modules/integrated_electronics/core/printer.dm
+++ b/code/modules/integrated_electronics/core/printer.dm
@@ -193,7 +193,7 @@
else if(ispath(build_type, /obj/item/integrated_circuit))
var/obj/item/integrated_circuit/IC = SScircuit.cached_components[build_type]
cost = IC.materials[MAT_METAL]
- else if(!build_type in SScircuit.circuit_fabricator_recipe_list["Tools"])
+ else if(!(build_type in SScircuit.circuit_fabricator_recipe_list["Tools"]))
return
var/datum/component/material_container/materials = GetComponent(/datum/component/material_container)
diff --git a/code/modules/integrated_electronics/subtypes/output.dm b/code/modules/integrated_electronics/subtypes/output.dm
index 86efc4c74a..3975da18b6 100644
--- a/code/modules/integrated_electronics/subtypes/output.dm
+++ b/code/modules/integrated_electronics/subtypes/output.dm
@@ -112,7 +112,7 @@
var/brightness = get_pin_data(IC_INPUT, 2)
if(new_color && isnum(brightness))
- brightness = CLAMP(brightness, 0, 4)
+ brightness = CLAMP(brightness, 0, 10)
light_rgb = new_color
light_brightness = brightness
@@ -411,4 +411,4 @@
if(assembly)
assembly.investigate_log("displayed \"[html_encode(stuff_to_display)]\" with [type].", INVESTIGATE_CIRCUIT)
else
- investigate_log("displayed \"[html_encode(stuff_to_display)]\" as [type].", INVESTIGATE_CIRCUIT)
\ No newline at end of file
+ investigate_log("displayed \"[html_encode(stuff_to_display)]\" as [type].", INVESTIGATE_CIRCUIT)
diff --git a/code/modules/jobs/job_exp.dm b/code/modules/jobs/job_exp.dm
index 4b7b175240..f99bf65071 100644
--- a/code/modules/jobs/job_exp.dm
+++ b/code/modules/jobs/job_exp.dm
@@ -8,6 +8,8 @@ GLOBAL_PROTECT(exp_to_update)
return 0
if(!CONFIG_GET(flag/use_exp_tracking))
return 0
+ if(!SSdbcore.Connect())
+ return 0
if(!exp_requirements || !exp_type)
return 0
if(!job_is_xp_locked(src.title))
diff --git a/code/modules/jobs/job_types/job.dm b/code/modules/jobs/job_types/_job.dm
similarity index 84%
rename from code/modules/jobs/job_types/job.dm
rename to code/modules/jobs/job_types/_job.dm
index f678700735..2eeffa8b7a 100644
--- a/code/modules/jobs/job_types/job.dm
+++ b/code/modules/jobs/job_types/_job.dm
@@ -1,228 +1,245 @@
-/datum/job
- //The name of the job
- var/title = "NOPE"
-
- //Job access. The use of minimal_access or access is determined by a config setting: config.jobs_have_minimal_access
- var/list/minimal_access = list() //Useful for servers which prefer to only have access given to the places a job absolutely needs (Larger server population)
- var/list/access = list() //Useful for servers which either have fewer players, so each person needs to fill more than one role, or servers which like to give more access, so players can't hide forever in their super secure departments (I'm looking at you, chemistry!)
-
- //Determines who can demote this position
- var/department_head = list()
-
- //Tells the given channels that the given mob is the new department head. See communications.dm for valid channels.
- var/list/head_announce = null
-
- //Bitflags for the job
- var/flag = 0
- var/department_flag = 0
-
- //Players will be allowed to spawn in as jobs that are set to "Station"
- var/faction = "None"
-
- //How many players can be this job
- var/total_positions = 0
-
- //How many players can spawn in as this job
- var/spawn_positions = 0
-
- //How many players have this job
- var/current_positions = 0
-
- //Supervisors, who this person answers to directly
- var/supervisors = ""
-
- //Sellection screen color
- var/selection_color = "#ffffff"
-
-
- //If this is set to 1, a text is printed to the player when jobs are assigned, telling him that he should let admins know that he has to disconnect.
- var/req_admin_notify
-
- var/custom_spawn_text
-
- //If you have the use_age_restriction_for_jobs config option enabled and the database set up, this option will add a requirement for players to be at least minimal_player_age days old. (meaning they first signed in at least that many days before.)
- var/minimal_player_age = 0
-
- var/outfit = null
-
- var/exp_requirements = 0
-
- var/exp_type = ""
- var/exp_type_department = ""
-
- //The amount of good boy points playing this role will earn you towards a higher chance to roll antagonist next round
- //can be overridden by antag_rep.txt config
- var/antag_rep = 10
-
- var/list/mind_traits // Traits added to the mind of the mob assigned this job
-
-//Only override this proc
-//H is usually a human unless an /equip override transformed it
-/datum/job/proc/after_spawn(mob/living/H, mob/M, latejoin = FALSE)
- //do actions on H but send messages to M as the key may not have been transferred_yet
- if(mind_traits)
- for(var/t in mind_traits)
- ADD_TRAIT(H.mind, t, JOB_TRAIT)
-
-/datum/job/proc/announce(mob/living/carbon/human/H)
- if(head_announce)
- announce_head(H, head_announce)
-
-/datum/job/proc/override_latejoin_spawn(mob/living/carbon/human/H) //Return TRUE to force latejoining to not automatically place the person in latejoin shuttle/whatever.
- return FALSE
-
-//Used for a special check of whether to allow a client to latejoin as this job.
-/datum/job/proc/special_check_latejoin(client/C)
- return TRUE
-
-/datum/job/proc/GetAntagRep()
- . = CONFIG_GET(keyed_list/antag_rep)[lowertext(title)]
- if(. == null)
- return antag_rep
-
-//Don't override this unless the job transforms into a non-human (Silicons do this for example)
-/datum/job/proc/equip(mob/living/carbon/human/H, visualsOnly = FALSE, announce = TRUE, latejoin = FALSE, datum/outfit/outfit_override = null)
- if(!H)
- return FALSE
-
- if(CONFIG_GET(flag/enforce_human_authority) && (title in GLOB.command_positions))
- if(H.dna.species.id != "human")
- H.set_species(/datum/species/human)
- H.apply_pref_name("human", H.client)
-
- //Equip the rest of the gear
- H.dna.species.before_equip_job(src, H, visualsOnly)
-
- if(outfit_override || outfit)
- H.equipOutfit(outfit_override ? outfit_override : outfit, visualsOnly)
-
- H.dna.species.after_equip_job(src, H, visualsOnly)
-
- if(!visualsOnly && announce)
- announce(H)
-
-/datum/job/proc/get_access()
- if(!config) //Needed for robots.
- return src.minimal_access.Copy()
-
- . = list()
-
- if(CONFIG_GET(flag/jobs_have_minimal_access))
- . = src.minimal_access.Copy()
- else
- . = src.access.Copy()
-
- if(CONFIG_GET(flag/everyone_has_maint_access)) //Config has global maint access set
- . |= list(ACCESS_MAINT_TUNNELS)
-
-/datum/job/proc/announce_head(var/mob/living/carbon/human/H, var/channels) //tells the given channel that the given mob is the new department head. See communications.dm for valid channels.
- if(H && GLOB.announcement_systems.len)
- //timer because these should come after the captain announcement
- SSticker.OnRoundstart(CALLBACK(GLOBAL_PROC, .proc/addtimer, CALLBACK(pick(GLOB.announcement_systems), /obj/machinery/announcement_system/proc/announce, "NEWHEAD", H.real_name, H.job, channels), 1))
-
-//If the configuration option is set to require players to be logged as old enough to play certain jobs, then this proc checks that they are, otherwise it just returns 1
-/datum/job/proc/player_old_enough(client/C)
- if(available_in_days(C) == 0)
- return TRUE //Available in 0 days = available right now = player is old enough to play.
- return FALSE
-
-
-/datum/job/proc/available_in_days(client/C)
- if(!C)
- return 0
- if(!CONFIG_GET(flag/use_age_restriction_for_jobs))
- return 0
- if(C.prefs.db_flags & DB_FLAG_EXEMPT)
- return 0
- if(!isnum(C.player_age))
- return 0 //This is only a number if the db connection is established, otherwise it is text: "Requires database", meaning these restrictions cannot be enforced
- if(!isnum(minimal_player_age))
- return 0
-
- return max(0, minimal_player_age - C.player_age)
-
-/datum/job/proc/config_check()
- return TRUE
-
-/datum/job/proc/map_check()
- return TRUE
-
-
-/datum/outfit/job
- name = "Standard Gear"
-
- var/jobtype = null
-
- uniform = /obj/item/clothing/under/color/grey
- id = /obj/item/card/id
- ears = /obj/item/radio/headset
- belt = /obj/item/pda
- back = /obj/item/storage/backpack
- shoes = /obj/item/clothing/shoes/sneakers/black
-
- var/backpack = /obj/item/storage/backpack
- var/satchel = /obj/item/storage/backpack/satchel
- var/duffelbag = /obj/item/storage/backpack/duffelbag
- var/box = /obj/item/storage/box/survival
-
- var/pda_slot = SLOT_BELT
-
-/datum/outfit/job/pre_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
- switch(H.backbag)
- if(GBACKPACK)
- back = /obj/item/storage/backpack //Grey backpack
- if(GSATCHEL)
- back = /obj/item/storage/backpack/satchel //Grey satchel
- if(GDUFFELBAG)
- back = /obj/item/storage/backpack/duffelbag //Grey Duffel bag
- if(LSATCHEL)
- back = /obj/item/storage/backpack/satchel/leather //Leather Satchel
- if(DSATCHEL)
- back = satchel //Department satchel
- if(DDUFFELBAG)
- back = duffelbag //Department duffel bag
- else
- back = backpack //Department backpack
-
- if(box)
- if(!backpack_contents)
- backpack_contents = list()
- backpack_contents.Insert(1, box) // Box always takes a first slot in backpack
- backpack_contents[box] = 1
-
-/datum/outfit/job/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
- if(visualsOnly)
- return
-
- var/datum/job/J = SSjob.GetJobType(jobtype)
- if(!J)
- J = SSjob.GetJob(H.job)
-
- if(H.nameless && J.dresscodecompliant)
- if(J.title in GLOB.command_positions)
- H.real_name = J.title
- else
- H.real_name = "[J.title] #[rand(10000, 99999)]"
-
- var/obj/item/card/id/C = H.wear_id
- if(istype(C))
- C.access = J.get_access()
- shuffle_inplace(C.access) // Shuffle access list to make NTNet passkeys less predictable
- C.registered_name = H.real_name
- C.assignment = J.title
- C.update_label()
- H.sec_hud_set_ID()
-
- var/obj/item/pda/PDA = H.get_item_by_slot(pda_slot)
- if(istype(PDA))
- PDA.owner = H.real_name
- PDA.ownjob = J.title
- PDA.update_label()
-
-/datum/outfit/job/get_chameleon_disguise_info()
- var/list/types = ..()
- types -= /obj/item/storage/backpack //otherwise this will override the actual backpacks
- types += backpack
- types += satchel
- types += duffelbag
- return types
+/datum/job
+ //The name of the job , used for preferences, bans and more. Make sure you know what you're doing before changing this.
+ var/title = "NOPE"
+
+ //Job access. The use of minimal_access or access is determined by a config setting: config.jobs_have_minimal_access
+ var/list/minimal_access = list() //Useful for servers which prefer to only have access given to the places a job absolutely needs (Larger server population)
+ var/list/access = list() //Useful for servers which either have fewer players, so each person needs to fill more than one role, or servers which like to give more access, so players can't hide forever in their super secure departments (I'm looking at you, chemistry!)
+
+ //Determines who can demote this position
+ var/department_head = list()
+
+ //Tells the given channels that the given mob is the new department head. See communications.dm for valid channels.
+ var/list/head_announce = null
+
+ //Bitflags for the job
+ var/flag = NONE //Deprecated
+ var/department_flag = NONE //Deprecated
+// var/auto_deadmin_role_flags = NONE
+
+ //Players will be allowed to spawn in as jobs that are set to "Station"
+ var/faction = "None"
+
+ //How many players can be this job
+ var/total_positions = 0
+
+ //How many players can spawn in as this job
+ var/spawn_positions = 0
+
+ //How many players have this job
+ var/current_positions = 0
+
+ //Supervisors, who this person answers to directly
+ var/supervisors = ""
+
+ //Sellection screen color
+ var/selection_color = "#ffffff"
+
+
+ //If this is set to 1, a text is printed to the player when jobs are assigned, telling him that he should let admins know that he has to disconnect.
+ var/req_admin_notify
+
+ // This is for Citadel specific tweaks to job notices.
+ var/custom_spawn_text
+
+ //If you have the use_age_restriction_for_jobs config option enabled and the database set up, this option will add a requirement for players to be at least minimal_player_age days old. (meaning they first signed in at least that many days before.)
+ var/minimal_player_age = 0
+
+ var/outfit = null
+
+ var/exp_requirements = 0
+
+ var/exp_type = ""
+ var/exp_type_department = ""
+
+ //The amount of good boy points playing this role will earn you towards a higher chance to roll antagonist next round
+ //can be overridden by antag_rep.txt config
+ var/antag_rep = 10
+
+ var/list/mind_traits // Traits added to the mind of the mob assigned this job
+ var/list/blacklisted_quirks //list of quirk typepaths blacklisted.
+
+ var/display_order = JOB_DISPLAY_ORDER_DEFAULT
+
+//Only override this proc
+//H is usually a human unless an /equip override transformed it
+/datum/job/proc/after_spawn(mob/living/H, mob/M, latejoin = FALSE)
+ //do actions on H but send messages to M as the key may not have been transferred_yet
+ if(mind_traits)
+ for(var/t in mind_traits)
+ ADD_TRAIT(H.mind, t, JOB_TRAIT)
+
+/datum/job/proc/announce(mob/living/carbon/human/H)
+ if(head_announce)
+ announce_head(H, head_announce)
+
+/datum/job/proc/override_latejoin_spawn(mob/living/carbon/human/H) //Return TRUE to force latejoining to not automatically place the person in latejoin shuttle/whatever.
+ return FALSE
+
+//Used for a special check of whether to allow a client to latejoin as this job.
+/datum/job/proc/special_check_latejoin(client/C)
+ return TRUE
+
+/datum/job/proc/GetAntagRep()
+ . = CONFIG_GET(keyed_list/antag_rep)[lowertext(title)]
+ if(. == null)
+ return antag_rep
+
+//Don't override this unless the job transforms into a non-human (Silicons do this for example)
+/datum/job/proc/equip(mob/living/carbon/human/H, visualsOnly = FALSE, announce = TRUE, latejoin = FALSE, datum/outfit/outfit_override = null, client/preference_source)
+ if(!H)
+ return FALSE
+
+ if(CONFIG_GET(flag/enforce_human_authority) && (title in GLOB.command_positions))
+ if(H.dna.species.id != "human")
+ H.set_species(/datum/species/human)
+ H.apply_pref_name("human", preference_source)
+
+ //Equip the rest of the gear
+ H.dna.species.before_equip_job(src, H, visualsOnly)
+
+ if(outfit_override || outfit)
+ H.equipOutfit(outfit_override ? outfit_override : outfit, visualsOnly)
+
+ H.dna.species.after_equip_job(src, H, visualsOnly)
+
+ if(!visualsOnly && announce)
+ announce(H)
+
+/datum/job/proc/get_access()
+ if(!config) //Needed for robots.
+ return src.minimal_access.Copy()
+
+ . = list()
+
+ if(CONFIG_GET(flag/jobs_have_minimal_access))
+ . = src.minimal_access.Copy()
+ else
+ . = src.access.Copy()
+
+ if(CONFIG_GET(flag/everyone_has_maint_access)) //Config has global maint access set
+ . |= list(ACCESS_MAINT_TUNNELS)
+
+/datum/job/proc/announce_head(var/mob/living/carbon/human/H, var/channels) //tells the given channel that the given mob is the new department head. See communications.dm for valid channels.
+ if(H && GLOB.announcement_systems.len)
+ //timer because these should come after the captain announcement
+ SSticker.OnRoundstart(CALLBACK(GLOBAL_PROC, .proc/addtimer, CALLBACK(pick(GLOB.announcement_systems), /obj/machinery/announcement_system/proc/announce, "NEWHEAD", H.real_name, H.job, channels), 1))
+
+//If the configuration option is set to require players to be logged as old enough to play certain jobs, then this proc checks that they are, otherwise it just returns 1
+/datum/job/proc/player_old_enough(client/C)
+ if(available_in_days(C) == 0)
+ return TRUE //Available in 0 days = available right now = player is old enough to play.
+ return FALSE
+
+
+/datum/job/proc/available_in_days(client/C)
+ if(!C)
+ return 0
+ if(!CONFIG_GET(flag/use_age_restriction_for_jobs))
+ return 0
+ if(!SSdbcore.Connect())
+ return 0 //Without a database connection we can't get a player's age so we'll assume they're old enough for all jobs
+ if(C.prefs.db_flags & DB_FLAG_EXEMPT)
+ return 0
+ if(!isnum(minimal_player_age))
+ return 0
+
+ return max(0, minimal_player_age - C.player_age)
+
+/datum/job/proc/config_check()
+ return TRUE
+
+/datum/job/proc/map_check()
+ return TRUE
+
+/datum/job/proc/radio_help_message(mob/M)
+ to_chat(M, "Prefix your message with :h to speak on your department's radio. To see other prefixes, look closely at your headset.")
+
+/datum/outfit/job
+ name = "Standard Gear"
+
+ var/jobtype = null
+
+ uniform = /obj/item/clothing/under/color/grey
+ id = /obj/item/card/id
+ ears = /obj/item/radio/headset
+ belt = /obj/item/pda
+ back = /obj/item/storage/backpack
+ shoes = /obj/item/clothing/shoes/sneakers/black
+ box = /obj/item/storage/box/survival
+
+ var/backpack = /obj/item/storage/backpack
+ var/satchel = /obj/item/storage/backpack/satchel
+ var/duffelbag = /obj/item/storage/backpack/duffelbag
+
+ var/pda_slot = SLOT_BELT
+
+/datum/outfit/job/pre_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
+ switch(H.backbag)
+ if(GBACKPACK)
+ back = /obj/item/storage/backpack //Grey backpack
+ if(GSATCHEL)
+ back = /obj/item/storage/backpack/satchel //Grey satchel
+ if(GDUFFELBAG)
+ back = /obj/item/storage/backpack/duffelbag //Grey Duffel bag
+ if(LSATCHEL)
+ back = /obj/item/storage/backpack/satchel/leather //Leather Satchel
+ if(DSATCHEL)
+ back = satchel //Department satchel
+ if(DDUFFELBAG)
+ back = duffelbag //Department duffel bag
+ else
+ back = backpack //Department backpack
+
+ //converts the uniform string into the path we'll wear, whether it's the skirt or regular variant
+ var/holder
+ if(H.jumpsuit_style == PREF_SKIRT)
+ holder = "[uniform]/skirt"
+ if(!text2path(holder))
+ holder = "[uniform]"
+ else
+ holder = "[uniform]"
+ uniform = text2path(holder)
+
+/datum/outfit/job/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
+ if(visualsOnly)
+ return
+
+ var/datum/job/J = SSjob.GetJobType(jobtype)
+ if(!J)
+ J = SSjob.GetJob(H.job)
+
+ if(H.nameless && J.dresscodecompliant)
+ if(J.title in GLOB.command_positions)
+ H.real_name = J.title
+ else
+ H.real_name = "[J.title] #[rand(10000, 99999)]"
+
+ var/obj/item/card/id/C = H.wear_id
+ if(istype(C))
+ C.access = J.get_access()
+ shuffle_inplace(C.access) // Shuffle access list to make NTNet passkeys less predictable
+ C.registered_name = H.real_name
+ C.assignment = J.title
+ C.update_label()
+ H.sec_hud_set_ID()
+
+ var/obj/item/pda/PDA = H.get_item_by_slot(pda_slot)
+ if(istype(PDA))
+ PDA.owner = H.real_name
+ PDA.ownjob = J.title
+ PDA.update_label()
+
+/datum/outfit/job/get_chameleon_disguise_info()
+ var/list/types = ..()
+ types -= /obj/item/storage/backpack //otherwise this will override the actual backpacks
+ types += backpack
+ types += satchel
+ types += duffelbag
+ return types
+
+//Warden and regular officers add this result to their get_access()
+/datum/job/proc/check_config_for_sec_maint()
+ if(CONFIG_GET(flag/security_has_maint_access))
+ return list(ACCESS_MAINT_TUNNELS)
+ return list()
diff --git a/code/modules/jobs/job_types/silicon.dm b/code/modules/jobs/job_types/ai.dm
similarity index 71%
rename from code/modules/jobs/job_types/silicon.dm
rename to code/modules/jobs/job_types/ai.dm
index ab963eb8f3..4bcfab5836 100644
--- a/code/modules/jobs/job_types/silicon.dm
+++ b/code/modules/jobs/job_types/ai.dm
@@ -1,90 +1,69 @@
-/*
-AI
-*/
-/datum/job/ai
- title = "AI"
- flag = AI_JF
- department_flag = ENGSEC
- faction = "Station"
- total_positions = 1
- spawn_positions = 1
- selection_color = "#ccffcc"
- supervisors = "your laws"
- req_admin_notify = TRUE
- minimal_player_age = 30
- exp_requirements = 180
- exp_type = EXP_TYPE_CREW
- exp_type_department = EXP_TYPE_SILICON
- var/do_special_check = TRUE
-
-/datum/job/ai/equip(mob/living/carbon/human/H, visualsOnly, announce, latejoin, outfit_override)
- . = H.AIize(latejoin)
-
-/datum/job/ai/after_spawn(mob/H, mob/M, latejoin)
- . = ..()
- if(latejoin)
- var/obj/structure/AIcore/latejoin_inactive/lateJoinCore
- for(var/obj/structure/AIcore/latejoin_inactive/P in GLOB.latejoin_ai_cores)
- if(P.is_available())
- lateJoinCore = P
- GLOB.latejoin_ai_cores -= P
- break
- if(lateJoinCore)
- lateJoinCore.available = FALSE
- H.forceMove(lateJoinCore.loc)
- qdel(lateJoinCore)
- var/mob/living/silicon/ai/AI = H
- AI.apply_pref_name("ai", M.client) //If this runtimes oh well jobcode is fucked.
- AI.set_core_display_icon(null, M.client)
-
- //we may have been created after our borg
- if(SSticker.current_state == GAME_STATE_SETTING_UP)
- for(var/mob/living/silicon/robot/R in GLOB.silicon_mobs)
- if(!R.connected_ai)
- R.TryConnectToAI()
-
- if(latejoin)
- announce(AI)
-
-/datum/job/ai/override_latejoin_spawn()
- return TRUE
-
-/datum/job/ai/special_check_latejoin(client/C)
- if(!do_special_check)
- return TRUE
- for(var/i in GLOB.latejoin_ai_cores)
- var/obj/structure/AIcore/latejoin_inactive/LAI = i
- if(istype(LAI))
- if(LAI.is_available())
- return TRUE
- return FALSE
-
-/datum/job/ai/announce(mob/living/silicon/ai/AI)
- . = ..()
- SSticker.OnRoundstart(CALLBACK(GLOBAL_PROC, .proc/minor_announce, "[AI] has been downloaded to an empty bluespace-networked AI core at [AREACOORD(AI)]."))
-
-/datum/job/ai/config_check()
- return CONFIG_GET(flag/allow_ai)
-
-/*
-Cyborg
-*/
-/datum/job/cyborg
- title = "Cyborg"
- flag = CYBORG
- department_flag = ENGSEC
- faction = "Station"
- total_positions = 0
- spawn_positions = 1
- supervisors = "your laws and the AI" //Nodrak
- selection_color = "#ddffdd"
- minimal_player_age = 21
- exp_requirements = 120
- exp_type = EXP_TYPE_CREW
-
-/datum/job/cyborg/equip(mob/living/carbon/human/H, visualsOnly = FALSE, announce = TRUE, latejoin = FALSE, outfit_override = null)
- return H.Robotize(FALSE, latejoin)
-
-/datum/job/cyborg/after_spawn(mob/living/silicon/robot/R, mob/M)
- R.updatename(M.client)
- R.gender = NEUTER
+/datum/job/ai
+ title = "AI"
+ flag = AI_JF
+// auto_deadmin_role_flags = DEADMIN_POSITION_SILICON
+ department_flag = ENGSEC
+ faction = "Station"
+ total_positions = 1
+ spawn_positions = 1
+ selection_color = "#ccffcc"
+ supervisors = "your laws"
+ req_admin_notify = TRUE
+ minimal_player_age = 30
+ exp_requirements = 180
+ exp_type = EXP_TYPE_CREW
+ exp_type_department = EXP_TYPE_SILICON
+ display_order = JOB_DISPLAY_ORDER_AI
+ var/do_special_check = TRUE
+
+/datum/job/ai/equip(mob/living/carbon/human/H, visualsOnly, announce, latejoin, datum/outfit/outfit_override, client/preference_source = null)
+ if(visualsOnly)
+ CRASH("dynamic preview is unsupported")
+ . = H.AIize(latejoin,preference_source)
+
+/datum/job/ai/after_spawn(mob/H, mob/M, latejoin)
+ . = ..()
+ if(latejoin)
+ var/obj/structure/AIcore/latejoin_inactive/lateJoinCore
+ for(var/obj/structure/AIcore/latejoin_inactive/P in GLOB.latejoin_ai_cores)
+ if(P.is_available())
+ lateJoinCore = P
+ GLOB.latejoin_ai_cores -= P
+ break
+ if(lateJoinCore)
+ lateJoinCore.available = FALSE
+ H.forceMove(lateJoinCore.loc)
+ qdel(lateJoinCore)
+ var/mob/living/silicon/ai/AI = H
+ AI.apply_pref_name("ai", M.client) //If this runtimes oh well jobcode is fucked.
+ AI.set_core_display_icon(null, M.client)
+
+ //we may have been created after our borg
+ if(SSticker.current_state == GAME_STATE_SETTING_UP)
+ for(var/mob/living/silicon/robot/R in GLOB.silicon_mobs)
+ if(!R.connected_ai)
+ R.TryConnectToAI()
+
+ if(latejoin)
+ announce(AI)
+
+/datum/job/ai/override_latejoin_spawn()
+ return TRUE
+
+/datum/job/ai/special_check_latejoin(client/C)
+ if(!do_special_check)
+ return TRUE
+ for(var/i in GLOB.latejoin_ai_cores)
+ var/obj/structure/AIcore/latejoin_inactive/LAI = i
+ if(istype(LAI))
+ if(LAI.is_available())
+ return TRUE
+ return FALSE
+
+/datum/job/ai/announce(mob/living/silicon/ai/AI)
+ . = ..()
+ SSticker.OnRoundstart(CALLBACK(GLOBAL_PROC, .proc/minor_announce, "[AI] has been downloaded to an empty bluespace-networked AI core at [AREACOORD(AI)]."))
+
+/datum/job/ai/config_check()
+ return CONFIG_GET(flag/allow_ai)
+
diff --git a/code/modules/jobs/job_types/assistant.dm b/code/modules/jobs/job_types/assistant.dm
index 65805f73fd..c04560f849 100644
--- a/code/modules/jobs/job_types/assistant.dm
+++ b/code/modules/jobs/job_types/assistant.dm
@@ -14,7 +14,7 @@ Assistant
minimal_access = list() //See /datum/job/assistant/get_access()
outfit = /datum/outfit/job/assistant
antag_rep = 7
-
+ display_order = JOB_DISPLAY_ORDER_ASSISTANT
/datum/job/assistant/get_access()
if(CONFIG_GET(flag/assistants_have_maint_access) || !CONFIG_GET(flag/jobs_have_minimal_access)) //Config has assistant maint access set
@@ -30,6 +30,12 @@ Assistant
/datum/outfit/job/assistant/pre_equip(mob/living/carbon/human/H)
..()
if (CONFIG_GET(flag/grey_assistants))
- uniform = /obj/item/clothing/under/color/grey
+ if(H.jumpsuit_style == PREF_SUIT)
+ uniform = /obj/item/clothing/under/color/grey
+ else
+ uniform = /obj/item/clothing/under/skirt/color/grey
else
- uniform = /obj/item/clothing/under/color/random
+ if(H.jumpsuit_style == PREF_SUIT)
+ uniform = /obj/item/clothing/under/color/random
+ else
+ uniform = /obj/item/clothing/under/skirt/color/random
diff --git a/code/modules/jobs/job_types/atmospheric_technician.dm b/code/modules/jobs/job_types/atmospheric_technician.dm
new file mode 100644
index 0000000000..93775beca9
--- /dev/null
+++ b/code/modules/jobs/job_types/atmospheric_technician.dm
@@ -0,0 +1,44 @@
+/datum/job/atmos
+ title = "Atmospheric Technician"
+ flag = ATMOSTECH
+ department_head = list("Chief Engineer")
+ department_flag = ENGSEC
+ faction = "Station"
+ total_positions = 3
+ spawn_positions = 2
+ supervisors = "the chief engineer"
+ selection_color = "#ff9b3d"
+ exp_requirements = 60
+ exp_type = EXP_TYPE_CREW
+
+ outfit = /datum/outfit/job/atmos
+
+ access = list(ACCESS_ENGINE, ACCESS_ENGINE_EQUIP, ACCESS_TECH_STORAGE, ACCESS_MAINT_TUNNELS,
+ ACCESS_EXTERNAL_AIRLOCKS, ACCESS_CONSTRUCTION, ACCESS_ATMOSPHERICS, ACCESS_MINERAL_STOREROOM)
+ minimal_access = list(ACCESS_ATMOSPHERICS, ACCESS_MAINT_TUNNELS, ACCESS_CONSTRUCTION, ACCESS_MINERAL_STOREROOM)
+ display_order = JOB_DISPLAY_ORDER_ATMOSPHERIC_TECHNICIAN
+
+/datum/outfit/job/atmos
+ name = "Atmospheric Technician"
+ jobtype = /datum/job/atmos
+
+ belt = /obj/item/storage/belt/utility/atmostech
+ l_pocket = /obj/item/pda/atmos
+ ears = /obj/item/radio/headset/headset_eng
+ uniform = /obj/item/clothing/under/rank/atmospheric_technician
+ r_pocket = /obj/item/analyzer
+
+ backpack = /obj/item/storage/backpack/industrial
+ satchel = /obj/item/storage/backpack/satchel/eng
+ duffelbag = /obj/item/storage/backpack/duffelbag/engineering
+ box = /obj/item/storage/box/engineer
+ pda_slot = SLOT_L_STORE
+ backpack_contents = list(/obj/item/modular_computer/tablet/preset/advanced=1)
+
+/datum/outfit/job/atmos/rig
+ name = "Atmospheric Technician (Hardsuit)"
+
+ mask = /obj/item/clothing/mask/gas
+ suit = /obj/item/clothing/suit/space/hardsuit/engine/atmos
+ suit_store = /obj/item/tank/internals/oxygen
+ internals_slot = SLOT_S_STORE
diff --git a/code/modules/jobs/job_types/bartender.dm b/code/modules/jobs/job_types/bartender.dm
new file mode 100644
index 0000000000..0ace449757
--- /dev/null
+++ b/code/modules/jobs/job_types/bartender.dm
@@ -0,0 +1,30 @@
+/datum/job/bartender
+ title = "Bartender"
+ flag = BARTENDER
+ department_head = list("Head of Personnel")
+ department_flag = CIVILIAN
+ faction = "Station"
+ total_positions = 1
+ spawn_positions = 1
+ supervisors = "the head of personnel"
+ selection_color = "#bbe291"
+ exp_type_department = EXP_TYPE_SERVICE // This is so the jobs menu can work properly
+
+ outfit = /datum/outfit/job/bartender
+
+ access = list(ACCESS_HYDROPONICS, ACCESS_BAR, ACCESS_KITCHEN, ACCESS_MORGUE, ACCESS_WEAPONS, ACCESS_MINERAL_STOREROOM)
+ minimal_access = list(ACCESS_BAR, ACCESS_MINERAL_STOREROOM)
+ display_order = JOB_DISPLAY_ORDER_BARTENDER
+
+/datum/outfit/job/bartender
+ name = "Bartender"
+ jobtype = /datum/job/bartender
+
+ glasses = /obj/item/clothing/glasses/sunglasses/reagent
+ belt = /obj/item/pda/bar
+ ears = /obj/item/radio/headset/headset_srv
+ uniform = /obj/item/clothing/under/rank/bartender
+ suit = /obj/item/clothing/suit/armor/vest
+ backpack_contents = list(/obj/item/storage/box/beanbag=1,/obj/item/book/granter/action/drink_fling=1)
+ shoes = /obj/item/clothing/shoes/laceup
+
diff --git a/code/modules/jobs/job_types/botanist.dm b/code/modules/jobs/job_types/botanist.dm
new file mode 100644
index 0000000000..e6338d9b0a
--- /dev/null
+++ b/code/modules/jobs/job_types/botanist.dm
@@ -0,0 +1,32 @@
+/datum/job/hydro
+ title = "Botanist"
+ flag = BOTANIST
+ department_head = list("Head of Personnel")
+ department_flag = CIVILIAN
+ faction = "Station"
+ total_positions = 3
+ spawn_positions = 2
+ supervisors = "the head of personnel"
+ selection_color = "#bbe291"
+
+ outfit = /datum/outfit/job/botanist
+
+ access = list(ACCESS_HYDROPONICS, ACCESS_BAR, ACCESS_KITCHEN, ACCESS_MORGUE, ACCESS_MINERAL_STOREROOM)
+ minimal_access = list(ACCESS_HYDROPONICS, ACCESS_MORGUE, ACCESS_MINERAL_STOREROOM)
+ display_order = JOB_DISPLAY_ORDER_BOTANIST
+
+/datum/outfit/job/botanist
+ name = "Botanist"
+ jobtype = /datum/job/hydro
+
+ belt = /obj/item/pda/botanist
+ ears = /obj/item/radio/headset/headset_srv
+ uniform = /obj/item/clothing/under/rank/hydroponics
+ suit = /obj/item/clothing/suit/apron
+ gloves =/obj/item/clothing/gloves/botanic_leather
+ suit_store = /obj/item/plant_analyzer
+
+ backpack = /obj/item/storage/backpack/botany
+ satchel = /obj/item/storage/backpack/satchel/hyd
+
+
diff --git a/code/modules/jobs/job_types/captain.dm b/code/modules/jobs/job_types/captain.dm
old mode 100755
new mode 100644
index cd9c914e7a..fea8557b40
--- a/code/modules/jobs/job_types/captain.dm
+++ b/code/modules/jobs/job_types/captain.dm
@@ -1,111 +1,66 @@
-/*
-Captain
-*/
-/datum/job/captain
- title = "Captain"
- flag = CAPTAIN
- department_head = list("CentCom")
- department_flag = ENGSEC
- faction = "Station"
- total_positions = 1
- spawn_positions = 1
- supervisors = "Nanotrasen officials and Space law"
- selection_color = "#ccccff"
- req_admin_notify = 1
- minimal_player_age = 14
- exp_requirements = 180
- exp_type = EXP_TYPE_CREW
-
- outfit = /datum/outfit/job/captain
-
- access = list() //See get_access()
- minimal_access = list() //See get_access()
-
-/datum/job/captain/get_access()
- return get_all_accesses()
-
-/datum/job/captain/announce(mob/living/carbon/human/H)
- ..()
- SSticker.OnRoundstart(CALLBACK(GLOBAL_PROC, .proc/minor_announce, "Captain [H.nameless ? "" : "[H.real_name] "]on deck!"))
-
-/datum/outfit/job/captain
- name = "Captain"
- jobtype = /datum/job/captain
-
- id = /obj/item/card/id/gold
- belt = /obj/item/pda/captain
- glasses = /obj/item/clothing/glasses/sunglasses
- ears = /obj/item/radio/headset/heads/captain/alt
- gloves = /obj/item/clothing/gloves/color/captain
- uniform = /obj/item/clothing/under/rank/captain
- suit = /obj/item/clothing/suit/armor/vest/capcarapace
- shoes = /obj/item/clothing/shoes/sneakers/brown
- head = /obj/item/clothing/head/caphat
- backpack_contents = list(/obj/item/melee/classic_baton/telescopic=1, /obj/item/station_charter=1)
-
- backpack = /obj/item/storage/backpack/captain
- satchel = /obj/item/storage/backpack/satchel/cap
- duffelbag = /obj/item/storage/backpack/duffelbag/captain
-
- implants = list(/obj/item/implant/mindshield)
- accessory = /obj/item/clothing/accessory/medal/gold/captain
-
- chameleon_extras = list(/obj/item/gun/energy/e_gun, /obj/item/stamp/captain)
-
-/datum/outfit/job/captain/hardsuit
- name = "Captain (Hardsuit)"
-
- mask = /obj/item/clothing/mask/gas/sechailer
- suit = /obj/item/clothing/suit/space/hardsuit/captain
- suit_store = /obj/item/tank/internals/oxygen
-
-/*
-Head of Personnel
-*/
-/datum/job/hop
- title = "Head of Personnel"
- flag = HOP
- department_head = list("Captain")
- department_flag = CIVILIAN
- head_announce = list("Supply", "Service")
- faction = "Station"
- total_positions = 1
- spawn_positions = 1
- supervisors = "the captain"
- selection_color = "#ddddff"
- req_admin_notify = 1
- minimal_player_age = 10
- exp_requirements = 180
- exp_type = EXP_TYPE_CREW
- exp_type_department = EXP_TYPE_SUPPLY
-
- outfit = /datum/outfit/job/hop
-
- access = list(ACCESS_SECURITY, ACCESS_SEC_DOORS, ACCESS_COURT, ACCESS_WEAPONS,
- ACCESS_MEDICAL, ACCESS_ENGINE, ACCESS_CHANGE_IDS, ACCESS_AI_UPLOAD, ACCESS_EVA, ACCESS_HEADS,
- ACCESS_ALL_PERSONAL_LOCKERS, ACCESS_MAINT_TUNNELS, ACCESS_BAR, ACCESS_JANITOR, ACCESS_CONSTRUCTION, ACCESS_MORGUE,
- ACCESS_CREMATORIUM, ACCESS_KITCHEN, ACCESS_CARGO, ACCESS_CARGO_BOT, ACCESS_MAILSORTING, ACCESS_QM, ACCESS_HYDROPONICS, ACCESS_LAWYER,
- ACCESS_THEATRE, ACCESS_CHAPEL_OFFICE, ACCESS_LIBRARY, ACCESS_RESEARCH, ACCESS_MINING, ACCESS_VAULT, ACCESS_MINING_STATION,
- ACCESS_HOP, ACCESS_RC_ANNOUNCE, ACCESS_KEYCARD_AUTH, ACCESS_GATEWAY, ACCESS_MINERAL_STOREROOM)
- minimal_access = list(ACCESS_SECURITY, ACCESS_SEC_DOORS, ACCESS_COURT, ACCESS_WEAPONS,
- ACCESS_MEDICAL, ACCESS_ENGINE, ACCESS_CHANGE_IDS, ACCESS_AI_UPLOAD, ACCESS_EVA, ACCESS_HEADS,
- ACCESS_ALL_PERSONAL_LOCKERS, ACCESS_MAINT_TUNNELS, ACCESS_BAR, ACCESS_JANITOR, ACCESS_CONSTRUCTION, ACCESS_MORGUE,
- ACCESS_CREMATORIUM, ACCESS_KITCHEN, ACCESS_CARGO, ACCESS_CARGO_BOT, ACCESS_MAILSORTING, ACCESS_QM, ACCESS_HYDROPONICS, ACCESS_LAWYER,
- ACCESS_THEATRE, ACCESS_CHAPEL_OFFICE, ACCESS_LIBRARY, ACCESS_RESEARCH, ACCESS_MINING, ACCESS_VAULT, ACCESS_MINING_STATION,
- ACCESS_HOP, ACCESS_RC_ANNOUNCE, ACCESS_KEYCARD_AUTH, ACCESS_GATEWAY, ACCESS_MINERAL_STOREROOM)
-
-
-/datum/outfit/job/hop
- name = "Head of Personnel"
- jobtype = /datum/job/hop
-
- id = /obj/item/card/id/silver
- belt = /obj/item/pda/heads/hop
- ears = /obj/item/radio/headset/heads/hop
- uniform = /obj/item/clothing/under/rank/head_of_personnel
- shoes = /obj/item/clothing/shoes/sneakers/brown
- head = /obj/item/clothing/head/hopcap
- backpack_contents = list(/obj/item/storage/box/ids=1,\
- /obj/item/melee/classic_baton/telescopic=1, /obj/item/modular_computer/tablet/preset/advanced = 1)
-
- chameleon_extras = list(/obj/item/gun/energy/e_gun, /obj/item/stamp/hop)
+/datum/job/captain
+ title = "Captain"
+ flag = CAPTAIN
+// auto_deadmin_role_flags = DEADMIN_POSITION_HEAD|DEADMIN_POSITION_SECURITY //:eyes:
+ department_head = list("CentCom")
+ department_flag = ENGSEC
+ faction = "Station"
+ total_positions = 1
+ spawn_positions = 1
+ supervisors = "Nanotrasen officials and Space law"
+ selection_color = "#aac1ee"
+ req_admin_notify = 1
+ minimal_player_age = 14
+ exp_requirements = 180
+ exp_type = EXP_TYPE_CREW
+ exp_type_department = EXP_TYPE_COMMAND
+
+ outfit = /datum/outfit/job/captain
+
+ access = list() //See get_access()
+ minimal_access = list() //See get_access()
+
+ mind_traits = list(TRAIT_CAPTAIN_METABOLISM)
+// mind_traits = list(TRAIT_DISK_VERIFIER)
+
+ display_order = JOB_DISPLAY_ORDER_CAPTAIN
+
+ blacklisted_quirks = list(/datum/quirk/mute, /datum/quirk/brainproblems, /datum/quirk/insanity)
+
+/datum/job/captain/get_access()
+ return get_all_accesses()
+
+/datum/job/captain/announce(mob/living/carbon/human/H)
+ ..()
+ SSticker.OnRoundstart(CALLBACK(GLOBAL_PROC, .proc/minor_announce, "Captain [H.nameless ? "" : "[H.real_name] "]on deck!"))
+
+/datum/outfit/job/captain
+ name = "Captain"
+ jobtype = /datum/job/captain
+
+ id = /obj/item/card/id/gold
+ belt = /obj/item/pda/captain
+ glasses = /obj/item/clothing/glasses/sunglasses
+ ears = /obj/item/radio/headset/heads/captain/alt
+ gloves = /obj/item/clothing/gloves/color/captain
+ uniform = /obj/item/clothing/under/rank/captain
+ suit = /obj/item/clothing/suit/armor/vest/capcarapace
+ shoes = /obj/item/clothing/shoes/sneakers/brown
+ head = /obj/item/clothing/head/caphat
+ backpack_contents = list(/obj/item/melee/classic_baton/telescopic=1, /obj/item/station_charter=1)
+
+ backpack = /obj/item/storage/backpack/captain
+ satchel = /obj/item/storage/backpack/satchel/cap
+ duffelbag = /obj/item/storage/backpack/duffelbag/captain
+
+ implants = list(/obj/item/implant/mindshield)
+ accessory = /obj/item/clothing/accessory/medal/gold/captain
+
+ chameleon_extras = list(/obj/item/gun/energy/e_gun, /obj/item/stamp/captain)
+
+/datum/outfit/job/captain/hardsuit
+ name = "Captain (Hardsuit)"
+
+ mask = /obj/item/clothing/mask/gas/sechailer
+ suit = /obj/item/clothing/suit/space/hardsuit/captain
+ suit_store = /obj/item/tank/internals/oxygen
\ No newline at end of file
diff --git a/code/modules/jobs/job_types/cargo_service.dm b/code/modules/jobs/job_types/cargo_service.dm
deleted file mode 100644
index 22ef2a9211..0000000000
--- a/code/modules/jobs/job_types/cargo_service.dm
+++ /dev/null
@@ -1,290 +0,0 @@
-/*
-Quartermaster
-*/
-/datum/job/qm
- title = "Quartermaster"
- flag = QUARTERMASTER
- department_head = list("Head of Personnel")
- department_flag = CIVILIAN
- faction = "Station"
- total_positions = 1
- spawn_positions = 1
- supervisors = "the head of personnel"
- selection_color = "#d7b088"
-
- outfit = /datum/outfit/job/quartermaster
-
- access = list(ACCESS_MAINT_TUNNELS, ACCESS_MAILSORTING, ACCESS_CARGO, ACCESS_CARGO_BOT, ACCESS_QM, ACCESS_MINING, ACCESS_MINING_STATION, ACCESS_MINERAL_STOREROOM, ACCESS_VAULT)
- minimal_access = list(ACCESS_MAINT_TUNNELS, ACCESS_MAILSORTING, ACCESS_CARGO, ACCESS_CARGO_BOT, ACCESS_QM, ACCESS_MINING, ACCESS_MINING_STATION, ACCESS_MINERAL_STOREROOM, ACCESS_VAULT)
-
-/datum/outfit/job/quartermaster
- name = "Quartermaster"
- jobtype = /datum/job/qm
-
- belt = /obj/item/pda/quartermaster
- ears = /obj/item/radio/headset/headset_cargo
- uniform = /obj/item/clothing/under/rank/cargo
- shoes = /obj/item/clothing/shoes/sneakers/brown
- glasses = /obj/item/clothing/glasses/sunglasses
- l_hand = /obj/item/clipboard
-
- chameleon_extras = /obj/item/stamp/qm
-
-/*
-Cargo Technician
-*/
-/datum/job/cargo_tech
- title = "Cargo Technician"
- flag = CARGOTECH
- department_head = list("Head of Personnel")
- department_flag = CIVILIAN
- faction = "Station"
- total_positions = 3
- spawn_positions = 2
- supervisors = "the quartermaster and the head of personnel"
- selection_color = "#dcba97"
-
- outfit = /datum/outfit/job/cargo_tech
-
- access = list(ACCESS_MAINT_TUNNELS, ACCESS_MAILSORTING, ACCESS_CARGO, ACCESS_CARGO_BOT, ACCESS_QM, ACCESS_MINING, ACCESS_MINING_STATION, ACCESS_MINERAL_STOREROOM)
- minimal_access = list(ACCESS_MAINT_TUNNELS, ACCESS_CARGO, ACCESS_CARGO_BOT, ACCESS_MAILSORTING, ACCESS_MINERAL_STOREROOM)
-
-/datum/outfit/job/cargo_tech
- name = "Cargo Technician"
- jobtype = /datum/job/cargo_tech
-
- belt = /obj/item/pda/cargo
- ears = /obj/item/radio/headset/headset_cargo
- uniform = /obj/item/clothing/under/rank/cargotech
- l_hand = /obj/item/export_scanner
-
-/*
-Shaft Miner
-*/
-/datum/job/mining
- title = "Shaft Miner"
- flag = MINER
- department_head = list("Head of Personnel")
- department_flag = CIVILIAN
- faction = "Station"
- total_positions = 3
- spawn_positions = 3
- supervisors = "the quartermaster and the head of personnel"
- selection_color = "#dcba97"
- custom_spawn_text = "Remember, you are a miner, not a hunter. Hunting monsters is not a requirement of your job, the only requirement of your job is to provide materials for the station. Don't be afraid to run away if you're inexperienced with fighting the mining area's locals."
-
- outfit = /datum/outfit/job/miner
-
- access = list(ACCESS_MAINT_TUNNELS, ACCESS_MAILSORTING, ACCESS_CARGO, ACCESS_CARGO_BOT, ACCESS_QM, ACCESS_MINING, ACCESS_MINING_STATION, ACCESS_MINERAL_STOREROOM)
- minimal_access = list(ACCESS_MINING, ACCESS_MINING_STATION, ACCESS_MAILSORTING, ACCESS_MINERAL_STOREROOM)
-
-/datum/outfit/job/miner
- name = "Shaft Miner (Lavaland)"
- jobtype = /datum/job/mining
-
- belt = /obj/item/pda/shaftminer
- ears = /obj/item/radio/headset/headset_cargo/mining
- shoes = /obj/item/clothing/shoes/workboots/mining
- gloves = /obj/item/clothing/gloves/color/black
- uniform = /obj/item/clothing/under/rank/miner/lavaland
- l_pocket = /obj/item/reagent_containers/hypospray/medipen/survival
- r_pocket = /obj/item/flashlight/seclite
- backpack_contents = list(
- /obj/item/storage/bag/ore=1,\
- /obj/item/kitchen/knife/combat/survival=1,\
- /obj/item/mining_voucher=1,\
- /obj/item/suit_voucher=1,\
- /obj/item/stack/marker_beacon/ten=1)
-
- backpack = /obj/item/storage/backpack/explorer
- satchel = /obj/item/storage/backpack/satchel/explorer
- duffelbag = /obj/item/storage/backpack/duffelbag
- box = /obj/item/storage/box/survival_mining
-
- chameleon_extras = /obj/item/gun/energy/kinetic_accelerator
-
-/datum/outfit/job/miner/asteroid
- name = "Shaft Miner (Asteroid)"
- uniform = /obj/item/clothing/under/rank/miner
- shoes = /obj/item/clothing/shoes/workboots
-
-/datum/outfit/job/miner/equipped
- name = "Shaft Miner (Lavaland + Equipment)"
- suit = /obj/item/clothing/suit/hooded/explorer/standard
- mask = /obj/item/clothing/mask/gas/explorer
- glasses = /obj/item/clothing/glasses/meson
- suit_store = /obj/item/tank/internals/oxygen
- internals_slot = SLOT_S_STORE
- backpack_contents = list(
- /obj/item/storage/bag/ore=1,
- /obj/item/kitchen/knife/combat/survival=1,
- /obj/item/mining_voucher=1,
- /obj/item/t_scanner/adv_mining_scanner/lesser=1,
- /obj/item/gun/energy/kinetic_accelerator=1,\
- /obj/item/stack/marker_beacon/ten=1)
-
-/datum/outfit/job/miner/equipped/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
- ..()
- if(visualsOnly)
- return
- if(istype(H.wear_suit, /obj/item/clothing/suit/hooded))
- var/obj/item/clothing/suit/hooded/S = H.wear_suit
- S.ToggleHood()
-
-/datum/outfit/job/miner/equipped/hardsuit
- name = "Shaft Miner (Equipment + Hardsuit)"
- suit = /obj/item/clothing/suit/space/hardsuit/mining
- mask = /obj/item/clothing/mask/breath
-
-
-/*
-Bartender
-*/
-/datum/job/bartender
- title = "Bartender"
- flag = BARTENDER
- department_head = list("Head of Personnel")
- department_flag = CIVILIAN
- faction = "Station"
- total_positions = 1
- spawn_positions = 1
- supervisors = "the head of personnel"
- selection_color = "#bbe291"
-
- outfit = /datum/outfit/job/bartender
-
- access = list(ACCESS_HYDROPONICS, ACCESS_BAR, ACCESS_KITCHEN, ACCESS_MORGUE, ACCESS_WEAPONS, ACCESS_MINERAL_STOREROOM)
- minimal_access = list(ACCESS_BAR, ACCESS_MINERAL_STOREROOM)
-
-
-/datum/outfit/job/bartender
- name = "Bartender"
- jobtype = /datum/job/bartender
-
- glasses = /obj/item/clothing/glasses/sunglasses/reagent
- belt = /obj/item/pda/bar
- ears = /obj/item/radio/headset/headset_srv
- uniform = /obj/item/clothing/under/rank/bartender
- suit = /obj/item/clothing/suit/armor/vest
- backpack_contents = list(/obj/item/storage/box/beanbag=1,/obj/item/book/granter/action/drink_fling=1)
- shoes = /obj/item/clothing/shoes/laceup
-
-/*
-Cook
-*/
-/datum/job/cook
- title = "Cook"
- flag = COOK
- department_head = list("Head of Personnel")
- department_flag = CIVILIAN
- faction = "Station"
- total_positions = 2
- spawn_positions = 1
- supervisors = "the head of personnel"
- selection_color = "#bbe291"
- var/cooks = 0 //Counts cooks amount
-
- outfit = /datum/outfit/job/cook
-
- access = list(ACCESS_HYDROPONICS, ACCESS_BAR, ACCESS_KITCHEN, ACCESS_MORGUE, ACCESS_MINERAL_STOREROOM)
- minimal_access = list(ACCESS_KITCHEN, ACCESS_MORGUE, ACCESS_MINERAL_STOREROOM)
-
-/datum/outfit/job/cook
- name = "Cook"
- jobtype = /datum/job/cook
-
- belt = /obj/item/pda/cook
- ears = /obj/item/radio/headset/headset_srv
- uniform = /obj/item/clothing/under/rank/chef
- suit = /obj/item/clothing/suit/toggle/chef
- head = /obj/item/clothing/head/chefhat
- mask = /obj/item/clothing/mask/fakemoustache/italian
- backpack_contents = list(/obj/item/sharpener = 1)
-
-/datum/outfit/job/cook/pre_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
- ..()
- var/datum/job/cook/J = SSjob.GetJobType(jobtype)
- if(J) // Fix for runtime caused by invalid job being passed
- if(J.cooks>0)//Cooks
- suit = /obj/item/clothing/suit/apron/chef
- head = /obj/item/clothing/head/soft/mime
- if(!visualsOnly)
- J.cooks++
-
-/datum/outfit/job/cook/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
- ..()
- if(visualsOnly)
- return
- var/list/possible_boxes = subtypesof(/obj/item/storage/box/ingredients)
- var/chosen_box = pick(possible_boxes)
- var/obj/item/storage/box/I = new chosen_box(src)
- H.equip_to_slot_or_del(I,SLOT_IN_BACKPACK)
- var/datum/martial_art/cqc/under_siege/justacook = new
- justacook.teach(H)
-
-/*
-Botanist
-*/
-/datum/job/hydro
- title = "Botanist"
- flag = BOTANIST
- department_head = list("Head of Personnel")
- department_flag = CIVILIAN
- faction = "Station"
- total_positions = 3
- spawn_positions = 2
- supervisors = "the head of personnel"
- selection_color = "#bbe291"
-
- outfit = /datum/outfit/job/botanist
-
- access = list(ACCESS_HYDROPONICS, ACCESS_BAR, ACCESS_KITCHEN, ACCESS_MORGUE, ACCESS_MINERAL_STOREROOM)
- minimal_access = list(ACCESS_HYDROPONICS, ACCESS_MORGUE, ACCESS_MINERAL_STOREROOM)
- // Removed tox and chem access because STOP PISSING OFF THE CHEMIST GUYS
- // Removed medical access because WHAT THE FUCK YOU AREN'T A DOCTOR YOU GROW WHEAT
- // Given Morgue access because they have a viable means of cloning.
-
-
-/datum/outfit/job/botanist
- name = "Botanist"
- jobtype = /datum/job/hydro
-
- belt = /obj/item/pda/botanist
- ears = /obj/item/radio/headset/headset_srv
- uniform = /obj/item/clothing/under/rank/hydroponics
- suit = /obj/item/clothing/suit/apron
- gloves =/obj/item/clothing/gloves/botanic_leather
- suit_store = /obj/item/plant_analyzer
-
- backpack = /obj/item/storage/backpack/botany
- satchel = /obj/item/storage/backpack/satchel/hyd
-
-
-/*
-Janitor
-*/
-/datum/job/janitor
- title = "Janitor"
- flag = JANITOR
- department_head = list("Head of Personnel")
- department_flag = CIVILIAN
- faction = "Station"
- total_positions = 2
- spawn_positions = 1
- supervisors = "the head of personnel"
- selection_color = "#bbe291"
- var/global/janitors = 0
-
- outfit = /datum/outfit/job/janitor
-
- access = list(ACCESS_JANITOR, ACCESS_MAINT_TUNNELS, ACCESS_MINERAL_STOREROOM)
- minimal_access = list(ACCESS_JANITOR, ACCESS_MAINT_TUNNELS, ACCESS_MINERAL_STOREROOM)
-
-/datum/outfit/job/janitor
- name = "Janitor"
- jobtype = /datum/job/janitor
-
- belt = /obj/item/pda/janitor
- ears = /obj/item/radio/headset/headset_srv
- uniform = /obj/item/clothing/under/rank/janitor
- backpack_contents = list(/obj/item/modular_computer/tablet/preset/advanced=1)
diff --git a/code/modules/jobs/job_types/cargo_technician.dm b/code/modules/jobs/job_types/cargo_technician.dm
new file mode 100644
index 0000000000..3ceb29bae2
--- /dev/null
+++ b/code/modules/jobs/job_types/cargo_technician.dm
@@ -0,0 +1,27 @@
+/datum/job/cargo_tech
+ title = "Cargo Technician"
+ flag = CARGOTECH
+ department_head = list("Quartermaster")
+ department_flag = CIVILIAN
+ faction = "Station"
+ total_positions = 3
+ spawn_positions = 2
+ supervisors = "the quartermaster"
+ selection_color = "#ca8f55"
+
+ outfit = /datum/outfit/job/cargo_tech
+
+ access = list(ACCESS_MAINT_TUNNELS, ACCESS_MAILSORTING, ACCESS_CARGO, ACCESS_QM, ACCESS_MINING, ACCESS_MINING_STATION, ACCESS_MINERAL_STOREROOM)
+ minimal_access = list(ACCESS_MAINT_TUNNELS, ACCESS_CARGO, ACCESS_MAILSORTING, ACCESS_MINERAL_STOREROOM)
+
+ display_order = JOB_DISPLAY_ORDER_CARGO_TECHNICIAN
+
+/datum/outfit/job/cargo_tech
+ name = "Cargo Technician"
+ jobtype = /datum/job/cargo_tech
+
+ belt = /obj/item/pda/cargo
+ ears = /obj/item/radio/headset/headset_cargo
+ uniform = /obj/item/clothing/under/rank/cargotech
+ l_hand = /obj/item/export_scanner
+
diff --git a/code/modules/jobs/job_types/civilian_chaplain.dm b/code/modules/jobs/job_types/chaplain.dm
similarity index 65%
rename from code/modules/jobs/job_types/civilian_chaplain.dm
rename to code/modules/jobs/job_types/chaplain.dm
index 2d190cfe60..97b1edc8c2 100644
--- a/code/modules/jobs/job_types/civilian_chaplain.dm
+++ b/code/modules/jobs/job_types/chaplain.dm
@@ -1,95 +1,121 @@
-//Due to how large this one is it gets its own file
-/*
-Chaplain
-*/
-/datum/job/chaplain
- title = "Chaplain"
- flag = CHAPLAIN
- department_head = list("Head of Personnel")
- department_flag = CIVILIAN
- faction = "Station"
- total_positions = 1
- spawn_positions = 1
- supervisors = "the head of personnel"
- selection_color = "#dddddd"
-
- outfit = /datum/outfit/job/chaplain
-
- access = list(ACCESS_MORGUE, ACCESS_CHAPEL_OFFICE, ACCESS_CREMATORIUM, ACCESS_THEATRE)
- minimal_access = list(ACCESS_MORGUE, ACCESS_CHAPEL_OFFICE, ACCESS_CREMATORIUM, ACCESS_THEATRE)
-
-/datum/job/chaplain/after_spawn(mob/living/H, mob/M)
- . = ..()
- if(H.mind)
- H.mind.isholy = TRUE
-
- var/obj/item/storage/book/bible/booze/B = new
-
- if(GLOB.religion)
- B.deity_name = GLOB.deity
- B.name = GLOB.bible_name
- B.icon_state = GLOB.bible_icon_state
- B.item_state = GLOB.bible_item_state
- to_chat(H, "There is already an established religion onboard the station. You are an acolyte of [GLOB.deity]. Defer to the Chaplain.")
- H.equip_to_slot_or_del(B, SLOT_IN_BACKPACK)
- var/nrt = GLOB.holy_weapon_type || /obj/item/nullrod
- var/obj/item/nullrod/N = new nrt(H)
- H.put_in_hands(N)
- return
-
- var/new_religion = "Christianity"
- if(M.client && M.client.prefs.custom_names["religion"])
- new_religion = M.client.prefs.custom_names["religion"]
-
- var/new_deity = "Space Jesus"
- if(M.client && M.client.prefs.custom_names["deity"])
- new_deity = M.client.prefs.custom_names["deity"]
-
- B.deity_name = new_deity
-
-
- switch(lowertext(new_religion))
- if("christianity")
- B.name = pick("The Holy Bible","The Dead Sea Scrolls")
- if("satanism")
- B.name = "The Unholy Bible"
- if("cthulhu")
- B.name = "The Necronomicon"
- if("islam")
- B.name = "Quran"
- if("scientology")
- B.name = pick("The Biography of L. Ron Hubbard","Dianetics")
- if("chaos")
- B.name = "The Book of Lorgar"
- if("imperium")
- B.name = "Uplifting Primer"
- if("toolboxia")
- B.name = "Toolbox Manifesto"
- if("homosexuality")
- B.name = "Guys Gone Wild"
- if("lol", "wtf", "gay", "penis", "ass", "poo", "badmin", "shitmin", "deadmin", "cock", "cocks", "meme", "memes")
- B.name = pick("Woodys Got Wood: The Aftermath", "War of the Cocks", "Sweet Bro and Hella Jef: Expanded Edition")
- H.adjustBrainLoss(100) // starts off retarded as fuck
- if("science")
- B.name = pick("Principle of Relativity", "Quantum Enigma: Physics Encounters Consciousness", "Programming the Universe", "Quantum Physics and Theology", "String Theory for Dummies", "How To: Build Your Own Warp Drive", "The Mysteries of Bluespace", "Playing God: Collector's Edition")
- else
- B.name = "The Holy Book of [new_religion]"
-
- GLOB.religion = new_religion
- GLOB.bible_name = B.name
- GLOB.deity = B.deity_name
-
- H.equip_to_slot_or_del(B, SLOT_IN_BACKPACK)
-
- SSblackbox.record_feedback("text", "religion_name", 1, "[new_religion]", 1)
- SSblackbox.record_feedback("text", "religion_deity", 1, "[new_deity]", 1)
-
-/datum/outfit/job/chaplain
- name = "Chaplain"
- jobtype = /datum/job/chaplain
-
- belt = /obj/item/pda/chaplain
- uniform = /obj/item/clothing/under/rank/chaplain
- backpack_contents = list(/obj/item/camera/spooky = 1)
- backpack = /obj/item/storage/backpack/cultpack
- satchel = /obj/item/storage/backpack/cultpack
+/datum/job/chaplain
+ title = "Chaplain"
+ flag = CHAPLAIN
+ department_head = list("Head of Personnel")
+ department_flag = CIVILIAN
+ faction = "Station"
+ total_positions = 1
+ spawn_positions = 1
+ supervisors = "the head of personnel"
+ selection_color = "#dddddd"
+
+ outfit = /datum/outfit/job/chaplain
+
+ access = list(ACCESS_MORGUE, ACCESS_CHAPEL_OFFICE, ACCESS_CREMATORIUM, ACCESS_THEATRE)
+ minimal_access = list(ACCESS_MORGUE, ACCESS_CHAPEL_OFFICE, ACCESS_CREMATORIUM, ACCESS_THEATRE)
+
+ display_order = JOB_DISPLAY_ORDER_CHAPLAIN
+
+
+/datum/job/chaplain/after_spawn(mob/living/H, mob/M)
+ . = ..()
+ if(H.mind)
+ H.mind.isholy = TRUE
+
+ var/obj/item/storage/book/bible/booze/B = new
+
+ if(GLOB.religion)
+ B.deity_name = GLOB.deity
+ B.name = GLOB.bible_name
+ B.icon_state = GLOB.bible_icon_state
+ B.item_state = GLOB.bible_item_state
+ to_chat(H, "There is already an established religion onboard the station. You are an acolyte of [GLOB.deity]. Defer to the Chaplain.")
+ H.equip_to_slot_or_del(B, SLOT_IN_BACKPACK)
+ var/nrt = GLOB.holy_weapon_type || /obj/item/nullrod
+ var/obj/item/nullrod/N = new nrt(H)
+ H.put_in_hands(N)
+ return
+
+ var/new_religion = DEFAULT_RELIGION
+ if(M.client && M.client.prefs.custom_names["religion"])
+ new_religion = M.client.prefs.custom_names["religion"]
+
+ var/new_deity = DEFAULT_DEITY
+ if(M.client && M.client.prefs.custom_names["deity"])
+ new_deity = M.client.prefs.custom_names["deity"]
+
+ B.deity_name = new_deity
+
+
+ switch(lowertext(new_religion))
+ if("christianity") // DEFAULT_RELIGION
+ B.name = pick("The Holy Bible","The Dead Sea Scrolls")
+ if("buddhism")
+ B.name = "The Sutras"
+ if("clownism","honkmother","honk","honkism","comedy")
+ B.name = pick("The Holy Joke Book", "Just a Prank", "Hymns to the Honkmother")
+ if("chaos")
+ B.name = "The Book of Lorgar"
+ if("cthulhu")
+ B.name = "The Necronomicon"
+ if("hinduism")
+ B.name = "The Vedas"
+ if("homosexuality")
+ B.name = pick("Guys Gone Wild","Coming Out of The Closet")
+ if("imperium")
+ B.name = "Uplifting Primer"
+ if("islam")
+ B.name = "Quran"
+ if("judaism")
+ B.name = "The Torah"
+ if("lampism")
+ B.name = "Fluorescent Incandescence"
+ if("lol", "wtf", "gay", "penis", "ass", "poo", "badmin", "shitmin", "deadmin", "cock", "cocks", "meme", "memes")
+ B.name = pick("Woodys Got Wood: The Aftermath", "War of the Cocks", "Sweet Bro and Hella Jef: Expanded Edition","F.A.T.A.L. Rulebook")
+ H.adjustOrganLoss(ORGAN_SLOT_BRAIN, 100) // starts off retarded as fuck
+ if("monkeyism","apism","gorillism","primatism")
+ B.name = pick("Going Bananas", "Bananas Out For Harambe")
+ if("mormonism")
+ B.name = "The Book of Mormon"
+ if("pastafarianism")
+ B.name = "The Gospel of the Flying Spaghetti Monster"
+ if("rastafarianism","rasta")
+ B.name = "The Holy Piby"
+ if("satanism")
+ B.name = "The Unholy Bible"
+ if("science")
+ B.name = pick("Principle of Relativity", "Quantum Enigma: Physics Encounters Consciousness", "Programming the Universe", "Quantum Physics and Theology", "String Theory for Dummies", "How To: Build Your Own Warp Drive", "The Mysteries of Bluespace", "Playing God: Collector's Edition")
+ if("scientology")
+ B.name = pick("The Biography of L. Ron Hubbard","Dianetics")
+ if("servicianism", "partying")
+ B.name = "The Tenets of Servicia"
+ B.deity_name = pick("Servicia", "Space Bacchus", "Space Dionysus")
+ B.desc = "Happy, Full, Clean. Live it and give it."
+ if("subgenius")
+ B.name = "Book of the SubGenius"
+ if("toolboxia","greytide")
+ B.name = pick("Toolbox Manifesto","iGlove Assistants")
+ if("weeaboo","kawaii")
+ B.name = pick("Fanfiction Compendium","Japanese for Dummies","The Manganomicon","Establishing Your O.T.P")
+ else
+ B.name = "The Holy Book of [new_religion]"
+
+ GLOB.religion = new_religion
+ GLOB.bible_name = B.name
+ GLOB.deity = B.deity_name
+
+ H.equip_to_slot_or_del(B, SLOT_IN_BACKPACK)
+
+ SSblackbox.record_feedback("text", "religion_name", 1, "[new_religion]", 1)
+ SSblackbox.record_feedback("text", "religion_deity", 1, "[new_deity]", 1)
+
+/datum/outfit/job/chaplain
+ name = "Chaplain"
+ jobtype = /datum/job/chaplain
+
+ belt = /obj/item/pda/chaplain
+ ears = /obj/item/radio/headset/headset_srv
+ uniform = /obj/item/clothing/under/rank/chaplain
+ backpack_contents = list(/obj/item/camera/spooky = 1)
+ backpack = /obj/item/storage/backpack/cultpack
+ satchel = /obj/item/storage/backpack/cultpack
diff --git a/code/modules/jobs/job_types/chemist.dm b/code/modules/jobs/job_types/chemist.dm
new file mode 100644
index 0000000000..a915d261ed
--- /dev/null
+++ b/code/modules/jobs/job_types/chemist.dm
@@ -0,0 +1,36 @@
+/datum/job/chemist
+ title = "Chemist"
+ flag = CHEMIST
+ department_head = list("Chief Medical Officer")
+ department_flag = MEDSCI
+ faction = "Station"
+ total_positions = 2
+ spawn_positions = 2
+ supervisors = "the chief medical officer"
+ selection_color = "#74b5e0"
+ exp_type = EXP_TYPE_CREW
+ exp_requirements = 60
+
+ outfit = /datum/outfit/job/chemist
+
+ access = list(ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_SURGERY, ACCESS_CHEMISTRY, ACCESS_VIROLOGY, ACCESS_GENETICS, ACCESS_CLONING, ACCESS_MINERAL_STOREROOM)
+ minimal_access = list(ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_CHEMISTRY, ACCESS_MINERAL_STOREROOM)
+
+ display_order = JOB_DISPLAY_ORDER_CHEMIST
+
+/datum/outfit/job/chemist
+ name = "Chemist"
+ jobtype = /datum/job/chemist
+
+ glasses = /obj/item/clothing/glasses/science
+ belt = /obj/item/pda/chemist
+ ears = /obj/item/radio/headset/headset_med
+ uniform = /obj/item/clothing/under/rank/chemist
+ shoes = /obj/item/clothing/shoes/sneakers/white
+ suit = /obj/item/clothing/suit/toggle/labcoat/chemist
+ backpack = /obj/item/storage/backpack/chemistry
+ satchel = /obj/item/storage/backpack/satchel/chem
+ duffelbag = /obj/item/storage/backpack/duffelbag/med
+
+ chameleon_extras = /obj/item/gun/syringe
+
diff --git a/code/modules/jobs/job_types/chief_engineer.dm b/code/modules/jobs/job_types/chief_engineer.dm
new file mode 100644
index 0000000000..da3f281267
--- /dev/null
+++ b/code/modules/jobs/job_types/chief_engineer.dm
@@ -0,0 +1,64 @@
+/datum/job/chief_engineer
+ title = "Chief Engineer"
+ flag = CHIEF
+// auto_deadmin_role_flags = DEADMIN_POSITION_HEAD
+ department_head = list("Captain")
+ department_flag = ENGSEC
+ head_announce = list(RADIO_CHANNEL_ENGINEERING)
+ faction = "Station"
+ total_positions = 1
+ spawn_positions = 1
+ supervisors = "the captain"
+ selection_color = "#ee7400"
+ req_admin_notify = 1
+ minimal_player_age = 7
+ exp_requirements = 180
+ exp_type = EXP_TYPE_CREW
+ exp_type_department = EXP_TYPE_ENGINEERING
+
+ outfit = /datum/outfit/job/ce
+
+ access = list(ACCESS_ENGINE, ACCESS_ENGINE_EQUIP, ACCESS_TECH_STORAGE, ACCESS_MAINT_TUNNELS,
+ ACCESS_EXTERNAL_AIRLOCKS, ACCESS_ATMOSPHERICS, ACCESS_EVA,
+ ACCESS_HEADS, ACCESS_CONSTRUCTION, ACCESS_SEC_DOORS, ACCESS_MINISAT,
+ ACCESS_CE, ACCESS_RC_ANNOUNCE, ACCESS_KEYCARD_AUTH, ACCESS_TCOMSAT, ACCESS_MINERAL_STOREROOM)
+ minimal_access = list(ACCESS_ENGINE, ACCESS_ENGINE_EQUIP, ACCESS_TECH_STORAGE, ACCESS_MAINT_TUNNELS,
+ ACCESS_EXTERNAL_AIRLOCKS, ACCESS_ATMOSPHERICS, ACCESS_EVA,
+ ACCESS_HEADS, ACCESS_CONSTRUCTION, ACCESS_SEC_DOORS, ACCESS_MINISAT,
+ ACCESS_CE, ACCESS_RC_ANNOUNCE, ACCESS_KEYCARD_AUTH, ACCESS_TCOMSAT, ACCESS_MINERAL_STOREROOM)
+
+ display_order = JOB_DISPLAY_ORDER_CHIEF_ENGINEER
+ blacklisted_quirks = list(/datum/quirk/mute, /datum/quirk/brainproblems, /datum/quirk/paraplegic, /datum/quirk/insanity)
+
+/datum/outfit/job/ce
+ name = "Chief Engineer"
+ jobtype = /datum/job/chief_engineer
+
+ id = /obj/item/card/id/silver
+ belt = /obj/item/storage/belt/utility/chief/full
+ l_pocket = /obj/item/pda/heads/ce
+ ears = /obj/item/radio/headset/heads/ce
+ uniform = /obj/item/clothing/under/rank/chief_engineer
+ shoes = /obj/item/clothing/shoes/sneakers/brown
+ head = /obj/item/clothing/head/hardhat/white
+ gloves = /obj/item/clothing/gloves/color/black/ce
+ backpack_contents = list(/obj/item/melee/classic_baton/telescopic=1, /obj/item/modular_computer/tablet/preset/advanced=1)
+
+ backpack = /obj/item/storage/backpack/industrial
+ satchel = /obj/item/storage/backpack/satchel/eng
+ duffelbag = /obj/item/storage/backpack/duffelbag/engineering
+ box = /obj/item/storage/box/engineer
+ pda_slot = SLOT_L_STORE
+ chameleon_extras = /obj/item/stamp/ce
+
+/datum/outfit/job/ce/rig
+ name = "Chief Engineer (Hardsuit)"
+
+ mask = /obj/item/clothing/mask/breath
+ suit = /obj/item/clothing/suit/space/hardsuit/engine/elite
+ shoes = /obj/item/clothing/shoes/magboots/advance
+ suit_store = /obj/item/tank/internals/oxygen
+ glasses = /obj/item/clothing/glasses/meson/engine
+ gloves = /obj/item/clothing/gloves/color/yellow
+ head = null
+ internals_slot = SLOT_S_STORE
diff --git a/code/modules/jobs/job_types/chief_medical_officer.dm b/code/modules/jobs/job_types/chief_medical_officer.dm
new file mode 100644
index 0000000000..4c7249f048
--- /dev/null
+++ b/code/modules/jobs/job_types/chief_medical_officer.dm
@@ -0,0 +1,59 @@
+/datum/job/cmo
+ title = "Chief Medical Officer"
+ flag = CMO_JF
+ department_head = list("Captain")
+ department_flag = MEDSCI
+// auto_deadmin_role_flags = DEADMIN_POSITION_HEAD
+ head_announce = list(RADIO_CHANNEL_MEDICAL)
+ faction = "Station"
+ total_positions = 1
+ spawn_positions = 1
+ supervisors = "the captain"
+ selection_color = "#509ed1"
+ req_admin_notify = 1
+ minimal_player_age = 7
+ exp_requirements = 180
+ exp_type = EXP_TYPE_CREW
+ exp_type_department = EXP_TYPE_MEDICAL
+
+ outfit = /datum/outfit/job/cmo
+
+ access = list(ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_GENETICS, ACCESS_CLONING, ACCESS_HEADS, ACCESS_MINERAL_STOREROOM,
+ ACCESS_CHEMISTRY, ACCESS_VIROLOGY, ACCESS_CMO, ACCESS_SURGERY, ACCESS_RC_ANNOUNCE,
+ ACCESS_KEYCARD_AUTH, ACCESS_SEC_DOORS, ACCESS_MAINT_TUNNELS)
+ minimal_access = list(ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_GENETICS, ACCESS_CLONING, ACCESS_HEADS, ACCESS_MINERAL_STOREROOM,
+ ACCESS_CHEMISTRY, ACCESS_VIROLOGY, ACCESS_CMO, ACCESS_SURGERY, ACCESS_RC_ANNOUNCE,
+ ACCESS_KEYCARD_AUTH, ACCESS_SEC_DOORS, ACCESS_MAINT_TUNNELS)
+
+ display_order = JOB_DISPLAY_ORDER_CHIEF_MEDICAL_OFFICER
+ blacklisted_quirks = list(/datum/quirk/mute, /datum/quirk/brainproblems, /datum/quirk/insanity)
+
+/datum/outfit/job/cmo
+ name = "Chief Medical Officer"
+ jobtype = /datum/job/cmo
+
+ id = /obj/item/card/id/silver
+ belt = /obj/item/pda/heads/cmo
+ l_pocket = /obj/item/pinpointer/crew
+ ears = /obj/item/radio/headset/heads/cmo
+ uniform = /obj/item/clothing/under/rank/chief_medical_officer
+ shoes = /obj/item/clothing/shoes/sneakers/brown
+ suit = /obj/item/clothing/suit/toggle/labcoat/cmo
+ l_hand = /obj/item/storage/firstaid/regular
+ suit_store = /obj/item/flashlight/pen
+ backpack_contents = list(/obj/item/melee/classic_baton/telescopic=1)
+
+ backpack = /obj/item/storage/backpack/medic
+ satchel = /obj/item/storage/backpack/satchel/med
+ duffelbag = /obj/item/storage/backpack/duffelbag/med
+
+ chameleon_extras = list(/obj/item/gun/syringe, /obj/item/stamp/cmo)
+
+/datum/outfit/job/cmo/hardsuit
+ name = "Chief Medical Officer (Hardsuit)"
+
+ mask = /obj/item/clothing/mask/breath
+ suit = /obj/item/clothing/suit/space/hardsuit/medical
+ suit_store = /obj/item/tank/internals/oxygen
+ r_pocket = /obj/item/flashlight/pen
+
diff --git a/code/modules/jobs/job_types/civilian.dm b/code/modules/jobs/job_types/civilian.dm
deleted file mode 100644
index 46e3d66f55..0000000000
--- a/code/modules/jobs/job_types/civilian.dm
+++ /dev/null
@@ -1,205 +0,0 @@
-/*
-Clown
-*/
-/datum/job/clown
- title = "Clown"
- flag = CLOWN
- department_head = list("Head of Personnel")
- department_flag = CIVILIAN
- faction = "Station"
- total_positions = 1
- spawn_positions = 1
- supervisors = "the head of personnel"
- selection_color = "#dddddd"
-
- outfit = /datum/outfit/job/clown
-
- access = list(ACCESS_THEATRE)
- minimal_access = list(ACCESS_THEATRE)
-
-/datum/job/clown/after_spawn(mob/living/carbon/human/H, mob/M)
- . = ..()
- H.apply_pref_name("clown", M.client)
-
-/datum/outfit/job/clown
- name = "Clown"
- jobtype = /datum/job/clown
-
- belt = /obj/item/pda/clown
- uniform = /obj/item/clothing/under/rank/clown
- shoes = /obj/item/clothing/shoes/clown_shoes
- mask = /obj/item/clothing/mask/gas/clown_hat
- l_pocket = /obj/item/bikehorn
- backpack_contents = list(
- /obj/item/stamp/clown = 1,
- /obj/item/reagent_containers/spray/waterflower = 1,
- /obj/item/reagent_containers/food/snacks/grown/banana = 1,
- /obj/item/instrument/bikehorn = 1,
- )
-
- implants = list(/obj/item/implant/sad_trombone)
-
- backpack = /obj/item/storage/backpack/clown
- satchel = /obj/item/storage/backpack/clown
- duffelbag = /obj/item/storage/backpack/duffelbag/clown //strangely has a duffel
-
- box = /obj/item/storage/box/hug/survival
-
- chameleon_extras = /obj/item/stamp/clown
-
-
-/datum/outfit/job/clown/pre_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
- ..()
- if(visualsOnly)
- return
-
- H.fully_replace_character_name(H.real_name, pick(GLOB.clown_names))
-
-/datum/outfit/job/clown/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
- ..()
- if(visualsOnly)
- return
-
- H.dna.add_mutation(CLOWNMUT)
-
-/*
-Mime
-*/
-/datum/job/mime
- title = "Mime"
- flag = MIME
- department_head = list("Head of Personnel")
- department_flag = CIVILIAN
- faction = "Station"
- total_positions = 1
- spawn_positions = 1
- supervisors = "the head of personnel"
- selection_color = "#dddddd"
-
- outfit = /datum/outfit/job/mime
-
- access = list(ACCESS_THEATRE)
- minimal_access = list(ACCESS_THEATRE)
-
-/datum/job/mime/after_spawn(mob/living/carbon/human/H, mob/M)
- H.apply_pref_name("mime", M.client)
-
-/datum/outfit/job/mime
- name = "Mime"
- jobtype = /datum/job/mime
-
- belt = /obj/item/pda/mime
- uniform = /obj/item/clothing/under/rank/mime
- mask = /obj/item/clothing/mask/gas/mime
- gloves = /obj/item/clothing/gloves/color/white
- head = /obj/item/clothing/head/frenchberet
- suit = /obj/item/clothing/suit/suspenders
- backpack_contents = list(/obj/item/reagent_containers/food/drinks/bottle/bottleofnothing=1)
-
- accessory = /obj/item/clothing/accessory/pocketprotector/cosmetology
- backpack = /obj/item/storage/backpack/mime
- satchel = /obj/item/storage/backpack/mime
-
-
-/datum/outfit/job/mime/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
- ..()
-
- if(visualsOnly)
- return
-
- if(H.mind)
- H.mind.AddSpell(new /obj/effect/proc_holder/spell/aoe_turf/conjure/mime_wall(null))
- H.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/mime/speak(null))
- H.mind.miming = 1
-
-/*
-Curator
-*/
-/datum/job/curator
- title = "Curator"
- flag = CURATOR
- department_head = list("Head of Personnel")
- department_flag = CIVILIAN
- faction = "Station"
- total_positions = 1
- spawn_positions = 1
- supervisors = "the head of personnel"
- selection_color = "#dddddd"
-
- outfit = /datum/outfit/job/curator
-
- access = list(ACCESS_LIBRARY)
- minimal_access = list(ACCESS_LIBRARY, ACCESS_CONSTRUCTION,ACCESS_MINING_STATION)
-
-/datum/outfit/job/curator
- name = "Curator"
- jobtype = /datum/job/curator
-
- belt = /obj/item/pda/curator
- uniform = /obj/item/clothing/under/rank/curator
- l_hand = /obj/item/storage/bag/books
- r_pocket = /obj/item/key/displaycase
- l_pocket = /obj/item/laser_pointer
- accessory = /obj/item/clothing/accessory/pocketprotector/full
- backpack_contents = list(
- /obj/item/melee/curator_whip = 1,
- /obj/item/soapstone = 1,
- /obj/item/barcodescanner = 1
- )
-
-
-/datum/outfit/job/curator/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
- ..()
-
- if(visualsOnly)
- return
-
- H.grant_all_languages(omnitongue=TRUE)
-/*
-Lawyer
-*/
-/datum/job/lawyer
- title = "Lawyer"
- flag = LAWYER
- department_head = list("Head of Personnel")
- department_flag = CIVILIAN
- faction = "Station"
- total_positions = 2
- spawn_positions = 2
- supervisors = "the head of personnel"
- selection_color = "#dddddd"
- var/lawyers = 0 //Counts lawyer amount
-
- outfit = /datum/outfit/job/lawyer
-
- access = list(ACCESS_LAWYER, ACCESS_COURT, ACCESS_SEC_DOORS)
- minimal_access = list(ACCESS_LAWYER, ACCESS_COURT, ACCESS_SEC_DOORS)
-
- mind_traits = list(TRAIT_LAW_ENFORCEMENT_METABOLISM)
-
-/datum/outfit/job/lawyer
- name = "Lawyer"
- jobtype = /datum/job/lawyer
-
- belt = /obj/item/pda/lawyer
- ears = /obj/item/radio/headset/headset_sec
- uniform = /obj/item/clothing/under/lawyer/bluesuit
- suit = /obj/item/clothing/suit/toggle/lawyer
- shoes = /obj/item/clothing/shoes/laceup
- l_hand = /obj/item/storage/briefcase/lawyer
- l_pocket = /obj/item/laser_pointer
- r_pocket = /obj/item/clothing/accessory/lawyers_badge
-
- chameleon_extras = /obj/item/stamp/law
-
-
-/datum/outfit/job/lawyer/pre_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
- ..()
- if(visualsOnly)
- return
-
- var/datum/job/lawyer/J = SSjob.GetJobType(jobtype)
- J.lawyers++
- if(J.lawyers>1)
- uniform = /obj/item/clothing/under/lawyer/purpsuit
- suit = /obj/item/clothing/suit/toggle/lawyer/purple
diff --git a/code/modules/jobs/job_types/clown.dm b/code/modules/jobs/job_types/clown.dm
new file mode 100644
index 0000000000..d8b88ae871
--- /dev/null
+++ b/code/modules/jobs/job_types/clown.dm
@@ -0,0 +1,58 @@
+/datum/job/clown
+ title = "Clown"
+ flag = CLOWN
+ department_head = list("Head of Personnel")
+ department_flag = CIVILIAN
+ faction = "Station"
+ total_positions = 1
+ spawn_positions = 1
+ supervisors = "the head of personnel"
+ selection_color = "#dddddd"
+
+ outfit = /datum/outfit/job/clown
+
+ access = list(ACCESS_THEATRE)
+ minimal_access = list(ACCESS_THEATRE)
+
+ display_order = JOB_DISPLAY_ORDER_CLOWN
+
+
+/datum/job/clown/after_spawn(mob/living/carbon/human/H, mob/M)
+ . = ..()
+ H.apply_pref_name("clown", M.client)
+
+/datum/outfit/job/clown
+ name = "Clown"
+ jobtype = /datum/job/clown
+
+ belt = /obj/item/pda/clown
+ ears = /obj/item/radio/headset/headset_srv
+ uniform = /obj/item/clothing/under/rank/clown
+ shoes = /obj/item/clothing/shoes/clown_shoes
+ mask = /obj/item/clothing/mask/gas/clown_hat
+ l_pocket = /obj/item/bikehorn
+ backpack_contents = list(
+ /obj/item/stamp/clown = 1,
+ /obj/item/reagent_containers/spray/waterflower = 1,
+ /obj/item/reagent_containers/food/snacks/grown/banana = 1,
+ /obj/item/instrument/bikehorn = 1,
+ )
+
+ implants = list(/obj/item/implant/sad_trombone)
+
+ backpack = /obj/item/storage/backpack/clown
+ satchel = /obj/item/storage/backpack/clown
+ duffelbag = /obj/item/storage/backpack/duffelbag/clown //strangely has a duffel
+
+ box = /obj/item/storage/box/hug/survival
+
+ chameleon_extras = /obj/item/stamp/clown
+
+/datum/outfit/job/clown/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
+ ..()
+ if(visualsOnly)
+ return
+
+ H.fully_replace_character_name(H.real_name, pick(GLOB.clown_names)) //rename the mob AFTER they're equipped so their ID gets updated properly.
+ H.dna.add_mutation(CLOWNMUT)
+ H.dna.add_mutation(SMILE)
diff --git a/code/modules/jobs/job_types/cook.dm b/code/modules/jobs/job_types/cook.dm
new file mode 100644
index 0000000000..c213d4dffc
--- /dev/null
+++ b/code/modules/jobs/job_types/cook.dm
@@ -0,0 +1,52 @@
+/datum/job/cook
+ title = "Cook"
+ flag = COOK
+ department_head = list("Head of Personnel")
+ department_flag = CIVILIAN
+ faction = "Station"
+ total_positions = 2
+ spawn_positions = 1
+ supervisors = "the head of personnel"
+ selection_color = "#bbe291"
+ var/cooks = 0 //Counts cooks amount
+
+ outfit = /datum/outfit/job/cook
+
+ access = list(ACCESS_HYDROPONICS, ACCESS_BAR, ACCESS_KITCHEN, ACCESS_MORGUE, ACCESS_MINERAL_STOREROOM)
+ minimal_access = list(ACCESS_KITCHEN, ACCESS_MORGUE, ACCESS_MINERAL_STOREROOM)
+
+ display_order = JOB_DISPLAY_ORDER_COOK
+
+/datum/outfit/job/cook
+ name = "Cook"
+ jobtype = /datum/job/cook
+
+ belt = /obj/item/pda/cook
+ ears = /obj/item/radio/headset/headset_srv
+ uniform = /obj/item/clothing/under/rank/chef
+ suit = /obj/item/clothing/suit/toggle/chef
+ head = /obj/item/clothing/head/chefhat
+ mask = /obj/item/clothing/mask/fakemoustache/italian
+ backpack_contents = list(/obj/item/sharpener = 1)
+
+/datum/outfit/job/cook/pre_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
+ ..()
+ var/datum/job/cook/J = SSjob.GetJobType(jobtype)
+ if(J) // Fix for runtime caused by invalid job being passed
+ if(J.cooks>0)//Cooks
+ suit = /obj/item/clothing/suit/apron/chef
+ head = /obj/item/clothing/head/soft/mime
+ if(!visualsOnly)
+ J.cooks++
+
+/datum/outfit/job/cook/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
+ ..()
+ if(visualsOnly)
+ return
+ var/list/possible_boxes = subtypesof(/obj/item/storage/box/ingredients)
+ var/chosen_box = pick(possible_boxes)
+ var/obj/item/storage/box/I = new chosen_box(src)
+ H.equip_to_slot_or_del(I,SLOT_IN_BACKPACK)
+ var/datum/martial_art/cqc/under_siege/justacook = new
+ justacook.teach(H)
+
diff --git a/code/modules/jobs/job_types/curator.dm b/code/modules/jobs/job_types/curator.dm
new file mode 100644
index 0000000000..35fa8483d5
--- /dev/null
+++ b/code/modules/jobs/job_types/curator.dm
@@ -0,0 +1,43 @@
+/datum/job/curator
+ title = "Curator"
+ flag = CURATOR
+ department_head = list("Head of Personnel")
+ department_flag = CIVILIAN
+ faction = "Station"
+ total_positions = 1
+ spawn_positions = 1
+ supervisors = "the head of personnel"
+ selection_color = "#dddddd"
+
+ outfit = /datum/outfit/job/curator
+
+ access = list(ACCESS_LIBRARY)
+ minimal_access = list(ACCESS_LIBRARY, ACCESS_CONSTRUCTION, ACCESS_MINING_STATION)
+
+ display_order = JOB_DISPLAY_ORDER_CURATOR
+
+/datum/outfit/job/curator
+ name = "Curator"
+ jobtype = /datum/job/curator
+
+ shoes = /obj/item/clothing/shoes/laceup
+ belt = /obj/item/pda/curator
+ ears = /obj/item/radio/headset/headset_srv
+ uniform = /obj/item/clothing/under/rank/curator
+ l_hand = /obj/item/storage/bag/books
+ r_pocket = /obj/item/key/displaycase
+ l_pocket = /obj/item/laser_pointer
+ accessory = /obj/item/clothing/accessory/pocketprotector/full
+ backpack_contents = list(
+ /obj/item/melee/curator_whip = 1,
+ /obj/item/soapstone = 1,
+ /obj/item/barcodescanner = 1
+ )
+
+/datum/outfit/job/curator/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
+ ..()
+
+ if(visualsOnly)
+ return
+
+ H.grant_all_languages(omnitongue=TRUE)
diff --git a/code/modules/jobs/job_types/cyborg.dm b/code/modules/jobs/job_types/cyborg.dm
new file mode 100644
index 0000000000..29c4c3d833
--- /dev/null
+++ b/code/modules/jobs/job_types/cyborg.dm
@@ -0,0 +1,27 @@
+/datum/job/cyborg
+ title = "Cyborg"
+ flag = CYBORG
+// auto_deadmin_role_flags = DEADMIN_POSITION_SILICON
+ department_flag = ENGSEC
+ faction = "Station"
+ total_positions = 0
+ spawn_positions = 1
+ supervisors = "your laws and the AI" //Nodrak
+ selection_color = "#ddffdd"
+ minimal_player_age = 21
+ exp_requirements = 120
+ exp_type = EXP_TYPE_CREW
+
+ display_order = JOB_DISPLAY_ORDER_CYBORG
+
+/datum/job/cyborg/equip(mob/living/carbon/human/H, visualsOnly = FALSE, announce = TRUE, latejoin = FALSE, datum/outfit/outfit_override = null, client/preference_source = null)
+ if(visualsOnly)
+ CRASH("dynamic preview is unsupported")
+ return H.Robotize(FALSE, latejoin)
+
+/datum/job/cyborg/after_spawn(mob/living/silicon/robot/R, mob/M)
+ R.updatename(M.client)
+ R.gender = NEUTER
+
+/datum/job/cyborg/radio_help_message(mob/M)
+ to_chat(M, "Prefix your message with :b to speak with other cyborgs and AI.")
diff --git a/code/modules/jobs/job_types/detective.dm b/code/modules/jobs/job_types/detective.dm
new file mode 100644
index 0000000000..27a54fbd1f
--- /dev/null
+++ b/code/modules/jobs/job_types/detective.dm
@@ -0,0 +1,57 @@
+/datum/job/detective
+ title = "Detective"
+ flag = DETECTIVE
+// auto_deadmin_role_flags = DEADMIN_POSITION_SECURITY
+ department_head = list("Head of Security")
+ department_flag = ENGSEC
+ faction = "Station"
+ total_positions = 1
+ spawn_positions = 1
+ supervisors = "the head of security"
+ selection_color = "#c02f2f"
+ minimal_player_age = 7
+ exp_requirements = 300
+ exp_type = EXP_TYPE_CREW
+
+ outfit = /datum/outfit/job/detective
+
+ access = list(ACCESS_SEC_DOORS, ACCESS_FORENSICS_LOCKERS, ACCESS_MORGUE, ACCESS_MAINT_TUNNELS, ACCESS_COURT, ACCESS_BRIG, ACCESS_WEAPONS, ACCESS_MINERAL_STOREROOM)
+ minimal_access = list(ACCESS_SEC_DOORS, ACCESS_FORENSICS_LOCKERS, ACCESS_MORGUE, ACCESS_MAINT_TUNNELS, ACCESS_COURT, ACCESS_BRIG, ACCESS_WEAPONS, ACCESS_MINERAL_STOREROOM)
+
+ mind_traits = list(TRAIT_LAW_ENFORCEMENT_METABOLISM)
+
+ display_order = JOB_DISPLAY_ORDER_DETECTIVE
+ blacklisted_quirks = list(/datum/quirk/mute, /datum/quirk/nonviolent, /datum/quirk/paraplegic)
+
+/datum/outfit/job/detective
+ name = "Detective"
+ jobtype = /datum/job/detective
+
+ belt = /obj/item/pda/detective
+ ears = /obj/item/radio/headset/headset_sec/alt
+ uniform = /obj/item/clothing/under/rank/det
+ neck = /obj/item/clothing/neck/tie/black
+ shoes = /obj/item/clothing/shoes/sneakers/brown
+ suit = /obj/item/clothing/suit/det_suit
+ gloves = /obj/item/clothing/gloves/color/black
+ head = /obj/item/clothing/head/fedora/det_hat
+ l_pocket = /obj/item/toy/crayon/white
+ r_pocket = /obj/item/lighter
+ backpack_contents = list(/obj/item/storage/box/evidence=1,\
+ /obj/item/detective_scanner=1,\
+ /obj/item/melee/classic_baton=1)
+ mask = /obj/item/clothing/mask/cigarette
+
+ implants = list(/obj/item/implant/mindshield)
+
+ chameleon_extras = list(/obj/item/gun/ballistic/revolver/detective, /obj/item/clothing/glasses/sunglasses)
+
+/datum/outfit/job/detective/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
+ ..()
+ var/obj/item/clothing/mask/cigarette/cig = H.wear_mask
+ if(istype(cig)) //Some species specfic changes can mess this up (plasmamen)
+ cig.light("")
+
+ if(visualsOnly)
+ return
+
diff --git a/code/modules/jobs/job_types/engineering.dm b/code/modules/jobs/job_types/engineering.dm
deleted file mode 100644
index f28e5f1afc..0000000000
--- a/code/modules/jobs/job_types/engineering.dm
+++ /dev/null
@@ -1,167 +0,0 @@
-/*
-Chief Engineer
-*/
-/datum/job/chief_engineer
- title = "Chief Engineer"
- flag = CHIEF
- department_head = list("Captain")
- department_flag = ENGSEC
- head_announce = list("Engineering")
- faction = "Station"
- total_positions = 1
- spawn_positions = 1
- supervisors = "the captain"
- selection_color = "#ffeeaa"
- req_admin_notify = 1
- minimal_player_age = 7
- exp_requirements = 180
- exp_type = EXP_TYPE_CREW
- exp_type_department = EXP_TYPE_ENGINEERING
-
- outfit = /datum/outfit/job/ce
-
- access = list(ACCESS_ENGINE, ACCESS_ENGINE_EQUIP, ACCESS_TECH_STORAGE, ACCESS_MAINT_TUNNELS,
- ACCESS_EXTERNAL_AIRLOCKS, ACCESS_ATMOSPHERICS, ACCESS_EMERGENCY_STORAGE, ACCESS_EVA,
- ACCESS_HEADS, ACCESS_CONSTRUCTION, ACCESS_SEC_DOORS, ACCESS_MINISAT,
- ACCESS_CE, ACCESS_RC_ANNOUNCE, ACCESS_KEYCARD_AUTH, ACCESS_TCOMSAT, ACCESS_MINERAL_STOREROOM)
- minimal_access = list(ACCESS_ENGINE, ACCESS_ENGINE_EQUIP, ACCESS_TECH_STORAGE, ACCESS_MAINT_TUNNELS,
- ACCESS_EXTERNAL_AIRLOCKS, ACCESS_ATMOSPHERICS, ACCESS_EMERGENCY_STORAGE, ACCESS_EVA,
- ACCESS_HEADS, ACCESS_CONSTRUCTION, ACCESS_SEC_DOORS, ACCESS_MINISAT,
- ACCESS_CE, ACCESS_RC_ANNOUNCE, ACCESS_KEYCARD_AUTH, ACCESS_TCOMSAT, ACCESS_MINERAL_STOREROOM)
-
-/datum/outfit/job/ce
- name = "Chief Engineer"
- jobtype = /datum/job/chief_engineer
-
- id = /obj/item/card/id/silver
- belt = /obj/item/storage/belt/utility/chief/full
- l_pocket = /obj/item/pda/heads/ce
- ears = /obj/item/radio/headset/heads/ce
- uniform = /obj/item/clothing/under/rank/chief_engineer
- shoes = /obj/item/clothing/shoes/sneakers/brown
- head = /obj/item/clothing/head/hardhat/white
- gloves = /obj/item/clothing/gloves/color/black/ce
- backpack_contents = list(/obj/item/melee/classic_baton/telescopic=1, /obj/item/modular_computer/tablet/preset/advanced=1)
-
- backpack = /obj/item/storage/backpack/industrial
- satchel = /obj/item/storage/backpack/satchel/eng
- duffelbag = /obj/item/storage/backpack/duffelbag/engineering
- box = /obj/item/storage/box/engineer
- pda_slot = SLOT_L_STORE
- chameleon_extras = /obj/item/stamp/ce
-
-/datum/outfit/job/ce/rig
- name = "Chief Engineer (Hardsuit)"
-
- mask = /obj/item/clothing/mask/breath
- suit = /obj/item/clothing/suit/space/hardsuit/engine/elite
- shoes = /obj/item/clothing/shoes/magboots/advance
- suit_store = /obj/item/tank/internals/oxygen
- glasses = /obj/item/clothing/glasses/meson/engine
- gloves = /obj/item/clothing/gloves/color/yellow
- head = null
- internals_slot = SLOT_S_STORE
-
-
-/*
-Station Engineer
-*/
-/datum/job/engineer
- title = "Station Engineer"
- flag = ENGINEER
- department_head = list("Chief Engineer")
- department_flag = ENGSEC
- faction = "Station"
- total_positions = 5
- spawn_positions = 5
- supervisors = "the chief engineer"
- selection_color = "#fff5cc"
- exp_requirements = 60
- exp_type = EXP_TYPE_CREW
-
- outfit = /datum/outfit/job/engineer
-
- access = list(ACCESS_ENGINE, ACCESS_ENGINE_EQUIP, ACCESS_TECH_STORAGE, ACCESS_MAINT_TUNNELS,
- ACCESS_EXTERNAL_AIRLOCKS, ACCESS_CONSTRUCTION, ACCESS_ATMOSPHERICS, ACCESS_TCOMSAT, ACCESS_MINERAL_STOREROOM)
- minimal_access = list(ACCESS_ENGINE, ACCESS_ENGINE_EQUIP, ACCESS_TECH_STORAGE, ACCESS_MAINT_TUNNELS,
- ACCESS_EXTERNAL_AIRLOCKS, ACCESS_CONSTRUCTION, ACCESS_TCOMSAT, ACCESS_MINERAL_STOREROOM)
-
-/datum/outfit/job/engineer
- name = "Station Engineer"
- jobtype = /datum/job/engineer
-
- belt = /obj/item/storage/belt/utility/full/engi
- l_pocket = /obj/item/pda/engineering
- ears = /obj/item/radio/headset/headset_eng
- uniform = /obj/item/clothing/under/rank/engineer
- shoes = /obj/item/clothing/shoes/workboots
- head = /obj/item/clothing/head/hardhat
- r_pocket = /obj/item/t_scanner
-
- backpack = /obj/item/storage/backpack/industrial
- satchel = /obj/item/storage/backpack/satchel/eng
- duffelbag = /obj/item/storage/backpack/duffelbag/engineering
- box = /obj/item/storage/box/engineer
- pda_slot = SLOT_L_STORE
- backpack_contents = list(/obj/item/modular_computer/tablet/preset/advanced=1)
-
-/datum/outfit/job/engineer/gloved
- name = "Station Engineer (Gloves)"
- gloves = /obj/item/clothing/gloves/color/yellow
-
-/datum/outfit/job/engineer/gloved/rig
- name = "Station Engineer (Hardsuit)"
-
- mask = /obj/item/clothing/mask/breath
- suit = /obj/item/clothing/suit/space/hardsuit/engine
- suit_store = /obj/item/tank/internals/oxygen
- head = null
- internals_slot = SLOT_S_STORE
-
-
-/*
-Atmospheric Technician
-*/
-/datum/job/atmos
- title = "Atmospheric Technician"
- flag = ATMOSTECH
- department_head = list("Chief Engineer")
- department_flag = ENGSEC
- faction = "Station"
- total_positions = 3
- spawn_positions = 2
- supervisors = "the chief engineer"
- selection_color = "#fff5cc"
- exp_requirements = 60
- exp_type = EXP_TYPE_CREW
-
- outfit = /datum/outfit/job/atmos
-
- access = list(ACCESS_ENGINE, ACCESS_ENGINE_EQUIP, ACCESS_TECH_STORAGE, ACCESS_MAINT_TUNNELS,
- ACCESS_EXTERNAL_AIRLOCKS, ACCESS_CONSTRUCTION, ACCESS_ATMOSPHERICS, ACCESS_MINERAL_STOREROOM)
- minimal_access = list(ACCESS_ATMOSPHERICS, ACCESS_MAINT_TUNNELS, ACCESS_EMERGENCY_STORAGE, ACCESS_CONSTRUCTION, ACCESS_MINERAL_STOREROOM)
-
-/datum/outfit/job/atmos
- name = "Atmospheric Technician"
- jobtype = /datum/job/atmos
-
- belt = /obj/item/storage/belt/utility/atmostech
- l_pocket = /obj/item/pda/atmos
- ears = /obj/item/radio/headset/headset_eng
- uniform = /obj/item/clothing/under/rank/atmospheric_technician
- r_pocket = /obj/item/analyzer
-
- backpack = /obj/item/storage/backpack/industrial
- satchel = /obj/item/storage/backpack/satchel/eng
- duffelbag = /obj/item/storage/backpack/duffelbag/engineering
- box = /obj/item/storage/box/engineer
- pda_slot = SLOT_L_STORE
- backpack_contents = list(/obj/item/modular_computer/tablet/preset/advanced=1)
-
-/datum/outfit/job/atmos/rig
- name = "Atmospheric Technician (Hardsuit)"
-
- mask = /obj/item/clothing/mask/gas
- suit = /obj/item/clothing/suit/space/hardsuit/engine/atmos
- suit_store = /obj/item/tank/internals/oxygen
- internals_slot = SLOT_S_STORE
diff --git a/code/modules/jobs/job_types/geneticist.dm b/code/modules/jobs/job_types/geneticist.dm
new file mode 100644
index 0000000000..d7f59ff883
--- /dev/null
+++ b/code/modules/jobs/job_types/geneticist.dm
@@ -0,0 +1,35 @@
+/datum/job/geneticist
+ title = "Geneticist"
+ flag = GENETICIST
+ department_head = list("Chief Medical Officer", "Research Director")
+ department_flag = MEDSCI
+ faction = "Station"
+ total_positions = 2
+ spawn_positions = 2
+ supervisors = "the chief medical officer and research director"
+ selection_color = "#74b5e0"
+ exp_type = EXP_TYPE_CREW
+ exp_requirements = 60
+
+ outfit = /datum/outfit/job/geneticist
+
+ access = list(ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_CHEMISTRY, ACCESS_GENETICS, ACCESS_CLONING, ACCESS_RESEARCH, ACCESS_XENOBIOLOGY, ACCESS_ROBOTICS, ACCESS_MINERAL_STOREROOM, ACCESS_TECH_STORAGE)
+ minimal_access = list(ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_GENETICS, ACCESS_CLONING, ACCESS_RESEARCH, ACCESS_MINERAL_STOREROOM)
+
+ display_order = JOB_DISPLAY_ORDER_GENETICIST
+
+/datum/outfit/job/geneticist
+ name = "Geneticist"
+ jobtype = /datum/job/geneticist
+
+ belt = /obj/item/pda/geneticist
+ ears = /obj/item/radio/headset/headset_medsci
+ uniform = /obj/item/clothing/under/rank/geneticist
+ shoes = /obj/item/clothing/shoes/sneakers/white
+ suit = /obj/item/clothing/suit/toggle/labcoat/genetics
+ suit_store = /obj/item/flashlight/pen
+
+ backpack = /obj/item/storage/backpack/genetics
+ satchel = /obj/item/storage/backpack/satchel/gen
+ duffelbag = /obj/item/storage/backpack/duffelbag/med
+
diff --git a/code/modules/jobs/job_types/head_of_personnel.dm b/code/modules/jobs/job_types/head_of_personnel.dm
new file mode 100644
index 0000000000..e320ce20b4
--- /dev/null
+++ b/code/modules/jobs/job_types/head_of_personnel.dm
@@ -0,0 +1,51 @@
+/datum/job/hop
+ title = "Head of Personnel"
+ flag = HOP
+// auto_deadmin_role_flags = DEADMIN_POSITION_HEAD
+ department_head = list("Captain")
+ department_flag = CIVILIAN
+ head_announce = list(RADIO_CHANNEL_SERVICE)
+ faction = "Station"
+ total_positions = 1
+ spawn_positions = 1
+ supervisors = "the captain"
+ selection_color = "#3a8529"
+ req_admin_notify = 1
+ minimal_player_age = 10
+ exp_requirements = 180
+ exp_type = EXP_TYPE_CREW
+ exp_type_department = EXP_TYPE_SERVICE
+
+ outfit = /datum/outfit/job/hop
+
+ access = list(ACCESS_SECURITY, ACCESS_SEC_DOORS, ACCESS_COURT, ACCESS_WEAPONS,
+ ACCESS_MEDICAL, ACCESS_ENGINE, ACCESS_CHANGE_IDS, ACCESS_AI_UPLOAD, ACCESS_EVA, ACCESS_HEADS,
+ ACCESS_ALL_PERSONAL_LOCKERS, ACCESS_MAINT_TUNNELS, ACCESS_BAR, ACCESS_JANITOR, ACCESS_CONSTRUCTION, ACCESS_MORGUE,
+ ACCESS_CREMATORIUM, ACCESS_KITCHEN, ACCESS_CARGO, ACCESS_MAILSORTING, ACCESS_QM, ACCESS_HYDROPONICS, ACCESS_LAWYER,
+ ACCESS_THEATRE, ACCESS_CHAPEL_OFFICE, ACCESS_LIBRARY, ACCESS_RESEARCH, ACCESS_MINING, ACCESS_VAULT, ACCESS_MINING_STATION,
+ ACCESS_HOP, ACCESS_RC_ANNOUNCE, ACCESS_KEYCARD_AUTH, ACCESS_GATEWAY, ACCESS_MINERAL_STOREROOM)
+ minimal_access = list(ACCESS_SECURITY, ACCESS_SEC_DOORS, ACCESS_COURT, ACCESS_WEAPONS,
+ ACCESS_MEDICAL, ACCESS_ENGINE, ACCESS_CHANGE_IDS, ACCESS_AI_UPLOAD, ACCESS_EVA, ACCESS_HEADS,
+ ACCESS_ALL_PERSONAL_LOCKERS, ACCESS_MAINT_TUNNELS, ACCESS_BAR, ACCESS_JANITOR, ACCESS_CONSTRUCTION, ACCESS_MORGUE,
+ ACCESS_CREMATORIUM, ACCESS_KITCHEN, ACCESS_CARGO, ACCESS_MAILSORTING, ACCESS_QM, ACCESS_HYDROPONICS, ACCESS_LAWYER,
+ ACCESS_THEATRE, ACCESS_CHAPEL_OFFICE, ACCESS_LIBRARY, ACCESS_RESEARCH, ACCESS_MINING, ACCESS_VAULT, ACCESS_MINING_STATION,
+ ACCESS_HOP, ACCESS_RC_ANNOUNCE, ACCESS_KEYCARD_AUTH, ACCESS_GATEWAY, ACCESS_MINERAL_STOREROOM)
+
+ display_order = JOB_DISPLAY_ORDER_HEAD_OF_PERSONNEL
+
+ blacklisted_quirks = list(/datum/quirk/mute, /datum/quirk/brainproblems, /datum/quirk/prosopagnosia, /datum/quirk/insanity)
+
+/datum/outfit/job/hop
+ name = "Head of Personnel"
+ jobtype = /datum/job/hop
+
+ id = /obj/item/card/id/silver
+ belt = /obj/item/pda/heads/hop
+ ears = /obj/item/radio/headset/heads/hop
+ uniform = /obj/item/clothing/under/rank/head_of_personnel
+ shoes = /obj/item/clothing/shoes/sneakers/brown
+ head = /obj/item/clothing/head/hopcap
+ backpack_contents = list(/obj/item/storage/box/ids=1,\
+ /obj/item/melee/classic_baton/telescopic=1, /obj/item/modular_computer/tablet/preset/advanced = 1)
+
+ chameleon_extras = list(/obj/item/gun/energy/e_gun, /obj/item/stamp/hop)
diff --git a/code/modules/jobs/job_types/head_of_security.dm b/code/modules/jobs/job_types/head_of_security.dm
new file mode 100644
index 0000000000..f6b5dbd3ef
--- /dev/null
+++ b/code/modules/jobs/job_types/head_of_security.dm
@@ -0,0 +1,68 @@
+/datum/job/hos
+ title = "Head of Security"
+ flag = HOS
+// auto_deadmin_role_flags = DEADMIN_POSITION_HEAD|DEADMIN_POSITION_SECURITY
+ department_head = list("Captain")
+ department_flag = ENGSEC
+ head_announce = list(RADIO_CHANNEL_SECURITY)
+ faction = "Station"
+ total_positions = 1
+ spawn_positions = 1
+ supervisors = "the captain"
+ selection_color = "#b90000"
+ req_admin_notify = 1
+ minimal_player_age = 14
+ exp_requirements = 300
+ exp_type = EXP_TYPE_CREW
+ exp_type_department = EXP_TYPE_SECURITY
+
+ outfit = /datum/outfit/job/hos
+ mind_traits = list(TRAIT_LAW_ENFORCEMENT_METABOLISM)
+
+ access = list(ACCESS_SECURITY, ACCESS_SEC_DOORS, ACCESS_BRIG, ACCESS_ARMORY, ACCESS_COURT, ACCESS_WEAPONS,
+ ACCESS_FORENSICS_LOCKERS, ACCESS_MORGUE, ACCESS_MAINT_TUNNELS, ACCESS_ALL_PERSONAL_LOCKERS,
+ ACCESS_RESEARCH, ACCESS_ENGINE, ACCESS_MINING, ACCESS_MEDICAL, ACCESS_CONSTRUCTION, ACCESS_MAILSORTING,
+ ACCESS_HEADS, ACCESS_HOS, ACCESS_RC_ANNOUNCE, ACCESS_KEYCARD_AUTH, ACCESS_GATEWAY, ACCESS_MAINT_TUNNELS, ACCESS_MINERAL_STOREROOM)
+ minimal_access = list(ACCESS_SECURITY, ACCESS_SEC_DOORS, ACCESS_BRIG, ACCESS_ARMORY, ACCESS_COURT, ACCESS_WEAPONS,
+ ACCESS_FORENSICS_LOCKERS, ACCESS_MORGUE, ACCESS_MAINT_TUNNELS, ACCESS_ALL_PERSONAL_LOCKERS,
+ ACCESS_RESEARCH, ACCESS_ENGINE, ACCESS_MINING, ACCESS_MEDICAL, ACCESS_CONSTRUCTION, ACCESS_MAILSORTING,
+ ACCESS_HEADS, ACCESS_HOS, ACCESS_RC_ANNOUNCE, ACCESS_KEYCARD_AUTH, ACCESS_GATEWAY, ACCESS_MAINT_TUNNELS, ACCESS_MINERAL_STOREROOM)
+
+ display_order = JOB_DISPLAY_ORDER_HEAD_OF_SECURITY
+ blacklisted_quirks = list(/datum/quirk/mute, /datum/quirk/brainproblems, /datum/quirk/nonviolent, /datum/quirk/paraplegic, /datum/quirk/insanity)
+
+/datum/outfit/job/hos
+ name = "Head of Security"
+ jobtype = /datum/job/hos
+
+ id = /obj/item/card/id/silver
+ belt = /obj/item/pda/heads/hos
+ ears = /obj/item/radio/headset/heads/hos/alt
+ uniform = /obj/item/clothing/under/rank/head_of_security
+ shoes = /obj/item/clothing/shoes/jackboots
+ suit = /obj/item/clothing/suit/armor/hos/trenchcoat
+ gloves = /obj/item/clothing/gloves/color/black/hos
+ head = /obj/item/clothing/head/HoS/beret
+ glasses = /obj/item/clothing/glasses/hud/security/sunglasses
+ suit_store = /obj/item/gun/energy/e_gun
+ r_pocket = /obj/item/assembly/flash/handheld
+ l_pocket = /obj/item/restraints/handcuffs
+ backpack_contents = list(/obj/item/melee/classic_baton/telescopic=1)
+
+ backpack = /obj/item/storage/backpack/security
+ satchel = /obj/item/storage/backpack/satchel/sec
+ duffelbag = /obj/item/storage/backpack/duffelbag/sec
+ box = /obj/item/storage/box/security
+
+ implants = list(/obj/item/implant/mindshield)
+
+ chameleon_extras = list(/obj/item/gun/energy/e_gun/hos, /obj/item/stamp/hos)
+
+/datum/outfit/job/hos/hardsuit
+ name = "Head of Security (Hardsuit)"
+
+ mask = /obj/item/clothing/mask/gas/sechailer
+ suit = /obj/item/clothing/suit/space/hardsuit/security/hos
+ suit_store = /obj/item/tank/internals/oxygen
+ backpack_contents = list(/obj/item/melee/baton/loaded=1, /obj/item/gun/energy/e_gun=1)
+
diff --git a/code/modules/jobs/job_types/janitor.dm b/code/modules/jobs/job_types/janitor.dm
new file mode 100644
index 0000000000..d0a06ca0e0
--- /dev/null
+++ b/code/modules/jobs/job_types/janitor.dm
@@ -0,0 +1,27 @@
+/datum/job/janitor
+ title = "Janitor"
+ flag = JANITOR
+ department_head = list("Head of Personnel")
+ department_flag = CIVILIAN
+ faction = "Station"
+ total_positions = 2
+ spawn_positions = 1
+ supervisors = "the head of personnel"
+ selection_color = "#bbe291"
+ var/global/janitors = 0
+
+ outfit = /datum/outfit/job/janitor
+
+ access = list(ACCESS_JANITOR, ACCESS_MAINT_TUNNELS, ACCESS_MINERAL_STOREROOM)
+ minimal_access = list(ACCESS_JANITOR, ACCESS_MAINT_TUNNELS, ACCESS_MINERAL_STOREROOM)
+
+ display_order = JOB_DISPLAY_ORDER_JANITOR
+
+/datum/outfit/job/janitor
+ name = "Janitor"
+ jobtype = /datum/job/janitor
+
+ belt = /obj/item/pda/janitor
+ ears = /obj/item/radio/headset/headset_srv
+ uniform = /obj/item/clothing/under/rank/janitor
+ backpack_contents = list(/obj/item/modular_computer/tablet/preset/advanced=1)
diff --git a/code/modules/jobs/job_types/lawyer.dm b/code/modules/jobs/job_types/lawyer.dm
new file mode 100644
index 0000000000..0b8be52116
--- /dev/null
+++ b/code/modules/jobs/job_types/lawyer.dm
@@ -0,0 +1,47 @@
+/datum/job/lawyer
+ title = "Lawyer"
+ flag = LAWYER
+ department_head = list("Head of Personnel")
+ department_flag = CIVILIAN
+ faction = "Station"
+ total_positions = 2
+ spawn_positions = 2
+ supervisors = "the head of personnel"
+ selection_color = "#dddddd"
+ var/lawyers = 0 //Counts lawyer amount
+
+ outfit = /datum/outfit/job/lawyer
+
+ access = list(ACCESS_LAWYER, ACCESS_COURT, ACCESS_SEC_DOORS)
+ minimal_access = list(ACCESS_LAWYER, ACCESS_COURT, ACCESS_SEC_DOORS)
+
+ mind_traits = list(TRAIT_LAW_ENFORCEMENT_METABOLISM)
+
+ display_order = JOB_DISPLAY_ORDER_LAWYER
+
+/datum/outfit/job/lawyer
+ name = "Lawyer"
+ jobtype = /datum/job/lawyer
+
+ belt = /obj/item/pda/lawyer
+ ears = /obj/item/radio/headset/headset_sec
+ uniform = /obj/item/clothing/under/lawyer/bluesuit
+ suit = /obj/item/clothing/suit/toggle/lawyer
+ shoes = /obj/item/clothing/shoes/laceup
+ l_hand = /obj/item/storage/briefcase/lawyer
+ l_pocket = /obj/item/laser_pointer
+ r_pocket = /obj/item/clothing/accessory/lawyers_badge
+
+ chameleon_extras = /obj/item/stamp/law
+
+
+/datum/outfit/job/lawyer/pre_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
+ ..()
+ if(visualsOnly)
+ return
+
+ var/datum/job/lawyer/J = SSjob.GetJobType(jobtype)
+ J.lawyers++
+ if(J.lawyers>1)
+ uniform = /obj/item/clothing/under/lawyer/purpsuit
+ suit = /obj/item/clothing/suit/toggle/lawyer/purple
diff --git a/code/modules/jobs/job_types/medical.dm b/code/modules/jobs/job_types/medical.dm
deleted file mode 100644
index 5a926f490a..0000000000
--- a/code/modules/jobs/job_types/medical.dm
+++ /dev/null
@@ -1,204 +0,0 @@
-/*
-Chief Medical Officer
-*/
-/datum/job/cmo
- title = "Chief Medical Officer"
- flag = CMO_JF
- department_head = list("Captain")
- department_flag = MEDSCI
- head_announce = list("Medical")
- faction = "Station"
- total_positions = 1
- spawn_positions = 1
- supervisors = "the captain"
- selection_color = "#ffddf0"
- req_admin_notify = 1
- minimal_player_age = 7
- exp_requirements = 180
- exp_type = EXP_TYPE_CREW
- exp_type_department = EXP_TYPE_MEDICAL
-
- outfit = /datum/outfit/job/cmo
-
- access = list(ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_GENETICS, ACCESS_CLONING, ACCESS_HEADS, ACCESS_MINERAL_STOREROOM,
- ACCESS_CHEMISTRY, ACCESS_VIROLOGY, ACCESS_CMO, ACCESS_SURGERY, ACCESS_RC_ANNOUNCE,
- ACCESS_KEYCARD_AUTH, ACCESS_SEC_DOORS, ACCESS_MAINT_TUNNELS)
- minimal_access = list(ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_GENETICS, ACCESS_CLONING, ACCESS_HEADS, ACCESS_MINERAL_STOREROOM,
- ACCESS_CHEMISTRY, ACCESS_VIROLOGY, ACCESS_CMO, ACCESS_SURGERY, ACCESS_RC_ANNOUNCE,
- ACCESS_KEYCARD_AUTH, ACCESS_SEC_DOORS, ACCESS_MAINT_TUNNELS)
-
-/datum/outfit/job/cmo
- name = "Chief Medical Officer"
- jobtype = /datum/job/cmo
-
- id = /obj/item/card/id/silver
- belt = /obj/item/pda/heads/cmo
- l_pocket = /obj/item/pinpointer/crew
- ears = /obj/item/radio/headset/heads/cmo
- uniform = /obj/item/clothing/under/rank/chief_medical_officer
- shoes = /obj/item/clothing/shoes/sneakers/brown
- suit = /obj/item/clothing/suit/toggle/labcoat/cmo
- l_hand = /obj/item/storage/firstaid/regular
- suit_store = /obj/item/flashlight/pen
- backpack_contents = list(/obj/item/melee/classic_baton/telescopic=1)
-
- backpack = /obj/item/storage/backpack/medic
- satchel = /obj/item/storage/backpack/satchel/med
- duffelbag = /obj/item/storage/backpack/duffelbag/med
-
- chameleon_extras = list(/obj/item/gun/syringe, /obj/item/stamp/cmo)
-
-/datum/outfit/job/cmo/hardsuit
- name = "Chief Medical Officer (Hardsuit)"
-
- mask = /obj/item/clothing/mask/breath
- suit = /obj/item/clothing/suit/space/hardsuit/medical
- suit_store = /obj/item/tank/internals/oxygen
- r_pocket = /obj/item/flashlight/pen
-
-/*
-Medical Doctor
-*/
-/datum/job/doctor
- title = "Medical Doctor"
- flag = DOCTOR
- department_head = list("Chief Medical Officer")
- department_flag = MEDSCI
- faction = "Station"
- total_positions = 5
- spawn_positions = 3
- supervisors = "the chief medical officer"
- selection_color = "#ffeef0"
-
- outfit = /datum/outfit/job/doctor
-
- access = list(ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_SURGERY, ACCESS_CHEMISTRY, ACCESS_GENETICS, ACCESS_CLONING, ACCESS_MINERAL_STOREROOM)
- minimal_access = list(ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_SURGERY, ACCESS_CLONING, ACCESS_MINERAL_STOREROOM)
-
-/datum/outfit/job/doctor
- name = "Medical Doctor"
- jobtype = /datum/job/doctor
-
- belt = /obj/item/pda/medical
- ears = /obj/item/radio/headset/headset_med
- uniform = /obj/item/clothing/under/rank/medical
- shoes = /obj/item/clothing/shoes/sneakers/white
- suit = /obj/item/clothing/suit/toggle/labcoat
- l_hand = /obj/item/storage/firstaid/regular
- suit_store = /obj/item/flashlight/pen
-
- backpack = /obj/item/storage/backpack/medic
- satchel = /obj/item/storage/backpack/satchel/med
- duffelbag = /obj/item/storage/backpack/duffelbag/med
-
- chameleon_extras = /obj/item/gun/syringe
-
-/*
-Chemist
-*/
-/datum/job/chemist
- title = "Chemist"
- flag = CHEMIST
- department_head = list("Chief Medical Officer")
- department_flag = MEDSCI
- faction = "Station"
- total_positions = 2
- spawn_positions = 2
- supervisors = "the chief medical officer"
- selection_color = "#ffeef0"
- exp_type = EXP_TYPE_CREW
- exp_requirements = 60
-
- outfit = /datum/outfit/job/chemist
-
- access = list(ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_SURGERY, ACCESS_CHEMISTRY, ACCESS_GENETICS, ACCESS_CLONING, ACCESS_MINERAL_STOREROOM)
- minimal_access = list(ACCESS_MEDICAL, ACCESS_CHEMISTRY, ACCESS_MINERAL_STOREROOM)
-
-/datum/outfit/job/chemist
- name = "Chemist"
- jobtype = /datum/job/chemist
-
- glasses = /obj/item/clothing/glasses/science
- belt = /obj/item/pda/chemist
- ears = /obj/item/radio/headset/headset_med
- uniform = /obj/item/clothing/under/rank/chemist
- shoes = /obj/item/clothing/shoes/sneakers/white
- suit = /obj/item/clothing/suit/toggle/labcoat/chemist
- backpack = /obj/item/storage/backpack/chemistry
- satchel = /obj/item/storage/backpack/satchel/chem
- duffelbag = /obj/item/storage/backpack/duffelbag/med
-
- chameleon_extras = /obj/item/gun/syringe
-
-/*
-Geneticist
-*/
-/datum/job/geneticist
- title = "Geneticist"
- flag = GENETICIST
- department_head = list("Chief Medical Officer", "Research Director")
- department_flag = MEDSCI
- faction = "Station"
- total_positions = 2
- spawn_positions = 2
- supervisors = "the chief medical officer and research director"
- selection_color = "#ffeef0"
- exp_type = EXP_TYPE_CREW
- exp_requirements = 60
-
- outfit = /datum/outfit/job/geneticist
-
- access = list(ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_CHEMISTRY, ACCESS_GENETICS, ACCESS_CLONING, ACCESS_RESEARCH, ACCESS_XENOBIOLOGY, ACCESS_ROBOTICS, ACCESS_MINERAL_STOREROOM, ACCESS_TECH_STORAGE)
- minimal_access = list(ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_GENETICS, ACCESS_CLONING, ACCESS_RESEARCH, ACCESS_MINERAL_STOREROOM)
-
-/datum/outfit/job/geneticist
- name = "Geneticist"
- jobtype = /datum/job/geneticist
-
- belt = /obj/item/pda/geneticist
- ears = /obj/item/radio/headset/headset_medsci
- uniform = /obj/item/clothing/under/rank/geneticist
- shoes = /obj/item/clothing/shoes/sneakers/white
- suit = /obj/item/clothing/suit/toggle/labcoat/genetics
- suit_store = /obj/item/flashlight/pen
-
- backpack = /obj/item/storage/backpack/genetics
- satchel = /obj/item/storage/backpack/satchel/gen
- duffelbag = /obj/item/storage/backpack/duffelbag/med
-
-/*
-Virologist
-*/
-/datum/job/virologist
- title = "Virologist"
- flag = VIROLOGIST
- department_head = list("Chief Medical Officer")
- department_flag = MEDSCI
- faction = "Station"
- total_positions = 1
- spawn_positions = 1
- supervisors = "the chief medical officer"
- selection_color = "#ffeef0"
- exp_type = EXP_TYPE_CREW
- exp_requirements = 60
-
- outfit = /datum/outfit/job/virologist
-
- access = list(ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_SURGERY, ACCESS_CHEMISTRY, ACCESS_VIROLOGY, ACCESS_GENETICS, ACCESS_CLONING, ACCESS_MINERAL_STOREROOM)
- minimal_access = list(ACCESS_MEDICAL, ACCESS_VIROLOGY, ACCESS_MINERAL_STOREROOM)
-
-/datum/outfit/job/virologist
- name = "Virologist"
- jobtype = /datum/job/virologist
-
- belt = /obj/item/pda/viro
- ears = /obj/item/radio/headset/headset_med
- uniform = /obj/item/clothing/under/rank/virologist
- mask = /obj/item/clothing/mask/surgical
- shoes = /obj/item/clothing/shoes/sneakers/white
- suit = /obj/item/clothing/suit/toggle/labcoat/virologist
- suit_store = /obj/item/flashlight/pen
-
- backpack = /obj/item/storage/backpack/virology
- satchel = /obj/item/storage/backpack/satchel/vir
- duffelbag = /obj/item/storage/backpack/duffelbag/med
diff --git a/code/modules/jobs/job_types/medical_doctor.dm b/code/modules/jobs/job_types/medical_doctor.dm
new file mode 100644
index 0000000000..19fa1c7158
--- /dev/null
+++ b/code/modules/jobs/job_types/medical_doctor.dm
@@ -0,0 +1,35 @@
+/datum/job/doctor
+ title = "Medical Doctor"
+ flag = DOCTOR
+ department_head = list("Chief Medical Officer")
+ department_flag = MEDSCI
+ faction = "Station"
+ total_positions = 5
+ spawn_positions = 3
+ supervisors = "the chief medical officer"
+ selection_color = "#74b5e0"
+
+ outfit = /datum/outfit/job/doctor
+
+ access = list(ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_SURGERY, ACCESS_CHEMISTRY, ACCESS_GENETICS, ACCESS_CLONING, ACCESS_VIROLOGY, ACCESS_MINERAL_STOREROOM)
+ minimal_access = list(ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_SURGERY, ACCESS_CLONING, ACCESS_MINERAL_STOREROOM)
+
+ display_order = JOB_DISPLAY_ORDER_MEDICAL_DOCTOR
+
+/datum/outfit/job/doctor
+ name = "Medical Doctor"
+ jobtype = /datum/job/doctor
+
+ belt = /obj/item/pda/medical
+ ears = /obj/item/radio/headset/headset_med
+ uniform = /obj/item/clothing/under/rank/medical
+ shoes = /obj/item/clothing/shoes/sneakers/white
+ suit = /obj/item/clothing/suit/toggle/labcoat
+ l_hand = /obj/item/storage/firstaid/regular
+ suit_store = /obj/item/flashlight/pen
+
+ backpack = /obj/item/storage/backpack/medic
+ satchel = /obj/item/storage/backpack/satchel/med
+ duffelbag = /obj/item/storage/backpack/duffelbag/med
+
+ chameleon_extras = /obj/item/gun/syringe
diff --git a/code/modules/jobs/job_types/mime.dm b/code/modules/jobs/job_types/mime.dm
new file mode 100644
index 0000000000..1347da7125
--- /dev/null
+++ b/code/modules/jobs/job_types/mime.dm
@@ -0,0 +1,49 @@
+/datum/job/mime
+ title = "Mime"
+ flag = MIME
+ department_head = list("Head of Personnel")
+ department_flag = CIVILIAN
+ faction = "Station"
+ total_positions = 1
+ spawn_positions = 1
+ supervisors = "the head of personnel"
+ selection_color = "#dddddd"
+
+ outfit = /datum/outfit/job/mime
+
+ access = list(ACCESS_THEATRE)
+ minimal_access = list(ACCESS_THEATRE)
+
+ display_order = JOB_DISPLAY_ORDER_MIME
+
+/datum/job/mime/after_spawn(mob/living/carbon/human/H, mob/M)
+ H.apply_pref_name("mime", M.client)
+
+/datum/outfit/job/mime
+ name = "Mime"
+ jobtype = /datum/job/mime
+
+ belt = /obj/item/pda/mime
+ ears = /obj/item/radio/headset/headset_srv
+ uniform = /obj/item/clothing/under/rank/mime
+ mask = /obj/item/clothing/mask/gas/mime
+ gloves = /obj/item/clothing/gloves/color/white
+ head = /obj/item/clothing/head/frenchberet
+ suit = /obj/item/clothing/suit/suspenders
+ backpack_contents = list(/obj/item/reagent_containers/food/drinks/bottle/bottleofnothing=1)
+
+ backpack = /obj/item/storage/backpack/mime
+ satchel = /obj/item/storage/backpack/mime
+
+
+/datum/outfit/job/mime/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
+ ..()
+
+ if(visualsOnly)
+ return
+
+ if(H.mind)
+ H.mind.AddSpell(new /obj/effect/proc_holder/spell/aoe_turf/conjure/mime_wall(null))
+ H.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/mime/speak(null))
+ H.mind.miming = 1
+
diff --git a/code/modules/jobs/job_types/quartermaster.dm b/code/modules/jobs/job_types/quartermaster.dm
new file mode 100644
index 0000000000..49a93026ba
--- /dev/null
+++ b/code/modules/jobs/job_types/quartermaster.dm
@@ -0,0 +1,41 @@
+/datum/job/qm
+ title = "Quartermaster"
+ flag = QUARTERMASTER
+ department_head = list("Captain")
+ department_flag = CIVILIAN
+ head_announce = list(RADIO_CHANNEL_SUPPLY)
+// auto_deadmin_role_flags = DEADMIN_POSITION_HEAD
+ faction = "Station"
+ total_positions = 1
+ spawn_positions = 1
+ supervisors = "the captain"
+ selection_color = "#a06121"
+ req_admin_notify = 1
+ minimal_player_age = 7
+ exp_requirements = 180
+ exp_type = EXP_TYPE_CREW
+ exp_type_department = EXP_TYPE_SUPPLY
+
+ outfit = /datum/outfit/job/quartermaster
+
+ access = list(ACCESS_MAINT_TUNNELS, ACCESS_MAILSORTING, ACCESS_CARGO, ACCESS_QM, ACCESS_MINING, ACCESS_MINING_STATION,
+ ACCESS_MINERAL_STOREROOM, ACCESS_VAULT)
+ minimal_access = list(ACCESS_MAINT_TUNNELS, ACCESS_MAILSORTING, ACCESS_CARGO, ACCESS_QM, ACCESS_MINING,
+ ACCESS_MINING_STATION, ACCESS_MINERAL_STOREROOM, ACCESS_VAULT)
+
+ display_order = JOB_DISPLAY_ORDER_QUARTERMASTER
+ blacklisted_quirks = list(/datum/quirk/mute, /datum/quirk/brainproblems, /datum/quirk/insanity)
+
+/datum/outfit/job/quartermaster
+ name = "Quartermaster"
+ jobtype = /datum/job/qm
+
+ belt = /obj/item/pda/quartermaster
+ ears = /obj/item/radio/headset/headset_cargo
+ uniform = /obj/item/clothing/under/rank/cargo
+ shoes = /obj/item/clothing/shoes/sneakers/brown
+ glasses = /obj/item/clothing/glasses/sunglasses
+ l_hand = /obj/item/clipboard
+
+ chameleon_extras = /obj/item/stamp/qm
+
diff --git a/code/modules/jobs/job_types/research_director.dm b/code/modules/jobs/job_types/research_director.dm
new file mode 100644
index 0000000000..5368ceee64
--- /dev/null
+++ b/code/modules/jobs/job_types/research_director.dm
@@ -0,0 +1,61 @@
+/datum/job/rd
+ title = "Research Director"
+ flag = RD_JF
+// auto_deadmin_role_flags = DEADMIN_POSITION_HEAD
+ department_head = list("Captain")
+ department_flag = MEDSCI
+ head_announce = list(RADIO_CHANNEL_SCIENCE)
+ faction = "Station"
+ total_positions = 1
+ spawn_positions = 1
+ supervisors = "the captain"
+ selection_color = "#7544cc"
+ req_admin_notify = 1
+ minimal_player_age = 7
+ exp_type_department = EXP_TYPE_SCIENCE
+ exp_requirements = 180
+ exp_type = EXP_TYPE_CREW
+
+ outfit = /datum/outfit/job/rd
+
+ access = list(ACCESS_RD, ACCESS_HEADS, ACCESS_TOX, ACCESS_GENETICS, ACCESS_MORGUE,
+ ACCESS_TOX_STORAGE, ACCESS_TELEPORTER, ACCESS_SEC_DOORS,
+ ACCESS_RESEARCH, ACCESS_ROBOTICS, ACCESS_XENOBIOLOGY, ACCESS_AI_UPLOAD,
+ ACCESS_RC_ANNOUNCE, ACCESS_KEYCARD_AUTH, ACCESS_GATEWAY, ACCESS_MINERAL_STOREROOM,
+ ACCESS_TECH_STORAGE, ACCESS_MINISAT, ACCESS_MAINT_TUNNELS, ACCESS_NETWORK)
+ minimal_access = list(ACCESS_RD, ACCESS_HEADS, ACCESS_TOX, ACCESS_GENETICS, ACCESS_MORGUE,
+ ACCESS_TOX_STORAGE, ACCESS_TELEPORTER, ACCESS_SEC_DOORS,
+ ACCESS_RESEARCH, ACCESS_ROBOTICS, ACCESS_XENOBIOLOGY, ACCESS_AI_UPLOAD,
+ ACCESS_RC_ANNOUNCE, ACCESS_KEYCARD_AUTH, ACCESS_GATEWAY, ACCESS_MINERAL_STOREROOM,
+ ACCESS_TECH_STORAGE, ACCESS_MINISAT, ACCESS_MAINT_TUNNELS, ACCESS_NETWORK)
+
+ display_order = JOB_DISPLAY_ORDER_RESEARCH_DIRECTOR
+ blacklisted_quirks = list(/datum/quirk/mute, /datum/quirk/brainproblems, /datum/quirk/insanity)
+
+/datum/outfit/job/rd
+ name = "Research Director"
+ jobtype = /datum/job/rd
+
+ id = /obj/item/card/id/silver
+ belt = /obj/item/pda/heads/rd
+ ears = /obj/item/radio/headset/heads/rd
+ uniform = /obj/item/clothing/under/rank/research_director
+ shoes = /obj/item/clothing/shoes/sneakers/brown
+ suit = /obj/item/clothing/suit/toggle/labcoat
+ l_hand = /obj/item/clipboard
+ l_pocket = /obj/item/laser_pointer
+ backpack_contents = list(/obj/item/melee/classic_baton/telescopic=1, /obj/item/modular_computer/tablet/preset/advanced=1)
+
+ backpack = /obj/item/storage/backpack/science
+ satchel = /obj/item/storage/backpack/satchel/tox
+
+ chameleon_extras = /obj/item/stamp/rd
+
+/datum/outfit/job/rd/rig
+ name = "Research Director (Hardsuit)"
+
+ l_hand = null
+ mask = /obj/item/clothing/mask/breath
+ suit = /obj/item/clothing/suit/space/hardsuit/rd
+ suit_store = /obj/item/tank/internals/oxygen
+ internals_slot = SLOT_S_STORE
diff --git a/code/modules/jobs/job_types/roboticist.dm b/code/modules/jobs/job_types/roboticist.dm
new file mode 100644
index 0000000000..782b175ad4
--- /dev/null
+++ b/code/modules/jobs/job_types/roboticist.dm
@@ -0,0 +1,34 @@
+/datum/job/roboticist
+ title = "Roboticist"
+ flag = ROBOTICIST
+ department_head = list("Research Director")
+ department_flag = MEDSCI
+ faction = "Station"
+ total_positions = 2
+ spawn_positions = 2
+ supervisors = "the research director"
+ selection_color = "#9574cd"
+ exp_requirements = 60
+ exp_type = EXP_TYPE_CREW
+
+ outfit = /datum/outfit/job/roboticist
+
+ access = list(ACCESS_ROBOTICS, ACCESS_TOX, ACCESS_TOX_STORAGE, ACCESS_TECH_STORAGE, ACCESS_MORGUE, ACCESS_RESEARCH, ACCESS_MINERAL_STOREROOM, ACCESS_XENOBIOLOGY, ACCESS_GENETICS)
+ minimal_access = list(ACCESS_ROBOTICS, ACCESS_TECH_STORAGE, ACCESS_MORGUE, ACCESS_RESEARCH, ACCESS_MINERAL_STOREROOM)
+
+ display_order = JOB_DISPLAY_ORDER_ROBOTICIST
+
+/datum/outfit/job/roboticist
+ name = "Roboticist"
+ jobtype = /datum/job/roboticist
+
+ belt = /obj/item/storage/belt/utility/full
+ l_pocket = /obj/item/pda/roboticist
+ ears = /obj/item/radio/headset/headset_sci
+ uniform = /obj/item/clothing/under/rank/roboticist
+ suit = /obj/item/clothing/suit/toggle/labcoat
+
+ backpack = /obj/item/storage/backpack/science
+ satchel = /obj/item/storage/backpack/satchel/tox
+
+ pda_slot = SLOT_L_STORE
diff --git a/code/modules/jobs/job_types/science.dm b/code/modules/jobs/job_types/science.dm
deleted file mode 100644
index 6a14f204a3..0000000000
--- a/code/modules/jobs/job_types/science.dm
+++ /dev/null
@@ -1,131 +0,0 @@
-/*
-Research Director
-*/
-/datum/job/rd
- title = "Research Director"
- flag = RD_JF
- department_head = list("Captain")
- department_flag = MEDSCI
- head_announce = list("Science")
- faction = "Station"
- total_positions = 1
- spawn_positions = 1
- supervisors = "the captain"
- selection_color = "#ffddff"
- req_admin_notify = 1
- minimal_player_age = 7
- exp_type_department = EXP_TYPE_SCIENCE
- exp_requirements = 180
- exp_type = EXP_TYPE_CREW
-
- outfit = /datum/outfit/job/rd
-
- access = list(ACCESS_RD, ACCESS_HEADS, ACCESS_TOX, ACCESS_GENETICS, ACCESS_MORGUE,
- ACCESS_TOX_STORAGE, ACCESS_TELEPORTER, ACCESS_SEC_DOORS,
- ACCESS_RESEARCH, ACCESS_ROBOTICS, ACCESS_XENOBIOLOGY, ACCESS_AI_UPLOAD,
- ACCESS_RC_ANNOUNCE, ACCESS_KEYCARD_AUTH, ACCESS_GATEWAY, ACCESS_MINERAL_STOREROOM,
- ACCESS_TECH_STORAGE, ACCESS_MINISAT, ACCESS_MAINT_TUNNELS, ACCESS_NETWORK)
- minimal_access = list(ACCESS_RD, ACCESS_HEADS, ACCESS_TOX, ACCESS_GENETICS, ACCESS_MORGUE,
- ACCESS_TOX_STORAGE, ACCESS_TELEPORTER, ACCESS_SEC_DOORS,
- ACCESS_RESEARCH, ACCESS_ROBOTICS, ACCESS_XENOBIOLOGY, ACCESS_AI_UPLOAD,
- ACCESS_RC_ANNOUNCE, ACCESS_KEYCARD_AUTH, ACCESS_GATEWAY, ACCESS_MINERAL_STOREROOM,
- ACCESS_TECH_STORAGE, ACCESS_MINISAT, ACCESS_MAINT_TUNNELS, ACCESS_NETWORK)
-
-/datum/outfit/job/rd
- name = "Research Director"
- jobtype = /datum/job/rd
-
- id = /obj/item/card/id/silver
- belt = /obj/item/pda/heads/rd
- ears = /obj/item/radio/headset/heads/rd
- uniform = /obj/item/clothing/under/rank/research_director
- shoes = /obj/item/clothing/shoes/sneakers/brown
- suit = /obj/item/clothing/suit/toggle/labcoat
- l_hand = /obj/item/clipboard
- l_pocket = /obj/item/laser_pointer
- backpack_contents = list(/obj/item/melee/classic_baton/telescopic=1, /obj/item/modular_computer/tablet/preset/advanced=1)
-
- backpack = /obj/item/storage/backpack/science
- satchel = /obj/item/storage/backpack/satchel/tox
-
- chameleon_extras = /obj/item/stamp/rd
-
-/datum/outfit/job/rd/rig
- name = "Research Director (Hardsuit)"
-
- l_hand = null
- mask = /obj/item/clothing/mask/breath
- suit = /obj/item/clothing/suit/space/hardsuit/rd
- suit_store = /obj/item/tank/internals/oxygen
- internals_slot = SLOT_S_STORE
-
-/*
-Scientist
-*/
-/datum/job/scientist
- title = "Scientist"
- flag = SCIENTIST
- department_head = list("Research Director")
- department_flag = MEDSCI
- faction = "Station"
- total_positions = 5
- spawn_positions = 3
- supervisors = "the research director"
- selection_color = "#ffeeff"
- exp_requirements = 60
- exp_type = EXP_TYPE_CREW
-
-
- outfit = /datum/outfit/job/scientist
-
- access = list(ACCESS_ROBOTICS, ACCESS_TOX, ACCESS_TOX_STORAGE, ACCESS_RESEARCH, ACCESS_XENOBIOLOGY, ACCESS_MINERAL_STOREROOM, ACCESS_TECH_STORAGE, ACCESS_GENETICS)
- minimal_access = list(ACCESS_TOX, ACCESS_TOX_STORAGE, ACCESS_RESEARCH, ACCESS_XENOBIOLOGY, ACCESS_MINERAL_STOREROOM)
-
-/datum/outfit/job/scientist
- name = "Scientist"
- jobtype = /datum/job/scientist
-
- belt = /obj/item/pda/toxins
- ears = /obj/item/radio/headset/headset_sci
- uniform = /obj/item/clothing/under/rank/scientist
- shoes = /obj/item/clothing/shoes/sneakers/white
- suit = /obj/item/clothing/suit/toggle/labcoat/science
-
- backpack = /obj/item/storage/backpack/science
- satchel = /obj/item/storage/backpack/satchel/tox
-
-/*
-Roboticist
-*/
-/datum/job/roboticist
- title = "Roboticist"
- flag = ROBOTICIST
- department_head = list("Research Director")
- department_flag = MEDSCI
- faction = "Station"
- total_positions = 2
- spawn_positions = 2
- supervisors = "the research director"
- selection_color = "#ffeeff"
- exp_requirements = 60
- exp_type = EXP_TYPE_CREW
-
- outfit = /datum/outfit/job/roboticist
-
- access = list(ACCESS_ROBOTICS, ACCESS_TOX, ACCESS_TOX_STORAGE, ACCESS_TECH_STORAGE, ACCESS_MORGUE, ACCESS_RESEARCH, ACCESS_MINERAL_STOREROOM, ACCESS_XENOBIOLOGY, ACCESS_GENETICS)
- minimal_access = list(ACCESS_ROBOTICS, ACCESS_TECH_STORAGE, ACCESS_MORGUE, ACCESS_RESEARCH, ACCESS_MINERAL_STOREROOM)
-
-/datum/outfit/job/roboticist
- name = "Roboticist"
- jobtype = /datum/job/roboticist
-
- belt = /obj/item/storage/belt/utility/full
- l_pocket = /obj/item/pda/roboticist
- ears = /obj/item/radio/headset/headset_sci
- uniform = /obj/item/clothing/under/rank/roboticist
- suit = /obj/item/clothing/suit/toggle/labcoat
-
- backpack = /obj/item/storage/backpack/science
- satchel = /obj/item/storage/backpack/satchel/tox
-
- pda_slot = SLOT_L_STORE
diff --git a/code/modules/jobs/job_types/scientist.dm b/code/modules/jobs/job_types/scientist.dm
new file mode 100644
index 0000000000..f40a25d6ba
--- /dev/null
+++ b/code/modules/jobs/job_types/scientist.dm
@@ -0,0 +1,33 @@
+/datum/job/scientist
+ title = "Scientist"
+ flag = SCIENTIST
+ department_head = list("Research Director")
+ department_flag = MEDSCI
+ faction = "Station"
+ total_positions = 5
+ spawn_positions = 3
+ supervisors = "the research director"
+ selection_color = "#9574cd"
+ exp_requirements = 60
+ exp_type = EXP_TYPE_CREW
+
+ outfit = /datum/outfit/job/scientist
+
+ access = list(ACCESS_ROBOTICS, ACCESS_TOX, ACCESS_TOX_STORAGE, ACCESS_RESEARCH, ACCESS_XENOBIOLOGY, ACCESS_MINERAL_STOREROOM, ACCESS_TECH_STORAGE, ACCESS_GENETICS)
+ minimal_access = list(ACCESS_TOX, ACCESS_TOX_STORAGE, ACCESS_RESEARCH, ACCESS_XENOBIOLOGY, ACCESS_MINERAL_STOREROOM)
+
+ display_order = JOB_DISPLAY_ORDER_SCIENTIST
+
+/datum/outfit/job/scientist
+ name = "Scientist"
+ jobtype = /datum/job/scientist
+
+ belt = /obj/item/pda/toxins
+ ears = /obj/item/radio/headset/headset_sci
+ uniform = /obj/item/clothing/under/rank/scientist
+ shoes = /obj/item/clothing/shoes/sneakers/white
+ suit = /obj/item/clothing/suit/toggle/labcoat/science
+
+ backpack = /obj/item/storage/backpack/science
+ satchel = /obj/item/storage/backpack/satchel/tox
+
diff --git a/code/modules/jobs/job_types/security.dm b/code/modules/jobs/job_types/security.dm
deleted file mode 100644
index 8d2b9e8681..0000000000
--- a/code/modules/jobs/job_types/security.dm
+++ /dev/null
@@ -1,341 +0,0 @@
-//Warden and regular officers add this result to their get_access()
-/datum/job/proc/check_config_for_sec_maint()
- if(CONFIG_GET(flag/security_has_maint_access))
- return list(ACCESS_MAINT_TUNNELS)
- return list()
-
-/*
-Head of Security
-*/
-/datum/job/hos
- title = "Head of Security"
- flag = HOS
- department_head = list("Captain")
- department_flag = ENGSEC
- head_announce = list("Security")
- faction = "Station"
- total_positions = 1
- spawn_positions = 1
- supervisors = "the captain"
- selection_color = "#ffdddd"
- req_admin_notify = 1
- minimal_player_age = 14
- exp_requirements = 300
- exp_type = EXP_TYPE_CREW
- exp_type_department = EXP_TYPE_SECURITY
-
- outfit = /datum/outfit/job/hos
-
- access = list(ACCESS_SECURITY, ACCESS_SEC_DOORS, ACCESS_BRIG, ACCESS_ARMORY, ACCESS_COURT, ACCESS_WEAPONS,
- ACCESS_FORENSICS_LOCKERS, ACCESS_MORGUE, ACCESS_MAINT_TUNNELS, ACCESS_ALL_PERSONAL_LOCKERS,
- ACCESS_RESEARCH, ACCESS_ENGINE, ACCESS_MINING, ACCESS_MEDICAL, ACCESS_CONSTRUCTION, ACCESS_MAILSORTING,
- ACCESS_HEADS, ACCESS_HOS, ACCESS_RC_ANNOUNCE, ACCESS_KEYCARD_AUTH, ACCESS_GATEWAY, ACCESS_MAINT_TUNNELS, ACCESS_MINERAL_STOREROOM)
- minimal_access = list(ACCESS_SECURITY, ACCESS_SEC_DOORS, ACCESS_BRIG, ACCESS_ARMORY, ACCESS_COURT, ACCESS_WEAPONS,
- ACCESS_FORENSICS_LOCKERS, ACCESS_MORGUE, ACCESS_MAINT_TUNNELS, ACCESS_ALL_PERSONAL_LOCKERS,
- ACCESS_RESEARCH, ACCESS_ENGINE, ACCESS_MINING, ACCESS_MEDICAL, ACCESS_CONSTRUCTION, ACCESS_MAILSORTING,
- ACCESS_HEADS, ACCESS_HOS, ACCESS_RC_ANNOUNCE, ACCESS_KEYCARD_AUTH, ACCESS_GATEWAY, ACCESS_MAINT_TUNNELS, ACCESS_MINERAL_STOREROOM)
-
- mind_traits = list(TRAIT_LAW_ENFORCEMENT_METABOLISM)
-
-/datum/outfit/job/hos
- name = "Head of Security"
- jobtype = /datum/job/hos
-
- id = /obj/item/card/id/silver
- belt = /obj/item/pda/heads/hos
- ears = /obj/item/radio/headset/heads/hos/alt
- uniform = /obj/item/clothing/under/rank/head_of_security
- shoes = /obj/item/clothing/shoes/jackboots
- suit = /obj/item/clothing/suit/armor/hos/trenchcoat
- gloves = /obj/item/clothing/gloves/color/black/hos
- head = /obj/item/clothing/head/HoS/beret
- glasses = /obj/item/clothing/glasses/hud/security/sunglasses
- suit_store = /obj/item/gun/energy/e_gun
- r_pocket = /obj/item/assembly/flash/handheld
- l_pocket = /obj/item/restraints/handcuffs
- backpack_contents = list(/obj/item/melee/classic_baton/telescopic=1)
-
- backpack = /obj/item/storage/backpack/security
- satchel = /obj/item/storage/backpack/satchel/sec
- duffelbag = /obj/item/storage/backpack/duffelbag/sec
- box = /obj/item/storage/box/security
-
- implants = list(/obj/item/implant/mindshield)
-
- chameleon_extras = list(/obj/item/gun/energy/e_gun/hos, /obj/item/stamp/hos)
-
-/datum/outfit/job/hos/hardsuit
- name = "Head of Security (Hardsuit)"
-
- mask = /obj/item/clothing/mask/gas/sechailer
- suit = /obj/item/clothing/suit/space/hardsuit/security/hos
- suit_store = /obj/item/tank/internals/oxygen
- backpack_contents = list(/obj/item/melee/baton/loaded=1, /obj/item/gun/energy/e_gun=1)
-
-/*
-Warden
-*/
-/datum/job/warden
- title = "Warden"
- flag = WARDEN
- department_head = list("Head of Security")
- department_flag = ENGSEC
- faction = "Station"
- total_positions = 1
- spawn_positions = 1
- supervisors = "the head of security"
- selection_color = "#ffeeee"
- minimal_player_age = 7
- exp_requirements = 300
- exp_type = EXP_TYPE_CREW
-
- outfit = /datum/outfit/job/warden
-
- access = list(ACCESS_SECURITY, ACCESS_SEC_DOORS, ACCESS_BRIG, ACCESS_ARMORY, ACCESS_COURT, ACCESS_MAINT_TUNNELS, ACCESS_MORGUE, ACCESS_WEAPONS, ACCESS_FORENSICS_LOCKERS, ACCESS_MINERAL_STOREROOM)
- minimal_access = list(ACCESS_SECURITY, ACCESS_SEC_DOORS, ACCESS_BRIG, ACCESS_ARMORY, ACCESS_COURT, ACCESS_WEAPONS, ACCESS_MINERAL_STOREROOM) //SEE /DATUM/JOB/WARDEN/GET_ACCESS()
-
- mind_traits = list(TRAIT_LAW_ENFORCEMENT_METABOLISM)
-
-/datum/job/warden/get_access()
- var/list/L = list()
- L = ..() | check_config_for_sec_maint()
- return L
-
-/datum/outfit/job/warden
- name = "Warden"
- jobtype = /datum/job/warden
-
- belt = /obj/item/pda/warden
- ears = /obj/item/radio/headset/headset_sec/alt
- uniform = /obj/item/clothing/under/rank/warden
- shoes = /obj/item/clothing/shoes/jackboots
- suit = /obj/item/clothing/suit/armor/vest/warden/alt
- gloves = /obj/item/clothing/gloves/color/black
- head = /obj/item/clothing/head/warden
- glasses = /obj/item/clothing/glasses/hud/security/sunglasses
- r_pocket = /obj/item/assembly/flash/handheld
- l_pocket = /obj/item/restraints/handcuffs
- suit_store = /obj/item/gun/energy/e_gun/advtaser
- backpack_contents = list(/obj/item/melee/baton/loaded=1)
-
- backpack = /obj/item/storage/backpack/security
- satchel = /obj/item/storage/backpack/satchel/sec
- duffelbag = /obj/item/storage/backpack/duffelbag/sec
- box = /obj/item/storage/box/security
-
- implants = list(/obj/item/implant/mindshield)
-
- chameleon_extras = /obj/item/gun/ballistic/shotgun/automatic/combat/compact
-
-/*
-Detective
-*/
-/datum/job/detective
- title = "Detective"
- flag = DETECTIVE
- department_head = list("Head of Security")
- department_flag = ENGSEC
- faction = "Station"
- total_positions = 1
- spawn_positions = 1
- supervisors = "the head of security"
- selection_color = "#ffeeee"
- minimal_player_age = 7
- exp_requirements = 300
- exp_type = EXP_TYPE_CREW
-
- outfit = /datum/outfit/job/detective
-
- access = list(ACCESS_SEC_DOORS, ACCESS_FORENSICS_LOCKERS, ACCESS_MORGUE, ACCESS_MAINT_TUNNELS, ACCESS_COURT, ACCESS_BRIG, ACCESS_WEAPONS, ACCESS_MINERAL_STOREROOM)
- minimal_access = list(ACCESS_SEC_DOORS, ACCESS_FORENSICS_LOCKERS, ACCESS_MORGUE, ACCESS_MAINT_TUNNELS, ACCESS_COURT, ACCESS_BRIG, ACCESS_WEAPONS, ACCESS_MINERAL_STOREROOM)
-
- mind_traits = list(TRAIT_LAW_ENFORCEMENT_METABOLISM)
-
-/datum/outfit/job/detective
- name = "Detective"
- jobtype = /datum/job/detective
-
- belt = /obj/item/pda/detective
- ears = /obj/item/radio/headset/headset_sec/alt
- uniform = /obj/item/clothing/under/rank/det
- shoes = /obj/item/clothing/shoes/sneakers/brown
- suit = /obj/item/clothing/suit/det_suit
- gloves = /obj/item/clothing/gloves/color/black
- head = /obj/item/clothing/head/fedora/det_hat
- l_pocket = /obj/item/toy/crayon/white
- r_pocket = /obj/item/lighter
- backpack_contents = list(/obj/item/storage/box/evidence=1,\
- /obj/item/detective_scanner=1,\
- /obj/item/melee/classic_baton=1)
- mask = /obj/item/clothing/mask/cigarette
-
- implants = list(/obj/item/implant/mindshield)
-
- chameleon_extras = list(/obj/item/gun/ballistic/revolver/detective, /obj/item/clothing/glasses/sunglasses)
-
-/datum/outfit/job/detective/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
- ..()
- var/obj/item/clothing/mask/cigarette/cig = H.wear_mask
- if(istype(cig)) //Some species specfic changes can mess this up (plasmamen)
- cig.light("")
-
- if(visualsOnly)
- return
-
-/*
-Security Officer
-*/
-/datum/job/officer
- title = "Security Officer"
- flag = OFFICER
- department_head = list("Head of Security")
- department_flag = ENGSEC
- faction = "Station"
- total_positions = 5 //Handled in /datum/controller/occupations/proc/setup_officer_positions()
- spawn_positions = 5 //Handled in /datum/controller/occupations/proc/setup_officer_positions()
- supervisors = "the head of security, and the head of your assigned department (if applicable)"
- selection_color = "#ffeeee"
- minimal_player_age = 7
- exp_requirements = 300
- exp_type = EXP_TYPE_CREW
-
- outfit = /datum/outfit/job/security
-
- access = list(ACCESS_SECURITY, ACCESS_SEC_DOORS, ACCESS_BRIG, ACCESS_COURT, ACCESS_MAINT_TUNNELS, ACCESS_MORGUE, ACCESS_WEAPONS, ACCESS_FORENSICS_LOCKERS, ACCESS_MINERAL_STOREROOM)
- minimal_access = list(ACCESS_SECURITY, ACCESS_SEC_DOORS, ACCESS_BRIG, ACCESS_COURT, ACCESS_WEAPONS, ACCESS_MINERAL_STOREROOM) //BUT SEE /DATUM/JOB/WARDEN/GET_ACCESS()
-
- mind_traits = list(TRAIT_LAW_ENFORCEMENT_METABOLISM)
-
-/datum/job/officer/get_access()
- var/list/L = list()
- L |= ..() | check_config_for_sec_maint()
- return L
-
-GLOBAL_LIST_INIT(available_depts, list(SEC_DEPT_ENGINEERING, SEC_DEPT_MEDICAL, SEC_DEPT_SCIENCE, SEC_DEPT_SUPPLY))
-
-/datum/job/officer/after_spawn(mob/living/carbon/human/H, mob/M)
- // Assign department security
- var/department
- if(M && M.client && M.client.prefs)
- department = M.client.prefs.prefered_security_department
- if(!LAZYLEN(GLOB.available_depts) || department == "None")
- return
- else if(department in GLOB.available_depts)
- LAZYREMOVE(GLOB.available_depts, department)
- else
- department = pick_n_take(GLOB.available_depts)
- var/ears = null
- var/accessory = null
- var/list/dep_access = null
- var/destination = null
- var/spawn_point = null
- switch(department)
- if(SEC_DEPT_SUPPLY)
- ears = /obj/item/radio/headset/headset_sec/alt/department/supply
- dep_access = list(ACCESS_MAILSORTING, ACCESS_MINING, ACCESS_MINING_STATION)
- destination = /area/security/checkpoint/supply
- spawn_point = locate(/obj/effect/landmark/start/depsec/supply) in GLOB.department_security_spawns
- accessory = /obj/item/clothing/accessory/armband/cargo
- if(SEC_DEPT_ENGINEERING)
- ears = /obj/item/radio/headset/headset_sec/alt/department/engi
- dep_access = list(ACCESS_CONSTRUCTION, ACCESS_ENGINE)
- destination = /area/security/checkpoint/engineering
- spawn_point = locate(/obj/effect/landmark/start/depsec/engineering) in GLOB.department_security_spawns
- accessory = /obj/item/clothing/accessory/armband/engine
- if(SEC_DEPT_MEDICAL)
- ears = /obj/item/radio/headset/headset_sec/alt/department/med
- dep_access = list(ACCESS_MEDICAL)
- destination = /area/security/checkpoint/medical
- spawn_point = locate(/obj/effect/landmark/start/depsec/medical) in GLOB.department_security_spawns
- accessory = /obj/item/clothing/accessory/armband/medblue
- if(SEC_DEPT_SCIENCE)
- ears = /obj/item/radio/headset/headset_sec/alt/department/sci
- dep_access = list(ACCESS_RESEARCH)
- destination = /area/security/checkpoint/science
- spawn_point = locate(/obj/effect/landmark/start/depsec/science) in GLOB.department_security_spawns
- accessory = /obj/item/clothing/accessory/armband/science
-
- if(accessory)
- var/obj/item/clothing/under/U = H.w_uniform
- U.attach_accessory(new accessory)
- if(ears)
- if(H.ears)
- qdel(H.ears)
- H.equip_to_slot_or_del(new ears(H),SLOT_EARS)
-
- var/obj/item/card/id/W = H.wear_id
- W.access |= dep_access
-
- var/teleport = 0
- if(!CONFIG_GET(flag/sec_start_brig))
- if(destination || spawn_point)
- teleport = 1
- if(teleport)
- var/turf/T
- if(spawn_point)
- T = get_turf(spawn_point)
- H.Move(T)
- else
- var/safety = 0
- while(safety < 25)
- T = safepick(get_area_turfs(destination))
- if(T && !H.Move(T))
- safety += 1
- continue
- else
- break
- if(department)
- to_chat(M, "You have been assigned to [department]!")
- else
- to_chat(M, "You have not been assigned to any department. Patrol the halls and help where needed.")
-
-
-
-/datum/outfit/job/security
- name = "Security Officer"
- jobtype = /datum/job/officer
-
- belt = /obj/item/pda/security
- ears = /obj/item/radio/headset/headset_sec/alt
- uniform = /obj/item/clothing/under/rank/security
- gloves = /obj/item/clothing/gloves/color/black
- head = /obj/item/clothing/head/helmet/sec
- suit = /obj/item/clothing/suit/armor/vest/alt
- shoes = /obj/item/clothing/shoes/jackboots
- l_pocket = /obj/item/restraints/handcuffs
- r_pocket = /obj/item/assembly/flash/handheld
- suit_store = /obj/item/gun/energy/e_gun/advtaser
- backpack_contents = list(/obj/item/melee/baton/loaded=1)
-
- backpack = /obj/item/storage/backpack/security
- satchel = /obj/item/storage/backpack/satchel/sec
- duffelbag = /obj/item/storage/backpack/duffelbag/sec
- box = /obj/item/storage/box/security
-
- implants = list(/obj/item/implant/mindshield)
-
- chameleon_extras = list(/obj/item/gun/energy/e_gun/advtaser, /obj/item/clothing/glasses/hud/security/sunglasses, /obj/item/clothing/head/helmet)
- //The helmet is necessary because /obj/item/clothing/head/helmet/sec is overwritten in the chameleon list by the standard helmet, which has the same name and icon state
-
-
-/obj/item/radio/headset/headset_sec/alt/department/Initialize()
- . = ..()
- wires = new/datum/wires/radio(src)
- secure_radio_connections = new
- recalculateChannels()
-
-/obj/item/radio/headset/headset_sec/alt/department/engi
- keyslot = new /obj/item/encryptionkey/headset_sec
- keyslot2 = new /obj/item/encryptionkey/headset_eng
-
-/obj/item/radio/headset/headset_sec/alt/department/supply
- keyslot = new /obj/item/encryptionkey/headset_sec
- keyslot2 = new /obj/item/encryptionkey/headset_cargo
-
-/obj/item/radio/headset/headset_sec/alt/department/med
- keyslot = new /obj/item/encryptionkey/headset_sec
- keyslot2 = new /obj/item/encryptionkey/headset_med
-
-/obj/item/radio/headset/headset_sec/alt/department/sci
- keyslot = new /obj/item/encryptionkey/headset_sec
- keyslot2 = new /obj/item/encryptionkey/headset_sci
diff --git a/code/modules/jobs/job_types/security_officer.dm b/code/modules/jobs/job_types/security_officer.dm
new file mode 100644
index 0000000000..4f12d6a19c
--- /dev/null
+++ b/code/modules/jobs/job_types/security_officer.dm
@@ -0,0 +1,159 @@
+/datum/job/officer
+ title = "Security Officer"
+ flag = OFFICER
+// auto_deadmin_role_flags = DEADMIN_POSITION_SECURITY
+ department_head = list("Head of Security")
+ department_flag = ENGSEC
+ faction = "Station"
+ total_positions = 5 //Handled in /datum/controller/occupations/proc/setup_officer_positions()
+ spawn_positions = 5 //Handled in /datum/controller/occupations/proc/setup_officer_positions()
+ supervisors = "the head of security, and the head of your assigned department (if applicable)"
+ selection_color = "#c02f2f"
+ minimal_player_age = 7
+ exp_requirements = 300
+ exp_type = EXP_TYPE_CREW
+
+ outfit = /datum/outfit/job/security
+
+ access = list(ACCESS_SECURITY, ACCESS_SEC_DOORS, ACCESS_BRIG, ACCESS_COURT, ACCESS_MAINT_TUNNELS, ACCESS_MORGUE, ACCESS_WEAPONS, ACCESS_FORENSICS_LOCKERS, ACCESS_MINERAL_STOREROOM)
+ minimal_access = list(ACCESS_SECURITY, ACCESS_SEC_DOORS, ACCESS_BRIG, ACCESS_COURT, ACCESS_WEAPONS, ACCESS_MINERAL_STOREROOM) // See /datum/job/officer/get_access()
+
+ mind_traits = list(TRAIT_LAW_ENFORCEMENT_METABOLISM)
+
+ display_order = JOB_DISPLAY_ORDER_SECURITY_OFFICER
+ blacklisted_quirks = list(/datum/quirk/mute, /datum/quirk/nonviolent, /datum/quirk/paraplegic)
+
+/datum/job/officer/get_access()
+ var/list/L = list()
+ L |= ..() | check_config_for_sec_maint()
+ return L
+
+GLOBAL_LIST_INIT(available_depts, list(SEC_DEPT_ENGINEERING, SEC_DEPT_MEDICAL, SEC_DEPT_SCIENCE, SEC_DEPT_SUPPLY))
+
+/datum/job/officer/after_spawn(mob/living/carbon/human/H, mob/M)
+ . = ..()
+ // Assign department security
+ var/department
+ if(M && M.client && M.client.prefs)
+ department = M.client.prefs.prefered_security_department
+ if(!LAZYLEN(GLOB.available_depts) || department == "None")
+ return
+ else if(department in GLOB.available_depts)
+ LAZYREMOVE(GLOB.available_depts, department)
+ else
+ department = pick_n_take(GLOB.available_depts)
+ var/ears = null
+ var/accessory = null
+ var/list/dep_access = null
+ var/destination = null
+ var/spawn_point = null
+ switch(department)
+ if(SEC_DEPT_SUPPLY)
+ ears = /obj/item/radio/headset/headset_sec/alt/department/supply
+ dep_access = list(ACCESS_MAILSORTING, ACCESS_MINING, ACCESS_MINING_STATION, ACCESS_CARGO)
+ destination = /area/security/checkpoint/supply
+ spawn_point = locate(/obj/effect/landmark/start/depsec/supply) in GLOB.department_security_spawns
+ accessory = /obj/item/clothing/accessory/armband/cargo
+ if(SEC_DEPT_ENGINEERING)
+ ears = /obj/item/radio/headset/headset_sec/alt/department/engi
+ dep_access = list(ACCESS_CONSTRUCTION, ACCESS_ENGINE, ACCESS_ATMOSPHERICS)
+ destination = /area/security/checkpoint/engineering
+ spawn_point = locate(/obj/effect/landmark/start/depsec/engineering) in GLOB.department_security_spawns
+ accessory = /obj/item/clothing/accessory/armband/engine
+ if(SEC_DEPT_MEDICAL)
+ ears = /obj/item/radio/headset/headset_sec/alt/department/med
+ dep_access = list(ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_SURGERY, ACCESS_CLONING)
+ destination = /area/security/checkpoint/medical
+ spawn_point = locate(/obj/effect/landmark/start/depsec/medical) in GLOB.department_security_spawns
+ accessory = /obj/item/clothing/accessory/armband/medblue
+ if(SEC_DEPT_SCIENCE)
+ ears = /obj/item/radio/headset/headset_sec/alt/department/sci
+ dep_access = list(ACCESS_RESEARCH, ACCESS_TOX)
+ destination = /area/security/checkpoint/science
+ spawn_point = locate(/obj/effect/landmark/start/depsec/science) in GLOB.department_security_spawns
+ accessory = /obj/item/clothing/accessory/armband/science
+
+ if(accessory)
+ var/obj/item/clothing/under/U = H.w_uniform
+ U.attach_accessory(new accessory)
+ if(ears)
+ if(H.ears)
+ qdel(H.ears)
+ H.equip_to_slot_or_del(new ears(H),SLOT_EARS)
+
+ var/obj/item/card/id/W = H.wear_id
+ W.access |= dep_access
+
+ var/teleport = 0
+ if(!CONFIG_GET(flag/sec_start_brig))
+ if(destination || spawn_point)
+ teleport = 1
+ if(teleport)
+ var/turf/T
+ if(spawn_point)
+ T = get_turf(spawn_point)
+ H.Move(T)
+ else
+ var/safety = 0
+ while(safety < 25)
+ T = safepick(get_area_turfs(destination))
+ if(T && !H.Move(T))
+ safety += 1
+ continue
+ else
+ break
+ if(department)
+ to_chat(M, "You have been assigned to [department]!")
+ else
+ to_chat(M, "You have not been assigned to any department. Patrol the halls and help where needed.")
+
+
+
+/datum/outfit/job/security
+ name = "Security Officer"
+ jobtype = /datum/job/officer
+
+ belt = /obj/item/pda/security
+ ears = /obj/item/radio/headset/headset_sec/alt
+ uniform = /obj/item/clothing/under/rank/security
+ gloves = /obj/item/clothing/gloves/color/black
+ head = /obj/item/clothing/head/helmet/sec
+ suit = /obj/item/clothing/suit/armor/vest/alt
+ shoes = /obj/item/clothing/shoes/jackboots
+ l_pocket = /obj/item/restraints/handcuffs
+ r_pocket = /obj/item/assembly/flash/handheld
+ suit_store = /obj/item/gun/energy/e_gun/advtaser
+ backpack_contents = list(/obj/item/melee/baton/loaded=1)
+
+ backpack = /obj/item/storage/backpack/security
+ satchel = /obj/item/storage/backpack/satchel/sec
+ duffelbag = /obj/item/storage/backpack/duffelbag/sec
+ box = /obj/item/storage/box/security
+
+ implants = list(/obj/item/implant/mindshield)
+
+ chameleon_extras = list(/obj/item/gun/energy/disabler, /obj/item/clothing/glasses/hud/security/sunglasses, /obj/item/clothing/head/helmet)
+ //The helmet is necessary because /obj/item/clothing/head/helmet/sec is overwritten in the chameleon list by the standard helmet, which has the same name and icon state
+
+
+/obj/item/radio/headset/headset_sec/alt/department/Initialize()
+ . = ..()
+ wires = new/datum/wires/radio(src)
+ secure_radio_connections = new
+ recalculateChannels()
+
+/obj/item/radio/headset/headset_sec/alt/department/engi
+ keyslot = new /obj/item/encryptionkey/headset_sec
+ keyslot2 = new /obj/item/encryptionkey/headset_eng
+
+/obj/item/radio/headset/headset_sec/alt/department/supply
+ keyslot = new /obj/item/encryptionkey/headset_sec
+ keyslot2 = new /obj/item/encryptionkey/headset_cargo
+
+/obj/item/radio/headset/headset_sec/alt/department/med
+ keyslot = new /obj/item/encryptionkey/headset_sec
+ keyslot2 = new /obj/item/encryptionkey/headset_med
+
+/obj/item/radio/headset/headset_sec/alt/department/sci
+ keyslot = new /obj/item/encryptionkey/headset_sec
+ keyslot2 = new /obj/item/encryptionkey/headset_sci
diff --git a/code/modules/jobs/job_types/shaft_miner.dm b/code/modules/jobs/job_types/shaft_miner.dm
new file mode 100644
index 0000000000..ef16d8e53f
--- /dev/null
+++ b/code/modules/jobs/job_types/shaft_miner.dm
@@ -0,0 +1,77 @@
+/datum/job/mining
+ title = "Shaft Miner"
+ flag = MINER
+ department_head = list("Quartermaster")
+ department_flag = CIVILIAN
+ faction = "Station"
+ total_positions = 3
+ spawn_positions = 3
+ supervisors = "the quartermaster"
+ selection_color = "#ca8f55"
+ custom_spawn_text = "Remember, you are a miner, not a hunter. Hunting monsters is not a requirement of your job, the only requirement of your job is to provide materials for the station. Don't be afraid to run away if you're inexperienced with fighting the mining area's locals."
+
+
+ outfit = /datum/outfit/job/miner
+
+ access = list(ACCESS_MAINT_TUNNELS, ACCESS_MAILSORTING, ACCESS_CARGO, ACCESS_QM, ACCESS_MINING, ACCESS_MINING_STATION, ACCESS_MINERAL_STOREROOM)
+ minimal_access = list(ACCESS_MINING, ACCESS_MINING_STATION, ACCESS_MAILSORTING, ACCESS_MINERAL_STOREROOM)
+
+ display_order = JOB_DISPLAY_ORDER_SHAFT_MINER
+
+/datum/outfit/job/miner
+ name = "Shaft Miner (Lavaland)"
+ jobtype = /datum/job/mining
+
+ belt = /obj/item/pda/shaftminer
+ ears = /obj/item/radio/headset/headset_cargo/mining
+ shoes = /obj/item/clothing/shoes/workboots/mining
+ gloves = /obj/item/clothing/gloves/color/black
+ uniform = /obj/item/clothing/under/rank/miner/lavaland
+ l_pocket = /obj/item/reagent_containers/hypospray/medipen/survival
+ r_pocket = /obj/item/storage/bag/ore //causes issues if spawned in backpack
+ backpack_contents = list(
+ /obj/item/flashlight/seclite=1,\
+ /obj/item/kitchen/knife/combat/survival=1,\
+ /obj/item/mining_voucher=1,\
+ /obj/item/suit_voucher=1,\
+ /obj/item/stack/marker_beacon/ten=1)
+
+ backpack = /obj/item/storage/backpack/explorer
+ satchel = /obj/item/storage/backpack/satchel/explorer
+ duffelbag = /obj/item/storage/backpack/duffelbag
+ box = /obj/item/storage/box/survival_mining
+
+ chameleon_extras = /obj/item/gun/energy/kinetic_accelerator
+
+/datum/outfit/job/miner/asteroid
+ name = "Shaft Miner (Asteroid)"
+ uniform = /obj/item/clothing/under/rank/miner
+ shoes = /obj/item/clothing/shoes/workboots
+
+/datum/outfit/job/miner/equipped
+ name = "Shaft Miner (Lavaland + Equipment)"
+ suit = /obj/item/clothing/suit/hooded/explorer/standard
+ mask = /obj/item/clothing/mask/gas/explorer
+ glasses = /obj/item/clothing/glasses/meson
+ suit_store = /obj/item/tank/internals/oxygen
+ internals_slot = SLOT_S_STORE
+ backpack_contents = list(
+ /obj/item/flashlight/seclite=1,\
+ /obj/item/kitchen/knife/combat/survival=1,
+ /obj/item/mining_voucher=1,
+ /obj/item/t_scanner/adv_mining_scanner/lesser=1,
+ /obj/item/gun/energy/kinetic_accelerator=1,\
+ /obj/item/stack/marker_beacon/ten=1)
+
+/datum/outfit/job/miner/equipped/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
+ ..()
+ if(visualsOnly)
+ return
+ if(istype(H.wear_suit, /obj/item/clothing/suit/hooded))
+ var/obj/item/clothing/suit/hooded/S = H.wear_suit
+ S.ToggleHood()
+
+/datum/outfit/job/miner/equipped/hardsuit
+ name = "Shaft Miner (Equipment + Hardsuit)"
+ suit = /obj/item/clothing/suit/space/hardsuit/mining
+ mask = /obj/item/clothing/mask/breath
diff --git a/code/modules/jobs/job_types/station_engineer.dm b/code/modules/jobs/job_types/station_engineer.dm
new file mode 100644
index 0000000000..55381549ba
--- /dev/null
+++ b/code/modules/jobs/job_types/station_engineer.dm
@@ -0,0 +1,54 @@
+/datum/job/engineer
+ title = "Station Engineer"
+ flag = ENGINEER
+ department_head = list("Chief Engineer")
+ department_flag = ENGSEC
+ faction = "Station"
+ total_positions = 5
+ spawn_positions = 5
+ supervisors = "the chief engineer"
+ selection_color = "#ff9b3d"
+ exp_requirements = 60
+ exp_type = EXP_TYPE_CREW
+
+ outfit = /datum/outfit/job/engineer
+
+ access = list(ACCESS_ENGINE, ACCESS_ENGINE_EQUIP, ACCESS_TECH_STORAGE, ACCESS_MAINT_TUNNELS,
+ ACCESS_EXTERNAL_AIRLOCKS, ACCESS_CONSTRUCTION, ACCESS_ATMOSPHERICS, ACCESS_TCOMSAT, ACCESS_MINERAL_STOREROOM)
+ minimal_access = list(ACCESS_ENGINE, ACCESS_ENGINE_EQUIP, ACCESS_TECH_STORAGE, ACCESS_MAINT_TUNNELS,
+ ACCESS_EXTERNAL_AIRLOCKS, ACCESS_CONSTRUCTION, ACCESS_TCOMSAT, ACCESS_MINERAL_STOREROOM)
+
+ display_order = JOB_DISPLAY_ORDER_STATION_ENGINEER
+
+/datum/outfit/job/engineer
+ name = "Station Engineer"
+ jobtype = /datum/job/engineer
+
+ belt = /obj/item/storage/belt/utility/full/engi
+ l_pocket = /obj/item/pda/engineering
+ ears = /obj/item/radio/headset/headset_eng
+ uniform = /obj/item/clothing/under/rank/engineer
+ shoes = /obj/item/clothing/shoes/workboots
+ head = /obj/item/clothing/head/hardhat
+ r_pocket = /obj/item/t_scanner
+
+ backpack = /obj/item/storage/backpack/industrial
+ satchel = /obj/item/storage/backpack/satchel/eng
+ duffelbag = /obj/item/storage/backpack/duffelbag/engineering
+ box = /obj/item/storage/box/engineer
+ pda_slot = SLOT_L_STORE
+ backpack_contents = list(/obj/item/modular_computer/tablet/preset/advanced=1)
+
+/datum/outfit/job/engineer/gloved
+ name = "Station Engineer (Gloves)"
+ gloves = /obj/item/clothing/gloves/color/yellow
+
+/datum/outfit/job/engineer/gloved/rig
+ name = "Station Engineer (Hardsuit)"
+ mask = /obj/item/clothing/mask/breath
+ suit = /obj/item/clothing/suit/space/hardsuit/engine
+ suit_store = /obj/item/tank/internals/oxygen
+ head = null
+ internals_slot = SLOT_S_STORE
+
+
diff --git a/code/modules/jobs/job_types/virologist.dm b/code/modules/jobs/job_types/virologist.dm
new file mode 100644
index 0000000000..dcc13af627
--- /dev/null
+++ b/code/modules/jobs/job_types/virologist.dm
@@ -0,0 +1,35 @@
+/datum/job/virologist
+ title = "Virologist"
+ flag = VIROLOGIST
+ department_head = list("Chief Medical Officer")
+ department_flag = MEDSCI
+ faction = "Station"
+ total_positions = 1
+ spawn_positions = 1
+ supervisors = "the chief medical officer"
+ selection_color = "#74b5e0"
+ exp_type = EXP_TYPE_CREW
+ exp_requirements = 60
+
+ outfit = /datum/outfit/job/virologist
+
+ access = list(ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_SURGERY, ACCESS_CHEMISTRY, ACCESS_VIROLOGY, ACCESS_GENETICS, ACCESS_CLONING, ACCESS_MINERAL_STOREROOM)
+ minimal_access = list(ACCESS_MEDICAL, ACCESS_VIROLOGY, ACCESS_MINERAL_STOREROOM)
+
+ display_order = JOB_DISPLAY_ORDER_VIROLOGIST
+
+/datum/outfit/job/virologist
+ name = "Virologist"
+ jobtype = /datum/job/virologist
+
+ belt = /obj/item/pda/viro
+ ears = /obj/item/radio/headset/headset_med
+ uniform = /obj/item/clothing/under/rank/virologist
+ mask = /obj/item/clothing/mask/surgical
+ shoes = /obj/item/clothing/shoes/sneakers/white
+ suit = /obj/item/clothing/suit/toggle/labcoat/virologist
+ suit_store = /obj/item/flashlight/pen
+
+ backpack = /obj/item/storage/backpack/virology
+ satchel = /obj/item/storage/backpack/satchel/vir
+ duffelbag = /obj/item/storage/backpack/duffelbag/med
diff --git a/code/modules/jobs/job_types/warden.dm b/code/modules/jobs/job_types/warden.dm
new file mode 100644
index 0000000000..a5c16ab5cf
--- /dev/null
+++ b/code/modules/jobs/job_types/warden.dm
@@ -0,0 +1,56 @@
+/datum/job/warden
+ title = "Warden"
+ flag = WARDEN
+// auto_deadmin_role_flags = DEADMIN_POSITION_SECURITY
+ department_head = list("Head of Security")
+ department_flag = ENGSEC
+ faction = "Station"
+ total_positions = 1
+ spawn_positions = 1
+ supervisors = "the head of security"
+ selection_color = "#c02f2f"
+ minimal_player_age = 7
+ exp_requirements = 300
+ exp_type = EXP_TYPE_CREW
+
+ outfit = /datum/outfit/job/warden
+
+ access = list(ACCESS_SECURITY, ACCESS_SEC_DOORS, ACCESS_BRIG, ACCESS_ARMORY, ACCESS_COURT, ACCESS_MAINT_TUNNELS, ACCESS_MORGUE, ACCESS_WEAPONS, ACCESS_FORENSICS_LOCKERS, ACCESS_MINERAL_STOREROOM)
+ minimal_access = list(ACCESS_SECURITY, ACCESS_SEC_DOORS, ACCESS_BRIG, ACCESS_ARMORY, ACCESS_COURT, ACCESS_WEAPONS, ACCESS_MINERAL_STOREROOM) // See /datum/job/warden/get_access()
+
+ mind_traits = list(TRAIT_LAW_ENFORCEMENT_METABOLISM)
+
+ display_order = JOB_DISPLAY_ORDER_WARDEN
+ blacklisted_quirks = list(/datum/quirk/mute, /datum/quirk/nonviolent, /datum/quirk/paraplegic)
+
+/datum/job/warden/get_access()
+ var/list/L = list()
+ L = ..() | check_config_for_sec_maint()
+ return L
+
+/datum/outfit/job/warden
+ name = "Warden"
+ jobtype = /datum/job/warden
+
+ belt = /obj/item/pda/warden
+ ears = /obj/item/radio/headset/headset_sec/alt
+ uniform = /obj/item/clothing/under/rank/warden
+ shoes = /obj/item/clothing/shoes/jackboots
+ suit = /obj/item/clothing/suit/armor/vest/warden/alt
+ gloves = /obj/item/clothing/gloves/color/black
+ head = /obj/item/clothing/head/warden
+ glasses = /obj/item/clothing/glasses/hud/security/sunglasses
+ r_pocket = /obj/item/assembly/flash/handheld
+ l_pocket = /obj/item/restraints/handcuffs
+ suit_store = /obj/item/gun/energy/e_gun/advtaser
+ backpack_contents = list(/obj/item/melee/baton/loaded=1)
+
+ backpack = /obj/item/storage/backpack/security
+ satchel = /obj/item/storage/backpack/satchel/sec
+ duffelbag = /obj/item/storage/backpack/duffelbag/sec
+ box = /obj/item/storage/box/security
+
+ implants = list(/obj/item/implant/mindshield)
+
+ chameleon_extras = /obj/item/gun/ballistic/shotgun/automatic/combat/compact
+
diff --git a/code/modules/keybindings/bindings_client.dm b/code/modules/keybindings/bindings_client.dm
index 548a734f74..2b8bfa6860 100644
--- a/code/modules/keybindings/bindings_client.dm
+++ b/code/modules/keybindings/bindings_client.dm
@@ -4,7 +4,42 @@
set instant = TRUE
set hidden = TRUE
+ client_keysend_amount += 1
+
+ var/cache = client_keysend_amount
+
+ if(keysend_tripped && next_keysend_trip_reset <= world.time)
+ keysend_tripped = FALSE
+
+ if(next_keysend_reset <= world.time)
+ client_keysend_amount = 0
+ next_keysend_reset = world.time + (1 SECONDS)
+
+ //The "tripped" system is to confirm that flooding is still happening after one spike
+ //not entirely sure how byond commands interact in relation to lag
+ //don't want to kick people if a lag spike results in a huge flood of commands being sent
+ if(cache >= MAX_KEYPRESS_AUTOKICK)
+ if(!keysend_tripped)
+ keysend_tripped = TRUE
+ next_keysend_trip_reset = world.time + (2 SECONDS)
+ else
+ log_admin("Client [ckey] was just autokicked for flooding keysends; likely abuse but potentially lagspike.")
+ message_admins("Client [ckey] was just autokicked for flooding keysends; likely abuse but potentially lagspike.")
+ QDEL_IN(src, 1)
+ return
+
+ ///Check if the key is short enough to even be a real key
+ if(LAZYLEN(_key) > MAX_KEYPRESS_COMMANDLENGTH)
+ to_chat(src, "Invalid KeyDown detected! You have been disconnected from the server automatically.")
+ log_admin("Client [ckey] just attempted to send an invalid keypress. Keymessage was over [MAX_KEYPRESS_COMMANDLENGTH] characters, autokicking due to likely abuse.")
+ message_admins("Client [ckey] just attempted to send an invalid keypress. Keymessage was over [MAX_KEYPRESS_COMMANDLENGTH] characters, autokicking due to likely abuse.")
+ QDEL_IN(src, 1)
+ return
+ //offset by 1 because the buffer address is 0 indexed because the math was simpler
+ keys_held[current_key_address + 1] = _key
+ //the time a key was pressed isn't actually used anywhere (as of 2019-9-10) but this allows easier access usage/checking
keys_held[_key] = world.time
+ current_key_address = ((current_key_address + 1) % HELD_KEY_BUFFER_LENGTH)
var/movement = SSinput.movement_keys[_key]
if(!(next_move_dir_sub & movement) && !keys_held["Ctrl"])
next_move_dir_add |= movement
@@ -35,7 +70,11 @@
set instant = TRUE
set hidden = TRUE
- keys_held -= _key
+ //Can't just do a remove because it would alter the length of the rolling buffer, instead search for the key then null it out if it exists
+ for(var/i in 1 to HELD_KEY_BUFFER_LENGTH)
+ if(keys_held[i] == _key)
+ keys_held[i] = null
+ break
var/movement = SSinput.movement_keys[_key]
if(!(next_move_dir_add & movement))
next_move_dir_sub |= movement
diff --git a/code/modules/keybindings/setup.dm b/code/modules/keybindings/setup.dm
index 54df252f5d..8433c9bf5a 100644
--- a/code/modules/keybindings/setup.dm
+++ b/code/modules/keybindings/setup.dm
@@ -1,9 +1,14 @@
/client
- var/list/keys_held = list() // A list of any keys held currently
- // These next two vars are to apply movement for keypresses and releases made while move delayed.
- // Because discarding that input makes the game less responsive.
- var/next_move_dir_add // On next move, add this dir to the move that would otherwise be done
- var/next_move_dir_sub // On next move, subtract this dir from the move that would otherwise be done
+ /// A rolling buffer of any keys held currently
+ var/list/keys_held = list()
+ ///used to keep track of the current rolling buffer position
+ var/current_key_address = 0
+ /// These next two vars are to apply movement for keypresses and releases made while move delayed.
+ /// Because discarding that input makes the game less responsive.
+ /// On next move, add this dir to the move that would otherwise be done
+ var/next_move_dir_add
+ /// On next move, subtract this dir from the move that would otherwise be done
+ var/next_move_dir_sub
// Set a client's focus to an object and override these procs on that object to let it handle keypresses
@@ -31,6 +36,11 @@
/client/proc/set_macros()
set waitfor = FALSE
+ //Reset and populate the rolling buffer
+ keys_held.Cut()
+ for(var/i in 1 to HELD_KEY_BUFFER_LENGTH)
+ keys_held += null
+
erase_all_macros()
var/list/macro_sets = SSinput.macro_sets
diff --git a/code/modules/library/lib_codex_gigas.dm b/code/modules/library/lib_codex_gigas.dm
index d3d95db974..57bf37d528 100644
--- a/code/modules/library/lib_codex_gigas.dm
+++ b/code/modules/library/lib_codex_gigas.dm
@@ -50,8 +50,8 @@
if(U.job in list("Curator")) // the curator is both faster, and more accurate than normal crew members at research
speed = 100
correctness = 100
- correctness -= U.getBrainLoss() *0.5 //Brain damage makes researching hard.
- speed += U.getBrainLoss() * 3
+ correctness -= U.getOrganLoss(ORGAN_SLOT_BRAIN) *0.5 //Brain damage makes researching hard.
+ speed += U.getOrganLoss(ORGAN_SLOT_BRAIN) * 3
if(do_after(user, speed, 0, user))
var/usedName = devilName
if(!prob(correctness))
diff --git a/code/modules/library/lib_machines.dm b/code/modules/library/lib_machines.dm
index 699df5de12..ef58943731 100644
--- a/code/modules/library/lib_machines.dm
+++ b/code/modules/library/lib_machines.dm
@@ -343,8 +343,11 @@ GLOBAL_LIST(cachedbooks) // List of our cached book datums
return ..()
/obj/machinery/computer/libraryconsole/bookmanagement/emag_act(mob/user)
- if(density && !(obj_flags & EMAGGED))
- obj_flags |= EMAGGED
+ . = ..()
+ if(!density || obj_flags & EMAGGED)
+ return
+ obj_flags |= EMAGGED
+ return TRUE
/obj/machinery/computer/libraryconsole/bookmanagement/Topic(href, href_list)
if(..())
diff --git a/code/modules/mapping/mapping_helpers.dm b/code/modules/mapping/mapping_helpers.dm
index 7a875ccf9f..e2459d780a 100644
--- a/code/modules/mapping/mapping_helpers.dm
+++ b/code/modules/mapping/mapping_helpers.dm
@@ -213,4 +213,4 @@ INITIALIZE_IMMEDIATE(/obj/effect/mapping_helpers/no_lava)
if(!ispath(disease_type,/datum/disease))
CRASH("Wrong disease type passed in.")
var/datum/disease/D = new disease_type()
- return list(component_type,D)
\ No newline at end of file
+ return list(component_type,D)
diff --git a/code/modules/mining/abandoned_crates.dm b/code/modules/mining/abandoned_crates.dm
index 86499e694b..f98f0755c8 100644
--- a/code/modules/mining/abandoned_crates.dm
+++ b/code/modules/mining/abandoned_crates.dm
@@ -169,6 +169,7 @@
locked = FALSE
cut_overlays()
add_overlay("securecrateg")
+ tamperproof = 0 // set explosion chance to zero, so we dont accidently hit it with a multitool and instantly die
else if (input == null || sanitycheck == null || length(input) != codelen)
to_chat(user, "You leave the crate alone.")
else
@@ -213,9 +214,18 @@
return
return ..()
+/obj/structure/closet/secure/loot/dive_into(mob/living/user)
+ if(!locked)
+ return ..()
+ to_chat(user, "That seems like a stupid idea.")
+ return FALSE
+
/obj/structure/closet/crate/secure/loot/emag_act(mob/user)
- if(locked)
- boom(user)
+ . = SEND_SIGNAL(src, COMSIG_ATOM_EMAG_ACT)
+ if(!locked)
+ return
+ boom(user)
+ return TRUE
/obj/structure/closet/crate/secure/loot/togglelock(mob/user)
if(locked)
@@ -224,4 +234,6 @@
..()
/obj/structure/closet/crate/secure/loot/deconstruct(disassembled = TRUE)
+ if(!locked && disassembled)
+ return ..()
boom()
diff --git a/code/modules/mining/equipment/explorer_gear.dm b/code/modules/mining/equipment/explorer_gear.dm
index 2c35c3148f..23ec02976d 100644
--- a/code/modules/mining/equipment/explorer_gear.dm
+++ b/code/modules/mining/equipment/explorer_gear.dm
@@ -43,7 +43,7 @@
name = "explorer gas mask"
desc = "A military-grade gas mask that can be connected to an air supply."
icon_state = "gas_mining"
- visor_flags = BLOCK_GAS_SMOKE_EFFECT | MASKINTERNALS
+ visor_flags = BLOCK_GAS_SMOKE_EFFECT | ALLOWINTERNALS
visor_flags_inv = HIDEFACIALHAIR
visor_flags_cover = MASKCOVERSMOUTH
actions_types = list(/datum/action/item_action/adjust)
diff --git a/code/modules/mining/equipment/kinetic_crusher.dm b/code/modules/mining/equipment/kinetic_crusher.dm
index b4afaac17f..ab8aa3e2fd 100644
--- a/code/modules/mining/equipment/kinetic_crusher.dm
+++ b/code/modules/mining/equipment/kinetic_crusher.dm
@@ -1,50 +1,51 @@
/*********************Mining Hammer****************/
-/obj/item/twohanded/required/kinetic_crusher
+/obj/item/twohanded/kinetic_crusher
icon = 'icons/obj/mining.dmi'
- icon_state = "mining_hammer1"
- item_state = "mining_hammer1"
+ icon_state = "crusher"
+ item_state = "crusher0"
lefthand_file = 'icons/mob/inhands/weapons/hammers_lefthand.dmi'
righthand_file = 'icons/mob/inhands/weapons/hammers_righthand.dmi'
name = "proto-kinetic crusher"
desc = "An early design of the proto-kinetic accelerator, it is little more than an combination of various mining tools cobbled together, forming a high-tech club. \
While it is an effective mining tool, it did little to aid any but the most skilled and/or suicidal miners against local fauna."
- force = 20 //As much as a bone spear, but this is significantly more annoying to carry around due to requiring the use of both hands at all times
+ force = 0 //You can't hit stuff unless wielded
w_class = WEIGHT_CLASS_BULKY
slot_flags = ITEM_SLOT_BACK
- force_unwielded = 20 //It's never not wielded so these are the same
+ force_unwielded = 0
force_wielded = 20
throwforce = 5
throw_speed = 4
- light_range = 7
- light_power = 2
armour_penetration = 10
materials = list(MAT_METAL=1150, MAT_GLASS=2075)
hitsound = 'sound/weapons/bladeslice.ogg'
attack_verb = list("smashed", "crushed", "cleaved", "chopped", "pulped")
sharpness = IS_SHARP
+ actions_types = list(/datum/action/item_action/toggle_light)
var/list/trophies = list()
var/charged = TRUE
var/charge_time = 15
var/detonation_damage = 50
var/backstab_bonus = 30
+ var/light_on = FALSE
+ var/brightness_on = 7
-/obj/item/twohanded/required/kinetic_crusher/Initialize()
+/obj/item/twohanded/kinetic_crusher/Initialize()
. = ..()
AddComponent(/datum/component/butchering, 60, 110) //technically it's huge and bulky, but this provides an incentive to use it
-/obj/item/twohanded/required/kinetic_crusher/Destroy()
+/obj/item/twohanded/kinetic_crusher/Destroy()
QDEL_LIST(trophies)
return ..()
-/obj/item/twohanded/required/kinetic_crusher/examine(mob/living/user)
+/obj/item/twohanded/kinetic_crusher/examine(mob/living/user)
..()
- to_chat(user, "Mark a large creature with the destabilizing force, then hit them in melee to do [force + detonation_damage] damage.")
- to_chat(user, "Does [force + detonation_damage + backstab_bonus] damage if the target is backstabbed, instead of [force + detonation_damage].")
+ to_chat(user, "Mark a large creature with the destabilizing force, then hit them in melee to do [force_wielded + detonation_damage] damage.")
+ to_chat(user, "Does [force_wielded + detonation_damage + backstab_bonus] damage if the target is backstabbed, instead of [force_wielded + detonation_damage].")
for(var/t in trophies)
var/obj/item/crusher_trophy/T = t
to_chat(user, "It has \a [T] attached, which causes [T.effect_desc()].")
-/obj/item/twohanded/required/kinetic_crusher/attackby(obj/item/I, mob/living/user)
+/obj/item/twohanded/kinetic_crusher/attackby(obj/item/I, mob/living/user)
if(istype(I, /obj/item/crowbar))
if(LAZYLEN(trophies))
to_chat(user, "You remove [src]'s trophies.")
@@ -60,7 +61,11 @@
else
return ..()
-/obj/item/twohanded/required/kinetic_crusher/attack(mob/living/target, mob/living/carbon/user)
+/obj/item/twohanded/kinetic_crusher/attack(mob/living/target, mob/living/carbon/user)
+ if(!wielded)
+ to_chat(user, "[src] is too heavy to use with one hand. You fumble and drop everything.")
+ user.drop_all_held_items()
+ return
var/datum/status_effect/crusher_damage/C = target.has_status_effect(STATUS_EFFECT_CRUSHERDAMAGETRACKING)
var/target_health = target.health
..()
@@ -71,11 +76,13 @@
if(!QDELETED(C) && !QDELETED(target))
C.total_damage += target_health - target.health //we did some damage, but let's not assume how much we did
-/obj/item/twohanded/required/kinetic_crusher/afterattack(atom/target, mob/living/user, proximity_flag, clickparams)
+/obj/item/twohanded/kinetic_crusher/afterattack(atom/target, mob/living/user, proximity_flag, clickparams)
. = ..()
if(istype(target, /obj/item/crusher_trophy))
var/obj/item/crusher_trophy/T = target
T.add_to(src, user)
+ if(!wielded)
+ return
if(!proximity_flag && charged)//Mark a target, or mine a tile.
var/turf/proj_turf = user.loc
if(!isturf(proj_turf))
@@ -90,7 +97,7 @@
playsound(user, 'sound/weapons/plasma_cutter.ogg', 100, 1)
D.fire()
charged = FALSE
- icon_state = "mining_hammer1_uncharged"
+ update_icon()
addtimer(CALLBACK(src, .proc/Recharge), charge_time)
return
if(proximity_flag && isliving(target))
@@ -122,12 +129,37 @@
if(user && lavaland_equipment_pressure_check(get_turf(user))) //CIT CHANGE - makes sure below only happens in low pressure environments
user.adjustStaminaLoss(-30)//CIT CHANGE - makes crushers heal stamina
-/obj/item/twohanded/required/kinetic_crusher/proc/Recharge()
+/obj/item/twohanded/kinetic_crusher/proc/Recharge()
if(!charged)
charged = TRUE
- icon_state = "mining_hammer1"
+ update_icon()
playsound(src.loc, 'sound/weapons/kenetic_reload.ogg', 60, 1)
+/obj/item/twohanded/kinetic_crusher/ui_action_click(mob/user, actiontype)
+ light_on = !light_on
+ playsound(user, 'sound/weapons/empty.ogg', 100, TRUE)
+ update_brightness(user)
+ update_icon()
+
+/obj/item/twohanded/kinetic_crusher/proc/update_brightness(mob/user = null)
+ if(light_on)
+ set_light(brightness_on)
+ else
+ set_light(0)
+
+/obj/item/twohanded/kinetic_crusher/update_icon()
+ ..()
+ cut_overlays()
+ if(!charged)
+ add_overlay("[icon_state]_uncharged")
+ if(light_on)
+ add_overlay("[icon_state]_lit")
+ spawn(1)
+ for(var/X in actions)
+ var/datum/action/A = X
+ A.UpdateButtonIcon()
+ item_state = "crusher[wielded]"
+
//destablizing force
/obj/item/projectile/destabilizer
name = "destabilizing force"
@@ -138,7 +170,7 @@
flag = "bomb"
range = 6
log_override = TRUE
- var/obj/item/twohanded/required/kinetic_crusher/hammer_synced
+ var/obj/item/twohanded/kinetic_crusher/hammer_synced
/obj/item/projectile/destabilizer/Destroy()
hammer_synced = null
@@ -177,12 +209,12 @@
return "errors"
/obj/item/crusher_trophy/attackby(obj/item/A, mob/living/user)
- if(istype(A, /obj/item/twohanded/required/kinetic_crusher))
+ if(istype(A, /obj/item/twohanded/kinetic_crusher))
add_to(A, user)
else
..()
-/obj/item/crusher_trophy/proc/add_to(obj/item/twohanded/required/kinetic_crusher/H, mob/living/user)
+/obj/item/crusher_trophy/proc/add_to(obj/item/twohanded/kinetic_crusher/H, mob/living/user)
for(var/t in H.trophies)
var/obj/item/crusher_trophy/T = t
if(istype(T, denied_type) || istype(src, T.denied_type))
@@ -194,7 +226,7 @@
to_chat(user, "You attach [src] to [H].")
return TRUE
-/obj/item/crusher_trophy/proc/remove_from(obj/item/twohanded/required/kinetic_crusher/H, mob/living/user)
+/obj/item/crusher_trophy/proc/remove_from(obj/item/twohanded/kinetic_crusher/H, mob/living/user)
forceMove(get_turf(H))
H.trophies -= src
return TRUE
@@ -281,12 +313,12 @@
/obj/item/crusher_trophy/legion_skull/effect_desc()
return "a kinetic crusher to recharge [bonus_value*0.1] second\s faster"
-/obj/item/crusher_trophy/legion_skull/add_to(obj/item/twohanded/required/kinetic_crusher/H, mob/living/user)
+/obj/item/crusher_trophy/legion_skull/add_to(obj/item/twohanded/kinetic_crusher/H, mob/living/user)
. = ..()
if(.)
H.charge_time -= bonus_value
-/obj/item/crusher_trophy/legion_skull/remove_from(obj/item/twohanded/required/kinetic_crusher/H, mob/living/user)
+/obj/item/crusher_trophy/legion_skull/remove_from(obj/item/twohanded/kinetic_crusher/H, mob/living/user)
. = ..()
if(.)
H.charge_time += bonus_value
@@ -339,7 +371,7 @@
/obj/item/crusher_trophy/demon_claws/effect_desc()
return "melee hits to do [bonus_value * 0.2] more damage and heal you for [bonus_value * 0.1], with 5X effect on mark detonation"
-/obj/item/crusher_trophy/demon_claws/add_to(obj/item/twohanded/required/kinetic_crusher/H, mob/living/user)
+/obj/item/crusher_trophy/demon_claws/add_to(obj/item/twohanded/kinetic_crusher/H, mob/living/user)
. = ..()
if(.)
H.force += bonus_value * 0.2
@@ -347,7 +379,7 @@
H.force_wielded += bonus_value * 0.2
H.detonation_damage += bonus_value * 0.8
-/obj/item/crusher_trophy/demon_claws/remove_from(obj/item/twohanded/required/kinetic_crusher/H, mob/living/user)
+/obj/item/crusher_trophy/demon_claws/remove_from(obj/item/twohanded/kinetic_crusher/H, mob/living/user)
. = ..()
if(.)
H.force -= bonus_value * 0.2
diff --git a/code/modules/mining/equipment/mining_tools.dm b/code/modules/mining/equipment/mining_tools.dm
index 0d6c337444..e02f38b7e3 100644
--- a/code/modules/mining/equipment/mining_tools.dm
+++ b/code/modules/mining/equipment/mining_tools.dm
@@ -65,9 +65,12 @@
/obj/item/pickaxe/drill/cyborg
name = "cyborg mining drill"
desc = "An integrated electric mining drill."
- item_flags = NODROP
flags_1 = NONE
+/obj/item/pickaxe/drill/cyborg/Initialize()
+ . = ..()
+ ADD_TRAIT(src, TRAIT_NODROP, CYBORG_ITEM_TRAIT)
+
/obj/item/pickaxe/drill/diamonddrill
name = "diamond-tipped mining drill"
icon_state = "diamonddrill"
diff --git a/code/modules/mining/laborcamp/laborstacker.dm b/code/modules/mining/laborcamp/laborstacker.dm
index 5193545c4b..4e54c3e222 100644
--- a/code/modules/mining/laborcamp/laborstacker.dm
+++ b/code/modules/mining/laborcamp/laborstacker.dm
@@ -10,12 +10,10 @@ GLOBAL_LIST(labor_sheet_values)
density = FALSE
var/obj/machinery/mineral/stacking_machine/laborstacker/stacking_machine = null
var/machinedir = SOUTH
- var/obj/item/card/id/prisoner/inserted_id
var/obj/machinery/door/airlock/release_door
var/door_tag = "prisonshuttle"
var/obj/item/radio/Radio //needed to send messages to sec radio
-
/obj/machinery/mineral/labor_claim_console/Initialize()
. = ..()
Radio = new/obj/item/radio(src)
@@ -34,18 +32,6 @@ GLOBAL_LIST(labor_sheet_values)
/proc/cmp_sheet_list(list/a, list/b)
return a["value"] - b["value"]
-/obj/machinery/mineral/labor_claim_console/attackby(obj/item/I, mob/user, params)
- if(istype(I, /obj/item/card/id/prisoner))
- if(!inserted_id)
- if(!user.transferItemToLoc(I, src))
- return
- inserted_id = I
- to_chat(user, "You insert [I].")
- return
- else
- to_chat(user, "There's an ID inserted already.")
- return ..()
-
/obj/machinery/mineral/labor_claim_console/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \
datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state)
ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
@@ -58,14 +44,20 @@ GLOBAL_LIST(labor_sheet_values)
var/can_go_home = FALSE
data["emagged"] = (obj_flags & EMAGGED) ? 1 : 0
- if(inserted_id)
- data["id"] = inserted_id
- data["id_name"] = inserted_id.registered_name
- data["points"] = inserted_id.points
- data["goal"] = inserted_id.goal
- if(check_auth())
+ if(obj_flags & EMAGGED)
can_go_home = TRUE
+ data["status_info"] = "No Prisoner ID detected."
+ var/obj/item/card/id/I = user.get_idcard(TRUE)
+ if(istype(I, /obj/item/card/id/prisoner))
+ var/obj/item/card/id/prisoner/P = I
+ data["id_points"] = P.points
+ if(P.points >= P.goal)
+ can_go_home = TRUE
+ data["status_info"] = "Goal met!"
+ else
+ data["status_info"] = "You are [(P.goal - P.points)] points away."
+
if(stacking_machine)
data["unclaimed_points"] = stacking_machine.points
@@ -78,29 +70,19 @@ GLOBAL_LIST(labor_sheet_values)
if(..())
return
switch(action)
- if("handle_id")
- if(inserted_id)
- if(!usr.get_active_held_item())
- usr.put_in_hands(inserted_id)
- inserted_id = null
- else
- inserted_id.forceMove(get_turf(src))
- inserted_id = null
- else
- var/obj/item/I = usr.get_active_held_item()
- if(istype(I, /obj/item/card/id/prisoner))
- if(!usr.transferItemToLoc(I, src))
- return
- inserted_id = I
if("claim_points")
- inserted_id.points += stacking_machine.points
- stacking_machine.points = 0
- to_chat(usr, "Points transferred.")
+ var/mob/M = usr
+ var/obj/item/card/id/I = M.get_idcard(TRUE)
+ if(istype(I, /obj/item/card/id/prisoner))
+ var/obj/item/card/id/prisoner/P = I
+ P.points += stacking_machine.points
+ stacking_machine.points = 0
+ to_chat(usr, "Points transferred.")
+ else
+ to_chat(usr, "No valid id for point transfer detected.")
if("move_shuttle")
if(!alone_in_area(get_area(src), usr))
to_chat(usr, "Prisoners are only allowed to be released while alone.")
- else if(!check_auth())
- to_chat(usr, "Prisoners are only allowed to be released when they reach their point goal.")
else
switch(SSshuttle.moveShuttle("laborcamp", "laborcamp_home", TRUE))
if(1)
@@ -112,14 +94,9 @@ GLOBAL_LIST(labor_sheet_values)
else
if(!(obj_flags & EMAGGED))
Radio.set_frequency(FREQ_SECURITY)
- Radio.talk_into(src, "[inserted_id.registered_name] has returned to the station. Minerals and Prisoner ID card ready for retrieval.", FREQ_SECURITY, get_spans(), get_default_language())
+ Radio.talk_into(src, "A prisoner has returned to the station. Minerals and Prisoner ID card ready for retrieval.", FREQ_SECURITY)
to_chat(usr, "Shuttle received message and will be sent shortly.")
-/obj/machinery/mineral/labor_claim_console/proc/check_auth()
- if(obj_flags & EMAGGED)
- return 1 //Shuttle is emagged, let any ol' person through
- return (istype(inserted_id) && inserted_id.points >= inserted_id.goal) //Otherwise, only let them out if the prisoner's reached his quota.
-
/obj/machinery/mineral/labor_claim_console/proc/locate_stacking_machine()
stacking_machine = locate(/obj/machinery/mineral/stacking_machine, get_step(src, machinedir))
if(stacking_machine)
@@ -128,14 +105,15 @@ GLOBAL_LIST(labor_sheet_values)
qdel(src)
/obj/machinery/mineral/labor_claim_console/emag_act(mob/user)
- if(!(obj_flags & EMAGGED))
- obj_flags |= EMAGGED
- to_chat(user, "PZZTTPFFFT")
-
+ . = ..()
+ if(obj_flags & EMAGGED)
+ return
+ obj_flags |= EMAGGED
+ to_chat(user, "PZZTTPFFFT")
+ return TRUE
/**********************Prisoner Collection Unit**************************/
-
/obj/machinery/mineral/stacking_machine/laborstacker
force_connect = TRUE
var/points = 0 //The unclaimed value of ore stacked.
@@ -149,8 +127,9 @@ GLOBAL_LIST(labor_sheet_values)
var/obj/item/stack/sheet/inp = I
points += inp.point_value * inp.amount
return ..()
-
+
/**********************Point Lookup Console**************************/
+
/obj/machinery/mineral/labor_points_checker
name = "points checking console"
desc = "A console used by prisoners to check the progress on their quotas. Simply swipe a prisoner ID."
diff --git a/code/modules/mining/lavaland/necropolis_chests.dm b/code/modules/mining/lavaland/necropolis_chests.dm
index eaac1984e3..5990c70813 100644
--- a/code/modules/mining/lavaland/necropolis_chests.dm
+++ b/code/modules/mining/lavaland/necropolis_chests.dm
@@ -143,7 +143,7 @@
//Rod of Asclepius
/obj/item/rod_of_asclepius
- name = "Rod of Asclepius"
+ name = "\improper Rod of Asclepius"
desc = "A wooden rod about the size of your forearm with a snake carved around it, winding it's way up the sides of the rod. Something about it seems to inspire in you the responsibilty and duty to help others."
icon = 'icons/obj/lavaland/artefacts.dmi'
icon_state = "asclepius_dormant"
@@ -189,7 +189,8 @@
activated()
/obj/item/rod_of_asclepius/proc/activated()
- item_flags = NODROP | DROPDEL
+ item_flags = DROPDEL
+ ADD_TRAIT(src, TRAIT_NODROP, CURSED_ITEM_TRAIT)
desc = "A short wooden rod with a mystical snake inseparably gripping itself and the rod to your forearm. It flows with a healing energy that disperses amongst yourself and those around you. "
icon_state = "asclepius_active"
activated = TRUE
@@ -640,6 +641,8 @@
nemesis_factions = list("mining", "boss")
var/transform_cooldown
var/swiping = FALSE
+ total_mass = 2.75
+ total_mass_on = 5
/obj/item/melee/transforming/cleaving_saw/examine(mob/user)
..()
@@ -784,30 +787,30 @@
/obj/item/melee/ghost_sword/process()
ghost_check()
-/obj/item/melee/ghost_sword/proc/ghost_check()
- var/ghost_counter = 0
- var/turf/T = get_turf(src)
- var/list/contents = T.GetAllContents()
- var/mob/dead/observer/current_spirits = list()
- for(var/thing in contents)
- var/atom/A = thing
- A.transfer_observers_to(src)
-
- for(var/i in orbiters?.orbiters)
- if(!isobserver(i))
+/obj/item/melee/ghost_sword/proc/recursive_orbit_collect(atom/A, list/L)
+ for(var/i in A.orbiters?.orbiters)
+ if(!isobserver(i) || (i in L))
continue
+ L |= i
+ recursive_orbit_collect(i, L)
+
+/obj/item/melee/ghost_sword/proc/ghost_check()
+ var/list/mob/dead/observer/current_spirits = list()
+
+ recursive_orbit_collect(src, current_spirits)
+ recursive_orbit_collect(loc, current_spirits) //anything holding us
+
+ for(var/i in spirits - current_spirits)
var/mob/dead/observer/G = i
- ghost_counter++
- G.invisibility = 0
- current_spirits |= G
-
- for(var/mob/dead/observer/G in spirits - current_spirits)
G.invisibility = GLOB.observer_default_invisibility
-
+
+ for(var/i in current_spirits)
+ var/mob/dead/observer/G = i
+ G.invisibility = 0
+
spirits = current_spirits
-
- return ghost_counter
-
+ return length(spirits)
+
/obj/item/melee/ghost_sword/attack(mob/living/target, mob/living/carbon/human/user)
force = 0
var/ghost_counter = ghost_check()
@@ -1332,4 +1335,4 @@
if(2)
new /obj/item/wisp_lantern(src)
if(3)
- new /obj/item/prisoncube(src)
\ No newline at end of file
+ new /obj/item/prisoncube(src)
diff --git a/code/modules/mining/machine_redemption.dm b/code/modules/mining/machine_redemption.dm
index 4605f7d693..6c1a00b020 100644
--- a/code/modules/mining/machine_redemption.dm
+++ b/code/modules/mining/machine_redemption.dm
@@ -13,7 +13,6 @@
speed_process = TRUE
circuit = /obj/item/circuitboard/machine/ore_redemption
layer = BELOW_OBJ_LAYER
- var/obj/item/card/id/inserted_id
var/points = 0
var/ore_pickup_rate = 15
var/sheet_per_ore = 1
@@ -48,18 +47,23 @@
point_upgrade = point_upgrade_temp
sheet_per_ore = sheet_per_ore_temp
+/obj/machinery/mineral/ore_redemption/examine(mob/user)
+ . = ..()
+ if(in_range(user, src) || isobserver(user))
+ . += "The status display reads: Smelting [sheet_per_ore] sheet(s) per piece of ore. Ore pickup speed at [ore_pickup_rate]."
+
/obj/machinery/mineral/ore_redemption/proc/smelt_ore(obj/item/stack/ore/O)
var/datum/component/material_container/mat_container = materials.mat_container
if (!mat_container)
return
- if(istype(O, /obj/item/stack/ore/bluespace_crystal/refined))
+ if(O.refined_type == null)
return
ore_buffer -= O
if(O && O.refined_type)
- points += O.points * point_upgrade * O.amount
+ points += O.points * O.amount
var/material_amount = mat_container.get_item_material_amount(O)
@@ -72,11 +76,8 @@
else
var/mats = O.materials & mat_container.materials
var/amount = O.amount
- var/id = inserted_id && inserted_id.registered_name
- if (id)
- id = " (ID: [id])"
mat_container.insert_item(O, sheet_per_ore) //insert it
- materials.silo_log(src, "smelted", amount, "ores[id]", mats)
+ materials.silo_log(src, "smelted", amount, "ores", mats)
qdel(O)
/obj/machinery/mineral/ore_redemption/proc/can_smelt_alloy(datum/design/D)
@@ -168,15 +169,7 @@
return
if(!powered())
- return
- if(istype(W, /obj/item/card/id))
- var/obj/item/card/id/I = user.get_active_held_item()
- if(istype(I) && !istype(inserted_id))
- if(!user.transferItemToLoc(I, src))
- return
- inserted_id = I
- interact(user)
- return
+ return ..()
if(istype(W, /obj/item/disk/design_disk))
if(user.transferItemToLoc(W, src))
@@ -205,9 +198,6 @@
/obj/machinery/mineral/ore_redemption/ui_data(mob/user)
var/list/data = list()
data["unclaimedPoints"] = points
- if(inserted_id)
- data["hasID"] = TRUE
- data["claimedPoints"] = inserted_id.mining_points
data["materials"] = list()
var/datum/component/material_container/mat_container = materials.mat_container
@@ -245,32 +235,24 @@
return
var/datum/component/material_container/mat_container = materials.mat_container
switch(action)
- if("Eject")
- if(!inserted_id)
- return
- usr.put_in_hands(inserted_id)
- inserted_id = null
- return TRUE
- if("Insert")
- var/obj/item/card/id/I = usr.get_active_held_item()
- if(istype(I))
- if(!usr.transferItemToLoc(I,src))
- return
- inserted_id = I
- else
- to_chat(usr, "Not a valid ID!")
- return TRUE
if("Claim")
- if(inserted_id)
- inserted_id.mining_points += points
- points = 0
+ var/mob/M = usr
+ var/obj/item/card/id/I = M.get_idcard(TRUE)
+ if(points)
+ if(I)
+ I.mining_points += points
+ points = 0
+ else
+ to_chat(usr, "No ID detected.")
+ else
+ to_chat(usr, "No points to claim.")
return TRUE
if("Release")
if(!mat_container)
return
if(materials.on_hold())
to_chat(usr, "Mineral access is on hold, please contact the quartermaster.")
- else if(!check_access(inserted_id) && !allowed(usr)) //Check the ID inside, otherwise check the user
+ else if(!allowed(usr)) //Check the ID inside, otherwise check the user
to_chat(usr, "Required access not found.")
else
var/mat_id = params["id"]
@@ -293,6 +275,7 @@
var/list/mats = list()
mats[mat_id] = MINERAL_MATERIAL_AMOUNT
materials.silo_log(src, "released", -count, "sheets", mats)
+ //Logging deleted for quick coding
return TRUE
if("diskInsert")
var/obj/item/disk/design_disk/disk = usr.get_active_held_item()
@@ -321,7 +304,7 @@
return
var/alloy_id = params["id"]
var/datum/design/alloy = stored_research.isDesignResearchedID(alloy_id)
- if((check_access(inserted_id) || allowed(usr)) && alloy)
+ if((check_access(inserted_scan_id) || allowed(usr)) && alloy)
var/smelt_amount = can_smelt_alloy(alloy)
var/desired = 0
if (params["sheets"])
diff --git a/code/modules/mining/machine_vending.dm b/code/modules/mining/machine_vending.dm
index aed90cebdf..5a48538161 100644
--- a/code/modules/mining/machine_vending.dm
+++ b/code/modules/mining/machine_vending.dm
@@ -20,6 +20,7 @@
new /datum/data/mining_equipment("Soap", /obj/item/soap/nanotrasen, 200),
new /datum/data/mining_equipment("Laser Pointer", /obj/item/laser_pointer, 300),
new /datum/data/mining_equipment("Alien Toy", /obj/item/clothing/mask/facehugger/toy, 300),
+ new /datum/data/mining_equipment("Stabilizing Serum", /obj/item/hivelordstabilizer, 400),
new /datum/data/mining_equipment("Fulton Beacon", /obj/item/fulton_core, 400),
new /datum/data/mining_equipment("Shelter Capsule", /obj/item/survivalcapsule, 400),
new /datum/data/mining_equipment("Survival Knife", /obj/item/kitchen/knife/combat/survival, 450),
@@ -28,11 +29,10 @@
new /datum/data/mining_equipment("Larger Ore Bag", /obj/item/storage/bag/ore/large, 500),
new /datum/data/mining_equipment("500 Point Transfer Card", /obj/item/card/mining_point_card/mp500, 500),
new /datum/data/mining_equipment("Tracking Implant Kit", /obj/item/storage/box/minertracker, 600),
- new /datum/data/mining_equipment("Survival Medipen", /obj/item/reagent_containers/hypospray/medipen/survival, 750),
- new /datum/data/mining_equipment("Stabilizing Serum", /obj/item/hivelordstabilizer, 750),
new /datum/data/mining_equipment("Jaunter", /obj/item/wormhole_jaunter, 750),
- new /datum/data/mining_equipment("Kinetic Crusher", /obj/item/twohanded/required/kinetic_crusher, 750),
+ new /datum/data/mining_equipment("Kinetic Crusher", /obj/item/twohanded/kinetic_crusher, 750),
new /datum/data/mining_equipment("Kinetic Accelerator", /obj/item/gun/energy/kinetic_accelerator, 750),
+ new /datum/data/mining_equipment("Survival Medipen", /obj/item/reagent_containers/hypospray/medipen/survival, 750),
new /datum/data/mining_equipment("Brute First-Aid Kit", /obj/item/storage/firstaid/brute, 800),
new /datum/data/mining_equipment("Burn First-Aid Kit", /obj/item/storage/firstaid/fire, 800),
new /datum/data/mining_equipment("First-Aid Kit", /obj/item/storage/firstaid/regular, 800),
@@ -54,7 +54,6 @@
new /datum/data/mining_equipment("Super Resonator", /obj/item/resonator/upgraded, 2500),
new /datum/data/mining_equipment("Jump Boots", /obj/item/clothing/shoes/bhop, 2500),
new /datum/data/mining_equipment("Luxury Shelter Capsule", /obj/item/survivalcapsule/luxury, 3000),
- new /datum/data/mining_equipment("Miner Full Replacement", /obj/item/storage/backpack/duffelbag/mining_cloned, 3000),
new /datum/data/mining_equipment("Nanotrasen Minebot", /mob/living/simple_animal/hostile/mining_drone, 800),
new /datum/data/mining_equipment("Minebot Melee Upgrade", /obj/item/mine_bot_upgrade, 400),
new /datum/data/mining_equipment("Minebot Armor Upgrade", /obj/item/mine_bot_upgrade/health, 400),
@@ -69,8 +68,8 @@
new /datum/data/mining_equipment("KA Damage Increase", /obj/item/borg/upgrade/modkit/damage, 1000),
new /datum/data/mining_equipment("KA Cooldown Decrease", /obj/item/borg/upgrade/modkit/cooldown, 1000),
new /datum/data/mining_equipment("KA AoE Damage", /obj/item/borg/upgrade/modkit/aoe/mobs, 2000),
+ new /datum/data/mining_equipment("Miner Full Replacement", /obj/item/storage/backpack/duffelbag/mining_cloned, 3000),
new /datum/data/mining_equipment("Premium Accelerator", /obj/item/gun/energy/kinetic_accelerator/premiumka, 8000)
-
)
/datum/data/mining_equipment
@@ -95,60 +94,42 @@
/obj/machinery/mineral/equipment_vendor/ui_interact(mob/user)
. = ..()
- var/dat
- dat +="
"
- if(istype(inserted_id))
- dat += "You have [inserted_id.mining_points] mining points collected. Eject ID. "
- else
- dat += "No ID inserted. Insert ID. "
- dat += "
"
+ var/list/dat = list()
dat += " Equipment point cost list:
"
for(var/datum/data/mining_equipment/prize in prize_list)
dat += "
"
- var/list/categorizedJobs = list(
- "Command" = list(jobs = list(), titles = GLOB.command_positions, color = "#aac1ee"),
- "Engineering" = list(jobs = list(), titles = GLOB.engineering_positions, color = "#ffd699"),
- "Supply" = list(jobs = list(), titles = GLOB.supply_positions, color = "#ead4ae"),
- "Miscellaneous" = list(jobs = list(), titles = list(), color = "#ffffff", colBreak = TRUE),
- "Ghost Role" = list(jobs = list(), titles = GLOB.mob_spawners, color = "#ffffff"),
- "Synthetic" = list(jobs = list(), titles = GLOB.nonhuman_positions, color = "#ccffcc"),
- "Service" = list(jobs = list(), titles = GLOB.civilian_positions, color = "#cccccc"),
- "Medical" = list(jobs = list(), titles = GLOB.medical_positions, color = "#99ffe6", colBreak = TRUE),
- "Science" = list(jobs = list(), titles = GLOB.science_positions, color = "#e6b3e6"),
- "Security" = list(jobs = list(), titles = GLOB.security_positions, color = "#ff9999"),
- )
+ var/list/categorizedJobs = list("Ghost Role" = list(jobs = list(), titles = GLOB.mob_spawners, color = "#ffffff"))
for(var/spawner in GLOB.mob_spawners)
if(!LAZYLEN(spawner))
continue
var/obj/effect/mob_spawn/S = pick(GLOB.mob_spawners[spawner])
if(!istype(S) || !S.can_latejoin())
continue
- categorizedJobs["Ghost Role"]["jobs"] += S
+ categorizedJobs["Ghost Role"]["jobs"] += spawner
- for(var/datum/job/job in SSjob.occupations)
- if(job && IsJobUnavailable(job.title, TRUE) == JOB_AVAILABLE)
- var/categorized = FALSE
- for(var/jobcat in categorizedJobs)
- var/list/jobs = categorizedJobs[jobcat]["jobs"]
- if(job.title in categorizedJobs[jobcat]["titles"])
- categorized = TRUE
- if(jobcat == "Command")
-
- if(job.title == "Captain") // Put captain at top of command jobs
- jobs.Insert(1, job)
- else
- jobs += job
- else // Put heads at top of non-command jobs
- if(job.title in GLOB.command_positions)
- jobs.Insert(1, job)
- else
- jobs += job
- if(!categorized)
- categorizedJobs["Miscellaneous"]["jobs"] += job
-
-
- dat += "
"
+ dat += "
"
for(var/jobcat in categorizedJobs)
- if(categorizedJobs[jobcat]["colBreak"])
- dat += "
"
if(!length(categorizedJobs[jobcat]["jobs"]))
continue
var/color = categorizedJobs[jobcat]["color"]
dat += "
"
dat += ""
- // Removing the old window method but leaving it here for reference
- //src << browse(dat, "window=latechoices;size=300x640;can_close=1")
-
- // Added the new browser window method
- var/datum/browser/popup = new(src, "latechoices", "Choose Profession", 680, 580)
+ var/datum/browser/popup = new(src, "latechoices", "Choose Profession", 720, 600)
popup.add_stylesheet("playeroptions", 'html/browser/playeroptions.css')
- popup.set_content(dat)
+ popup.set_content(jointext(dat, ""))
popup.open(FALSE) // FALSE is passed to open so that it doesn't use the onclose() proc
-
/mob/dead/new_player/proc/create_character(transfer_after)
spawning = 1
close_spawn_windows()
@@ -590,3 +603,31 @@
src << browse(null, "window=preferences") //closes job selection
src << browse(null, "window=mob_occupation")
src << browse(null, "window=latechoices") //closes late job selection
+
+/* Used to make sure that a player has a valid job preference setup, used to knock players out of eligibility for anything if their prefs don't make sense.
+ A "valid job preference setup" in this situation means at least having one job set to low, or not having "return to lobby" enabled
+ Prevents "antag rolling" by setting antag prefs on, all jobs to never, and "return to lobby if preferences not availible"
+ Doing so would previously allow you to roll for antag, then send you back to lobby if you didn't get an antag role
+ This also does some admin notification and logging as well, as well as some extra logic to make sure things don't go wrong
+*/
+
+/mob/dead/new_player/proc/check_preferences()
+ if(!client)
+ return FALSE //Not sure how this would get run without the mob having a client, but let's just be safe.
+ if(client.prefs.joblessrole != RETURNTOLOBBY)
+ return TRUE
+ // If they have antags enabled, they're potentially doing this on purpose instead of by accident. Notify admins if so.
+ var/has_antags = FALSE
+ if(client.prefs.be_special.len > 0)
+ has_antags = TRUE
+ if(client.prefs.job_preferences.len == 0)
+ if(!ineligible_for_roles)
+ to_chat(src, "You have no jobs enabled, along with return to lobby if job is unavailable. This makes you ineligible for any round start role, please update your job preferences.")
+ ineligible_for_roles = TRUE
+ ready = PLAYER_NOT_READY
+ if(has_antags)
+ log_admin("[src.ckey] just got booted back to lobby with no jobs, but antags enabled.")
+ message_admins("[src.ckey] just got booted back to lobby with no jobs enabled, but antag rolling enabled. Likely antag rolling abuse.")
+
+ return FALSE //This is the only case someone should actually be completely blocked from antag rolling as well
+ return TRUE
diff --git a/code/modules/mob/dead/new_player/preferences_setup.dm b/code/modules/mob/dead/new_player/preferences_setup.dm
index 353df3aa66..994d082585 100644
--- a/code/modules/mob/dead/new_player/preferences_setup.dm
+++ b/code/modules/mob/dead/new_player/preferences_setup.dm
@@ -6,8 +6,11 @@
else
gender = pick(MALE,FEMALE)
underwear = random_underwear(gender)
+ undie_color = random_short_color()
undershirt = random_undershirt(gender)
+ shirt_color = random_short_color()
socks = random_socks()
+ socks_color = random_short_color()
skin_tone = random_skin_tone()
hair_style = random_hair_style(gender)
facial_hair_style = random_facial_hair_style(gender)
@@ -21,50 +24,35 @@
age = rand(AGE_MIN,AGE_MAX)
/datum/preferences/proc/update_preview_icon()
- // Silicons only need a very basic preview since there is no customization for them.
-// var/wide_icon = FALSE //CITDEL THINGS
-// if(features["taur"] != "None")
-// wide_icon = TRUE
- if(job_engsec_high)
- switch(job_engsec_high)
- if(AI_JF)
- parent.show_character_previews(image('icons/mob/ai.dmi', resolve_ai_icon(preferred_ai_core_display), dir = SOUTH))
- return
- if(CYBORG)
- parent.show_character_previews(image('icons/mob/robots.dmi', icon_state = "robot", dir = SOUTH))
- return
+ // Determine what job is marked as 'High' priority, and dress them up as such.
+ var/datum/job/previewJob
+ var/highest_pref = 0
+ for(var/job in job_preferences)
+ if(job_preferences["[job]"] > highest_pref)
+ previewJob = SSjob.GetJob(job)
+ highest_pref = job_preferences["[job]"]
+
+ if(previewJob)
+ // Silicons only need a very basic preview since there is no customization for them.
+ if(istype(previewJob,/datum/job/ai))
+ parent.show_character_previews(image('icons/mob/ai.dmi', icon_state = resolve_ai_icon(preferred_ai_core_display), dir = SOUTH))
+ return
+ if(istype(previewJob,/datum/job/cyborg))
+ parent.show_character_previews(image('icons/mob/robots.dmi', icon_state = "robot", dir = SOUTH))
+ return
// Set up the dummy for its photoshoot
var/mob/living/carbon/human/dummy/mannequin = generate_or_wait_for_human_dummy(DUMMY_HUMAN_SLOT_PREFERENCES)
mannequin.cut_overlays()
+ // Apply the Dummy's preview background first so we properly layer everything else on top of it.
mannequin.add_overlay(mutable_appearance('modular_citadel/icons/ui/backgrounds.dmi', bgstate, layer = SPACE_LAYER))
copy_to(mannequin)
- // Determine what job is marked as 'High' priority, and dress them up as such.
- var/datum/job/previewJob
- var/highRankFlag = job_civilian_high | job_medsci_high | job_engsec_high
-
- if(job_civilian_low & ASSISTANT)
- previewJob = SSjob.GetJob("Assistant")
- else if(highRankFlag)
- var/highDeptFlag
- if(job_civilian_high)
- highDeptFlag = CIVILIAN
- else if(job_medsci_high)
- highDeptFlag = MEDSCI
- else if(job_engsec_high)
- highDeptFlag = ENGSEC
-
- for(var/datum/job/job in SSjob.occupations)
- if(job.flag == highRankFlag && job.department_flag == highDeptFlag)
- previewJob = job
- break
-
if(previewJob)
- if(current_tab != 2)
- mannequin.job = previewJob.title
- previewJob.equip(mannequin, TRUE)
+ mannequin.job = previewJob.title
+ previewJob.equip(mannequin, TRUE, preference_source = parent)
COMPILE_OVERLAYS(mannequin)
parent.show_character_previews(new /mutable_appearance(mannequin))
unset_busy_human_dummy(DUMMY_HUMAN_SLOT_PREFERENCES)
+
diff --git a/code/modules/mob/dead/new_player/sprite_accessories/Citadel_Snowflake.dm b/code/modules/mob/dead/new_player/sprite_accessories/Citadel_Snowflake.dm
new file mode 100644
index 0000000000..020776a75f
--- /dev/null
+++ b/code/modules/mob/dead/new_player/sprite_accessories/Citadel_Snowflake.dm
@@ -0,0 +1,53 @@
+/datum/sprite_accessory/mam_tails/shark/datashark
+ name = "DataShark"
+ icon_state = "datashark"
+ ckeys_allowed = list("rubyflamewing")
+
+/datum/sprite_accessory/mam_tails_animated/shark/datashark
+ name = "DataShark"
+ icon_state = "datashark"
+ ckeys_allowed = list("rubyflamewing")
+
+/datum/sprite_accessory/mam_body_markings/shark/datashark
+ name = "DataShark"
+ icon_state = "datashark"
+ ckeys_allowed = list("rubyflamewing")
+
+//Sabresune
+/datum/sprite_accessory/mam_ears/sabresune
+ name = "Sabresune"
+ icon_state = "sabresune"
+ ckeys_allowed = list("poojawa")
+ extra = TRUE
+ extra_color_src = MUTCOLORS3
+
+/datum/sprite_accessory/mam_tails/sabresune
+ name = "Sabresune"
+ icon_state = "sabresune"
+ ckeys_allowed = list("poojawa")
+
+/datum/sprite_accessory/mam_tails_animated/sabresune
+ name = "Sabresune"
+ icon_state = "sabresune"
+ ckeys_allowed = list("poojawa")
+
+/datum/sprite_accessory/mam_body_markings/sabresune
+ name = "Sabresune"
+ icon_state = "sabresune"
+ ckeys_allowed = list("poojawa")
+
+//Lunasune
+/datum/sprite_accessory/mam_ears/lunasune
+ name = "lunasune"
+ icon_state = "lunasune"
+ ckeys_allowed = list("invader4352")
+
+/datum/sprite_accessory/mam_tails/lunasune
+ name = "lunasune"
+ icon_state = "lunasune"
+ ckeys_allowed = list("invader4352")
+
+/datum/sprite_accessory/mam_tails_animated/lunasune
+ name = "lunasune"
+ icon_state = "lunasune"
+ ckeys_allowed = list("invader4352")
diff --git a/code/modules/mob/dead/new_player/sprite_accessories/_sprite_accessories.dm b/code/modules/mob/dead/new_player/sprite_accessories/_sprite_accessories.dm
index 699c3c97e4..dd66f68e5d 100644
--- a/code/modules/mob/dead/new_player/sprite_accessories/_sprite_accessories.dm
+++ b/code/modules/mob/dead/new_player/sprite_accessories/_sprite_accessories.dm
@@ -59,4 +59,19 @@
var/locked = FALSE //Is this part locked from roundstart selection? Used for parts that apply effects
var/dimension_x = 32
var/dimension_y = 32
- var/center = FALSE //Should we center the sprite?
\ No newline at end of file
+ var/center = FALSE //Should we center the sprite?
+
+ //Special / holdover traits for Citadel specific sprites.
+ var/extra = FALSE
+ var/extra_color_src = MUTCOLORS2 //The color source for the extra overlay.
+ var/extra_icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+ var/extra2 = FALSE
+ var/extra2_color_src = MUTCOLORS3
+ var/extra2_icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+
+ //for snowflake/donor specific sprites
+ var/list/ckeys_allowed
+
+/datum/sprite_accessory/underwear
+ icon = 'icons/mob/underwear.dmi'
+ var/has_color = FALSE
\ No newline at end of file
diff --git a/code/modules/mob/dead/new_player/sprite_accessories/alienpeople.dm b/code/modules/mob/dead/new_player/sprite_accessories/alienpeople.dm
new file mode 100644
index 0000000000..6c0659f851
--- /dev/null
+++ b/code/modules/mob/dead/new_player/sprite_accessories/alienpeople.dm
@@ -0,0 +1,53 @@
+
+/******************************************
+*********** Xeno Dorsal Tubes *************
+*******************************************/
+/datum/sprite_accessory/xeno_dorsal
+ icon = 'modular_citadel/icons/mob/xeno_parts_greyscale.dmi'
+
+/datum/sprite_accessory/xeno_dorsal/standard
+ name = "Standard"
+ icon_state = "standard"
+
+/datum/sprite_accessory/xeno_dorsal/royal
+ name = "Royal"
+ icon_state = "royal"
+
+/datum/sprite_accessory/xeno_dorsal/down
+ name = "Dorsal Down"
+ icon_state = "down"
+
+/******************************************
+************* Xeno Tails ******************
+*******************************************/
+/datum/sprite_accessory/xeno_tail
+ icon = 'modular_citadel/icons/mob/xeno_parts_greyscale.dmi'
+
+/datum/sprite_accessory/xeno_tail/none
+ name = "None"
+
+/datum/sprite_accessory/xeno_tail/standard
+ name = "Xenomorph Tail"
+ icon_state = "xeno"
+
+/******************************************
+************* Xeno Heads ******************
+*******************************************/
+/datum/sprite_accessory/xeno_head
+ icon = 'modular_citadel/icons/mob/xeno_parts_greyscale.dmi'
+
+/datum/sprite_accessory/xeno_head/standard
+ name = "Standard"
+ icon_state = "standard"
+
+/datum/sprite_accessory/xeno_head/royal
+ name = "royal"
+ icon_state = "royal"
+
+/datum/sprite_accessory/xeno_head/hollywood
+ name = "hollywood"
+ icon_state = "hollywood"
+
+/datum/sprite_accessory/xeno_head/warrior
+ name = "warrior"
+ icon_state = "warrior"
diff --git a/code/modules/mob/dead/new_player/sprite_accessories/body_markings.dm b/code/modules/mob/dead/new_player/sprite_accessories/body_markings.dm
index 6bce18d7ce..2f1d48cfa7 100644
--- a/code/modules/mob/dead/new_player/sprite_accessories/body_markings.dm
+++ b/code/modules/mob/dead/new_player/sprite_accessories/body_markings.dm
@@ -1,6 +1,6 @@
-//////////.//////////////////
-// MutantParts Definitions //
-/////////////////////////////
+/******************************************
+************* Lizard Markings *************
+*******************************************/
/datum/sprite_accessory/body_markings
icon = 'icons/mob/mutant_bodyparts.dmi'
@@ -22,4 +22,271 @@
/datum/sprite_accessory/body_markings/lbelly
name = "Light Belly"
icon_state = "lbelly"
- gender_specific = 1
\ No newline at end of file
+ gender_specific = 1
+
+/******************************************
+************ Furry Markings ***************
+*******************************************/
+
+// These are all color matrixed and applied per-limb by default. you MUST comply with this if you want to have your markings work --Pooj
+// use the HumanScissors tool to break your sprite up into the zones easier.
+// Although Byond supposedly doesn't have an icon limit anymore of 512 states after 512.1478, just be careful about too many additions.
+
+/datum/sprite_accessory/mam_body_markings
+ extra = FALSE
+ extra2 = FALSE
+ color_src = MATRIXED
+ gender_specific = 0
+ icon = 'modular_citadel/icons/mob/mam_markings.dmi'
+
+/datum/sprite_accessory/mam_body_markings/none
+ name = "None"
+ icon_state = "none"
+ ckeys_allowed = list("yousshouldnteverbeseeingthisyoumeme")
+ icon = 'modular_citadel/icons/mob/markings_notmammals.dmi'
+
+/datum/sprite_accessory/mam_body_markings/plain
+ name = "Plain"
+ icon_state = "plain"
+ icon = 'modular_citadel/icons/mob/markings_notmammals.dmi'
+
+/datum/sprite_accessory/mam_body_markings/redpanda
+ name = "Redpanda"
+ icon_state = "redpanda"
+
+/datum/sprite_accessory/mam_body_markings/bee
+ name = "Bee"
+ icon_state = "bee"
+ icon = 'modular_citadel/icons/mob/markings_notmammals.dmi'
+
+/datum/sprite_accessory/mam_body_markings/belly
+ name = "Belly"
+ icon_state = "belly"
+ icon = 'modular_citadel/icons/mob/markings_notmammals.dmi'
+
+/datum/sprite_accessory/mam_body_markings/bellyslim
+ name = "Bellyslim"
+ icon_state = "bellyslim"
+ icon = 'modular_citadel/icons/mob/markings_notmammals.dmi'
+
+/datum/sprite_accessory/mam_body_markings/corgi
+ name = "Corgi"
+ icon_state = "corgi"
+
+/datum/sprite_accessory/mam_body_markings/cow
+ name = "Bovine"
+ icon_state = "bovine"
+
+/datum/sprite_accessory/mam_body_markings/corvid
+ name = "Corvid"
+ icon_state = "corvid"
+
+/datum/sprite_accessory/mam_body_markings/dalmation
+ name = "Dalmation"
+ icon_state = "dalmation"
+
+/datum/sprite_accessory/mam_body_markings/deer
+ name = "Deer"
+ icon_state = "deer"
+
+/datum/sprite_accessory/mam_body_markings/dog
+ name = "Dog"
+ icon_state = "dog"
+
+/datum/sprite_accessory/mam_body_markings/eevee
+ name = "Eevee"
+ icon_state = "eevee"
+
+/datum/sprite_accessory/mam_body_markings/fennec
+ name = "Fennec"
+ icon_state = "Fennec"
+
+/datum/sprite_accessory/mam_body_markings/fox
+ name = "Fox"
+ icon_state = "fox"
+
+/datum/sprite_accessory/mam_body_markings/frog
+ name = "Frog"
+ icon_state = "frog"
+ icon = 'modular_citadel/icons/mob/markings_notmammals.dmi'
+
+/datum/sprite_accessory/mam_body_markings/goat
+ name = "Goat"
+ icon_state = "goat"
+
+/datum/sprite_accessory/mam_body_markings/handsfeet
+ name = "Handsfeet"
+ icon_state = "handsfeet"
+ icon = 'modular_citadel/icons/mob/markings_notmammals.dmi'
+
+/datum/sprite_accessory/mam_body_markings/hawk
+ name = "Hawk"
+ icon_state = "hawk"
+
+/datum/sprite_accessory/mam_body_markings/husky
+ name = "Husky"
+ icon_state = "husky"
+
+/datum/sprite_accessory/mam_body_markings/hyena
+ name = "Hyena"
+ icon_state = "hyena"
+
+/datum/sprite_accessory/mam_body_markings/lab
+ name = "Lab"
+ icon_state = "lab"
+
+/datum/sprite_accessory/mam_body_markings/insect
+ name = "Insect"
+ icon_state = "insect"
+ icon = 'modular_citadel/icons/mob/markings_notmammals.dmi'
+
+/datum/sprite_accessory/mam_body_markings/otie
+ name = "Otie"
+ icon_state = "otie"
+
+/datum/sprite_accessory/mam_body_markings/otter
+ name = "Otter"
+ icon_state = "otter"
+
+/datum/sprite_accessory/mam_body_markings/orca
+ name = "Orca"
+ icon_state = "orca"
+
+/datum/sprite_accessory/mam_body_markings/panther
+ name = "Panther"
+ icon_state = "panther"
+
+/datum/sprite_accessory/mam_body_markings/possum
+ name = "Possum"
+ icon_state = "possum"
+
+/datum/sprite_accessory/mam_body_markings/raccoon
+ name = "Raccoon"
+ icon_state = "raccoon"
+
+/datum/sprite_accessory/mam_body_markings/pede
+ name = "Scolipede"
+ icon_state = "scolipede"
+ icon = 'modular_citadel/icons/mob/markings_notmammals.dmi'
+
+/datum/sprite_accessory/mam_body_markings/shark
+ name = "Shark"
+ icon_state = "shark"
+
+/datum/sprite_accessory/mam_body_markings/skunk
+ name = "Skunk"
+ icon_state = "skunk"
+
+/datum/sprite_accessory/mam_body_markings/sergal
+ name = "Sergal"
+ icon_state = "sergal"
+
+/datum/sprite_accessory/mam_body_markings/shepherd
+ name = "Shepherd"
+ icon_state = "shepherd"
+
+/datum/sprite_accessory/mam_body_markings/tajaran
+ name = "Tajaran"
+ icon_state = "tajaran"
+
+/datum/sprite_accessory/mam_body_markings/tiger
+ name = "Tiger"
+ icon_state = "tiger"
+
+/datum/sprite_accessory/mam_body_markings/turian
+ name = "Turian"
+ icon_state = "turian"
+ icon = 'modular_citadel/icons/mob/markings_notmammals.dmi'
+
+/datum/sprite_accessory/mam_body_markings/wolf
+ name = "Wolf"
+ icon_state = "wolf"
+
+/datum/sprite_accessory/mam_body_markings/xeno
+ name = "Xeno"
+ icon_state = "xeno"
+ icon = 'modular_citadel/icons/mob/markings_notmammals.dmi'
+
+/******************************************
+************* Insect Markings *************
+*******************************************/
+
+/datum/sprite_accessory/insect_fluff
+ icon = 'icons/mob/wings.dmi'
+ color_src = 0
+
+/datum/sprite_accessory/insect_fluff/none
+ name = "None"
+ icon_state = "none"
+
+/datum/sprite_accessory/insect_fluff/plain
+ name = "Plain"
+ icon_state = "plain"
+
+/datum/sprite_accessory/insect_fluff/reddish
+ name = "Reddish"
+ icon_state = "redish"
+
+/datum/sprite_accessory/insect_fluff/royal
+ name = "Royal"
+ icon_state = "royal"
+
+/datum/sprite_accessory/insect_fluff/gothic
+ name = "Gothic"
+ icon_state = "gothic"
+
+/datum/sprite_accessory/insect_fluff/lovers
+ name = "Lovers"
+ icon_state = "lovers"
+
+/datum/sprite_accessory/insect_fluff/whitefly
+ name = "White Fly"
+ icon_state = "whitefly"
+
+/datum/sprite_accessory/insect_fluff/punished
+ name = "Burnt Off"
+ icon_state = "punished"
+
+/datum/sprite_accessory/insect_fluff/firewatch
+ name = "Firewatch"
+ icon_state = "firewatch"
+
+/datum/sprite_accessory/insect_fluff/deathhead
+ name = "Deathshead"
+ icon_state = "deathhead"
+
+/datum/sprite_accessory/insect_fluff/poison
+ name = "Poison"
+ icon_state = "poison"
+
+/datum/sprite_accessory/insect_fluff/ragged
+ name = "Ragged"
+ icon_state = "ragged"
+
+/datum/sprite_accessory/insect_fluff/moonfly
+ name = "Moon Fly"
+ icon_state = "moonfly"
+
+/datum/sprite_accessory/insect_fluff/snow
+ name = "Snow"
+ icon_state = "snow"
+
+/datum/sprite_accessory/insect_fluff/colored
+ name = "Colored (Hair)"
+ icon_state = "snow"
+ color_src = HAIR
+
+/datum/sprite_accessory/insect_fluff/colored1
+ name = "Colored (Primary)"
+ icon_state = "snow"
+ color_src = MUTCOLORS
+
+/datum/sprite_accessory/insect_fluff/colored2
+ name = "Colored (Secondary)"
+ icon_state = "snow"
+ color_src = MUTCOLORS2
+
+/datum/sprite_accessory/insect_fluff/colored3
+ name = "Colored (Tertiary)"
+ icon_state = "snow"
+ color_src = MUTCOLORS3
\ No newline at end of file
diff --git a/code/modules/mob/dead/new_player/sprite_accessories/ears.dm b/code/modules/mob/dead/new_player/sprite_accessories/ears.dm
index 163f8370a2..1496ca030a 100644
--- a/code/modules/mob/dead/new_player/sprite_accessories/ears.dm
+++ b/code/modules/mob/dead/new_player/sprite_accessories/ears.dm
@@ -5,8 +5,295 @@
name = "None"
icon_state = "none"
+/******************************************
+*************** Human Ears ****************
+*******************************************/
+
+/datum/sprite_accessory/ears/human/axolotl
+ name = "Axolotl"
+ icon_state = "axolotl"
+ icon = 'modular_citadel/icons/mob/mam_ears.dmi'
+
+/datum/sprite_accessory/ears/human/bear
+ name = "Bear"
+ icon_state = "bear"
+ icon = 'modular_citadel/icons/mob/mam_ears.dmi'
+ color_src = MATRIXED
+
+/datum/sprite_accessory/ears/human/bigwolf
+ name = "Big Wolf"
+ icon_state = "bigwolf"
+ icon = 'modular_citadel/icons/mob/mam_ears.dmi'
+ color_src = MATRIXED
+
+/datum/sprite_accessory/ears/human/bigwolfinner
+ name = "Big Wolf (ALT)"
+ icon_state = "bigwolfinner"
+ hasinner = 1
+ icon = 'modular_citadel/icons/mob/mam_ears.dmi'
+ color_src = MATRIXED
+
+/datum/sprite_accessory/ears/human/bigwolfdark
+ name = "Dark Big Wolf"
+ icon_state = "bigwolfdark"
+ icon = 'modular_citadel/icons/mob/mam_ears.dmi'
+ color_src = MATRIXED
+
+/datum/sprite_accessory/ears/human/bigwolfinnerdark
+ name = "Dark Big Wolf (ALT)"
+ icon_state = "bigwolfinnerdark"
+ hasinner = 1
+ icon = 'modular_citadel/icons/mob/mam_ears.dmi'
+ color_src = MATRIXED
+
/datum/sprite_accessory/ears/cat
name = "Cat"
icon_state = "cat"
hasinner = 1
- color_src = HAIR
\ No newline at end of file
+ color_src = HAIR
+
+/datum/sprite_accessory/ears/human/cow
+ name = "Cow"
+ icon_state = "cow"
+ icon = 'modular_citadel/icons/mob/mam_ears.dmi'
+ color_src = MATRIXED
+
+/datum/sprite_accessory/ears/human/curled
+ name = "Curled Horn"
+ icon_state = "horn1"
+ icon = 'modular_citadel/icons/mob/mam_ears.dmi'
+ color_src = MUTCOLORS3
+
+/datum/sprite_accessory/ears/human/eevee
+ name = "Eevee"
+ icon_state = "eevee"
+ icon = 'modular_citadel/icons/mob/mam_ears.dmi'
+ color_src = MATRIXED
+
+/datum/sprite_accessory/ears/human/elephant
+ name = "Elephant"
+ icon_state = "elephant"
+ icon = 'modular_citadel/icons/mob/mam_ears.dmi'
+ color_src = MATRIXED
+
+/datum/sprite_accessory/ears/human/elf
+ name = "Elf"
+ icon_state = "elf"
+ icon = 'modular_citadel/icons/mob/mam_ears.dmi'
+ color_src = SKINTONE
+
+/datum/sprite_accessory/ears/fennec
+ name = "Fennec"
+ icon_state = "fennec"
+ icon = 'modular_citadel/icons/mob/mam_ears.dmi'
+ color_src = MATRIXED
+
+/datum/sprite_accessory/ears/fish
+ name = "Fish"
+ icon_state = "fish"
+ icon = 'modular_citadel/icons/mob/mam_ears.dmi'
+ color_src = MATRIXED
+
+/datum/sprite_accessory/ears/fox
+ name = "Fox"
+ icon_state = "fox"
+ color_src = MATRIXED
+ icon = 'modular_citadel/icons/mob/mam_ears.dmi'
+
+/datum/sprite_accessory/ears/human/jellyfish
+ name = "Jellyfish"
+ icon_state = "jellyfish"
+ color_src = HAIR
+
+/datum/sprite_accessory/ears/lab
+ name = "Dog, Floppy"
+ icon_state = "lab"
+ color_src = MATRIXED
+ icon = 'modular_citadel/icons/mob/mam_ears.dmi'
+
+/datum/sprite_accessory/ears/murid
+ name = "Murid"
+ icon_state = "murid"
+ icon = 'modular_citadel/icons/mob/mam_ears.dmi'
+ color_src = MATRIXED
+
+/datum/sprite_accessory/ears/human/otie
+ name = "Otusian"
+ icon_state = "otie"
+ color_src = MATRIXED
+ icon = 'modular_citadel/icons/mob/mam_ears.dmi'
+
+/datum/sprite_accessory/ears/human/pede
+ name = "Scolipede"
+ icon_state = "pede"
+ icon = 'modular_citadel/icons/mob/mam_ears.dmi'
+ color_src = MATRIXED
+
+/datum/sprite_accessory/ears/human/rabbit
+ name = "Rabbit"
+ icon_state = "rabbit"
+ color_src = MATRIXED
+ icon = 'modular_citadel/icons/mob/mam_ears.dmi'
+
+/datum/sprite_accessory/ears/human/sergal
+ name = "Sergal"
+ icon_state = "sergal"
+ icon = 'modular_citadel/icons/mob/mam_ears.dmi'
+ color_src = MATRIXED
+
+/datum/sprite_accessory/ears/human/skunk
+ name = "skunk"
+ icon_state = "skunk"
+ icon = 'modular_citadel/icons/mob/mam_ears.dmi'
+ color_src = MATRIXED
+
+/datum/sprite_accessory/ears/squirrel
+ name = "Squirrel"
+ icon_state = "squirrel"
+ icon = 'modular_citadel/icons/mob/mam_ears.dmi'
+ color_src = MATRIXED
+
+/datum/sprite_accessory/ears/wolf
+ name = "Wolf"
+ icon_state = "wolf"
+ color_src = MATRIXED
+ icon = 'modular_citadel/icons/mob/mam_ears.dmi'
+
+
+/******************************************
+*************** Furry Ears ****************
+*******************************************/
+
+/datum/sprite_accessory/mam_ears
+ icon = 'modular_citadel/icons/mob/mam_ears.dmi'
+ color_src = MATRIXED
+
+/datum/sprite_accessory/mam_ears/none
+ name = "None"
+ icon_state = "none"
+
+/datum/sprite_accessory/mam_ears/axolotl
+ name = "Axolotl"
+ icon_state = "axolotl"
+
+/datum/sprite_accessory/mam_ears/bear
+ name = "Bear"
+ icon_state = "bear"
+
+/datum/sprite_accessory/mam_ears/bigwolf
+ name = "Big Wolf"
+ icon_state = "bigwolf"
+
+/datum/sprite_accessory/mam_ears/bigwolfinner
+ name = "Big Wolf (ALT)"
+ icon_state = "bigwolfinner"
+ hasinner = 1
+
+/datum/sprite_accessory/mam_ears/bigwolfdark
+ name = "Dark Big Wolf"
+ icon_state = "bigwolfdark"
+
+/datum/sprite_accessory/mam_ears/bigwolfinnerdark
+ name = "Dark Big Wolf (ALT)"
+ icon_state = "bigwolfinnerdark"
+ hasinner = 1
+
+/datum/sprite_accessory/mam_ears/cat
+ name = "Cat"
+ icon_state = "cat"
+ hasinner = 1
+ color_src = HAIR
+
+/datum/sprite_accessory/mam_ears/catbig
+ name = "Cat, Big"
+ icon_state = "catbig"
+
+/datum/sprite_accessory/mam_ears/cow
+ name = "Cow"
+ icon_state = "cow"
+
+/datum/sprite_accessory/mam_ears/curled
+ name = "Curled Horn"
+ icon_state = "horn1"
+ color_src = MUTCOLORS3
+
+/datum/sprite_accessory/mam_ears/deer
+ name = "Deer"
+ icon_state = "deer"
+ color_src = MUTCOLORS3
+
+/datum/sprite_accessory/mam_ears/eevee
+ name = "Eevee"
+ icon_state = "eevee"
+
+
+/datum/sprite_accessory/mam_ears/elf
+ name = "Elf"
+ icon_state = "elf"
+ color_src = MUTCOLORS3
+
+
+/datum/sprite_accessory/mam_ears/elephant
+ name = "Elephant"
+ icon_state = "elephant"
+
+/datum/sprite_accessory/mam_ears/fennec
+ name = "Fennec"
+ icon_state = "fennec"
+
+/datum/sprite_accessory/mam_ears/fish
+ name = "Fish"
+ icon_state = "fish"
+
+/datum/sprite_accessory/mam_ears/fox
+ name = "Fox"
+ icon_state = "fox"
+
+/datum/sprite_accessory/mam_ears/husky
+ name = "Husky"
+ icon_state = "wolf"
+
+/datum/sprite_accessory/mam_ears/kangaroo
+ name = "kangaroo"
+ icon_state = "kangaroo"
+
+/datum/sprite_accessory/mam_ears/jellyfish
+ name = "Jellyfish"
+ icon_state = "jellyfish"
+ color_src = HAIR
+
+/datum/sprite_accessory/mam_ears/lab
+ name = "Dog, Long"
+ icon_state = "lab"
+
+/datum/sprite_accessory/mam_ears/murid
+ name = "Murid"
+ icon_state = "murid"
+
+/datum/sprite_accessory/mam_ears/otie
+ name = "Otusian"
+ icon_state = "otie"
+
+/datum/sprite_accessory/mam_ears/squirrel
+ name = "Squirrel"
+ icon_state = "squirrel"
+
+/datum/sprite_accessory/mam_ears/pede
+ name = "Scolipede"
+ icon_state = "pede"
+
+/datum/sprite_accessory/mam_ears/rabbit
+ name = "Rabbit"
+ icon_state = "rabbit"
+
+/datum/sprite_accessory/mam_ears/sergal
+ name = "Sergal"
+ icon_state = "sergal"
+
+/datum/sprite_accessory/mam_ears/skunk
+ name = "skunk"
+ icon_state = "skunk"
+
+/datum/sprite_accessory/mam_ears/wolf
+ name = "Wolf"
+ icon_state = "wolf"
diff --git a/code/modules/mob/dead/new_player/sprite_accessories/hair_face.dm b/code/modules/mob/dead/new_player/sprite_accessories/hair_face.dm
index 3566f3dea5..d11299fd5b 100644
--- a/code/modules/mob/dead/new_player/sprite_accessories/hair_face.dm
+++ b/code/modules/mob/dead/new_player/sprite_accessories/hair_face.dm
@@ -86,4 +86,45 @@
/datum/sprite_accessory/facial_hair/elvis
name = "Sideburns (Elvis)"
- icon_state = "facial_elvis"
\ No newline at end of file
+ icon_state = "facial_elvis"
+
+#define VFACE(_name, new_state) /datum/sprite_accessory/facial_hair/##new_state/icon_state=#new_state;;/datum/sprite_accessory/facial_hair/##new_state/name= #_name + " (Virgo)"
+VFACE("Watson", facial_watson_s)
+VFACE("Chaplin", facial_chaplin_s)
+VFACE("Fullbeard", facial_fullbeard_s)
+VFACE("Vandyke", facial_vandyke_s)
+VFACE("Elvis", facial_elvis_s)
+VFACE("Abe", facial_abe_s)
+VFACE("Chin", facial_chin_s)
+VFACE("GT", facial_gt_s)
+VFACE("Hip", facial_hip_s)
+VFACE("Hogan", facial_hogan_s)
+VFACE("Selleck", facial_selleck_s)
+VFACE("Neckbeard", facial_neckbeard_s)
+VFACE("Longbeard", facial_longbeard_s)
+VFACE("Dwarf", facial_dwarf_s)
+VFACE("Sideburn", facial_sideburn_s)
+VFACE("Mutton", facial_mutton_s)
+VFACE("Moustache", facial_moustache_s)
+VFACE("Pencilstache", facial_pencilstache_s)
+VFACE("Goatee", facial_goatee_s)
+VFACE("Smallstache", facial_smallstache_s)
+VFACE("Volaju", facial_volaju_s)
+VFACE("3 O\'clock", facial_3oclock_s)
+VFACE("5 O\'clock", facial_5oclock_s)
+VFACE("7 O\'clock", facial_7oclock_s)
+VFACE("5 O\'clock Moustache", facial_5oclockmoustache_s)
+VFACE("7 O\'clock", facial_7oclockmoustache_s)
+VFACE("Walrus", facial_walrus_s)
+VFACE("Muttonmus", facial_muttonmus_s)
+VFACE("Wise", facial_wise_s)
+VFACE("Martial Artist", facial_martialartist_s)
+VFACE("Dorsalfnil", facial_dorsalfnil_s)
+VFACE("Hornadorns", facial_hornadorns_s)
+VFACE("Spike", facial_spike_s)
+VFACE("Chinhorns", facial_chinhorns_s)
+VFACE("Cropped Fullbeard", facial_croppedfullbeard_s)
+VFACE("Chinless Beard", facial_chinlessbeard_s)
+VFACE("Moonshiner", facial_moonshiner_s)
+VFACE("Tribearder", facial_tribearder_s)
+#undef VFACE
\ No newline at end of file
diff --git a/code/modules/mob/dead/new_player/sprite_accessories/hair_head.dm b/code/modules/mob/dead/new_player/sprite_accessories/hair_head.dm
index f8d8d26328..abcc90c0ee 100644
--- a/code/modules/mob/dead/new_player/sprite_accessories/hair_head.dm
+++ b/code/modules/mob/dead/new_player/sprite_accessories/hair_head.dm
@@ -461,4 +461,163 @@
/datum/sprite_accessory/hair/longestalt
name = "Very Long with Fringe"
- icon_state = "hair_vlongfringe"
\ No newline at end of file
+ icon_state = "hair_vlongfringe"
+
+/*************** VIRGO PORTED HAIRS ****************************/
+#define VHAIR(_name, new_state) /datum/sprite_accessory/hair/##new_state/icon_state=#new_state;/datum/sprite_accessory/hair/##new_state/name = #_name + " (Virgo)"
+//VIRGO PORTED HAIRS
+VHAIR("Short Hair Rosa", hair_rosa_s)
+VHAIR("Short Hair 80s", hair_80s_s)
+VHAIR("Long Bedhead", hair_long_bedhead_s)
+VHAIR("Dave", hair_dave_s)
+VHAIR("Country", hair_country_s)
+VHAIR("Shy", hair_shy_s)
+VHAIR("Unshaven Mohawk", hair_unshaven_mohawk_s)
+VHAIR("Manbun", hair_manbun_s)
+VHAIR("Longer Bedhead", hair_longer_bedhead_s)
+VHAIR("Ponytail", hair_ponytail_s)
+VHAIR("Ziegler", hair_ziegler_s)
+VHAIR("Emo Fringe", hair_emofringe_s)
+VHAIR("Very Short Over Eye Alt", hair_veryshortovereyealternate_s)
+VHAIR("Shorthime", hair_shorthime_s)
+VHAIR("High Tight", hair_hightight_s)
+VHAIR("Thinning Front", hair_thinningfront_s)
+VHAIR("Big Afro", hair_bigafro_s)
+VHAIR("Afro", hair_afro_s)
+VHAIR("High Braid", hair_hbraid_s)
+VHAIR("Braid", hair_braid_s)
+VHAIR("Sargeant", hair_sargeant_s)
+VHAIR("Gelled", hair_gelled_s)
+VHAIR("Kagami", hair_kagami_s)
+VHAIR("ShortTail", hair_stail_s)
+VHAIR("Gentle", hair_gentle_s)
+VHAIR("Grande", hair_grande_s)
+VHAIR("Bobcurl", hair_bobcurl_s)
+VHAIR("Pompadeur", hair_pompadour_s)
+VHAIR("Plait", hair_plait_s)
+VHAIR("Long", hair_long_s)
+VHAIR("Rattail", hair_rattail_s)
+VHAIR("Tajspiky", hair_tajspiky_s)
+VHAIR("Messy", hair_messy_s)
+VHAIR("Bangs", hair_bangs_s)
+VHAIR("TBraid", hair_tbraid_s)
+VHAIR("Toriyama2", hair_toriyama2_s)
+VHAIR("CIA", hair_cia_s)
+VHAIR("Mulder", hair_mulder_s)
+VHAIR("Scully", hair_scully_s)
+VHAIR("Nitori", hair_nitori_s)
+VHAIR("Joestar", hair_joestar_s)
+VHAIR("Ponytail4", hair_ponytail4_s)
+VHAIR("Ponytail5", hair_ponytail5_s)
+VHAIR("Beehive2", hair_beehive2_s)
+VHAIR("Short Braid", hair_shortbraid_s)
+VHAIR("Reverse Mohawk", hair_reversemohawk_s)
+VHAIR("SHort Bangs", hair_shortbangs_s)
+VHAIR("Half Shaved", hair_halfshaved_s)
+VHAIR("Longer Alt 2", hair_longeralt2_s)
+VHAIR("Bun", hair_bun_s)
+VHAIR("Curly", hair_curly_s)
+VHAIR("Victory", hair_victory_s)
+VHAIR("Ponytail6", hair_ponytail6_s)
+VHAIR("Undercut3", hair_undercut3_s)
+VHAIR("Bobcut Alt", hair_bobcultalt_s)
+VHAIR("Fingerwave", hair_fingerwave_s)
+VHAIR("Oxton", hair_oxton_s)
+VHAIR("Poofy2", hair_poofy2_s)
+VHAIR("Fringe Tail", hair_fringetail_s)
+VHAIR("Bun3", hair_bun3_s)
+VHAIR("Wisp", hair_wisp_s)
+VHAIR("Undercut2", hair_undercut2_s)
+VHAIR("TBob", hair_tbob_s)
+VHAIR("Spiky Ponytail", hair_spikyponytail_s)
+VHAIR("Rowbun", hair_rowbun_s)
+VHAIR("Rowdualtail", hair_rowdualtail_s)
+VHAIR("Rowbraid", hair_rowbraid_s)
+VHAIR("Shaved Mohawk", hair_shavedmohawk_s)
+VHAIR("Topknot", hair_topknot_s)
+VHAIR("Ronin", hair_ronin_s)
+VHAIR("Bowlcut2", hair_bowlcut2_s)
+VHAIR("Thinning Rear", hair_thinningrear_s)
+VHAIR("Thinning", hair_thinning_s)
+VHAIR("Jade", hair_jade_s)
+VHAIR("Bedhead", hair_bedhead_s)
+VHAIR("Dreadlocks", hair_dreads_s)
+VHAIR("Very Long", hair_vlong_s)
+VHAIR("Jensen", hair_jensen_s)
+VHAIR("Halfbang", hair_halfbang_s)
+VHAIR("Kusangi", hair_kusangi_s)
+VHAIR("Ponytail", hair_ponytail_s)
+VHAIR("Ponytail3", hair_ponytail3_s)
+VHAIR("Halfbang Alt", hair_halfbang_alt_s)
+VHAIR("Bedhead V2", hair_bedheadv2_s)
+VHAIR("Long Fringe", hair_longfringe_s)
+VHAIR("Flair", hair_flair_s)
+VHAIR("Bedhead V3", hair_bedheadv3_s)
+VHAIR("Himecut", hair_himecut_s)
+VHAIR("Curls", hair_curls_s)
+VHAIR("Very Long Fringe", hair_vlongfringe_s)
+VHAIR("Longest", hair_longest_s)
+VHAIR("Father", hair_father_s)
+VHAIR("Emo Long", hair_emolong_s)
+VHAIR("Short Hair 3", hair_shorthair3_s)
+VHAIR("Double Bun", hair_doublebun_s)
+VHAIR("Sleeze", hair_sleeze_s)
+VHAIR("Twintail", hair_twintail_s)
+VHAIR("Emo 2", hair_emo2_s)
+VHAIR("Low Fade", hair_lowfade_s)
+VHAIR("Med Fade", hair_medfade_s)
+VHAIR("High Fade", hair_highfade_s)
+VHAIR("Bald Fade", hair_baldfade_s)
+VHAIR("No Fade", hair_nofade_s)
+VHAIR("Trim Flat", hair_trimflat_s)
+VHAIR("Shaved", hair_shaved_s)
+VHAIR("Trimmed", hair_trimmed_s)
+VHAIR("Tight Bun", hair_tightbun_s)
+VHAIR("Short Hair 4", hair_d_s)
+VHAIR("Short Hair 5", hair_e_s)
+VHAIR("Short Hair 6", hair_f_s)
+VHAIR("Skinhead", hair_skinhead_s)
+VHAIR("Afro2", hair_afro2_s)
+VHAIR("Bobcut", hair_bobcut_s)
+VHAIR("Emo", hair_emo_s)
+VHAIR("Long Over Eye", hair_longovereye_s)
+VHAIR("Feather", hair_feather_s)
+VHAIR("Hitop", hair_hitop_s)
+VHAIR("Short Over Eye", hair_shortoverye_s)
+VHAIR("Straight", hair_straight_s)
+VHAIR("Buzzcut", hair_buzzcut_s)
+VHAIR("Combover", hair_combover_s)
+VHAIR("Crewcut", hair_crewcut_s)
+VHAIR("Devillock", hair_devilock_s)
+VHAIR("Clean", hair_clean_s)
+VHAIR("Shaggy", hair_shaggy_s)
+VHAIR("Updo", hair_updo_s)
+VHAIR("Mohawk", hair_mohawk_s)
+VHAIR("Odango", hair_odango_s)
+VHAIR("Ombre", hair_ombre_s)
+VHAIR("Parted", hair_parted_s)
+VHAIR("Quiff", hair_quiff_s)
+VHAIR("Volaju", hair_volaju_s)
+VHAIR("Bun2", hair_bun2_s)
+VHAIR("Rows1", hair_rows1_s)
+VHAIR("Rows2", hair_rows2_s)
+VHAIR("Dandy Pompadour", hair_dandypompadour_s)
+VHAIR("Poofy", hair_poofy_s)
+VHAIR("Toriyama", hair_toriyama_s)
+VHAIR("Drillruru", hair_drillruru_s)
+VHAIR("Bowlcut", hair_bowlcut_s)
+VHAIR("Coffee House", hair_coffeehouse_s)
+VHAIR("Family Man", hair_thefamilyman_s)
+VHAIR("Shaved Part", hair_shavedpart_s)
+VHAIR("Modern", hair_modern_s)
+VHAIR("One Shoulder", hair_oneshoulder_s)
+VHAIR("Very Short Over Eye", hair_veryshortovereye_s)
+VHAIR("Unkept", hair_unkept_s)
+VHAIR("Wife", hair_wife_s)
+VHAIR("Nia", hair_nia_s)
+VHAIR("Undercut", hair_undercut_s)
+VHAIR("Bobcut Alt", hair_bobcutalt_s)
+VHAIR("Short Hair 4 alt", hair_shorthair4_s)
+VHAIR("Tressshoulder", hair_tressshoulder_s)
+ //END
+#undef VHAIR
\ No newline at end of file
diff --git a/code/modules/mob/dead/new_player/sprite_accessories/horns.dm b/code/modules/mob/dead/new_player/sprite_accessories/horns.dm
index 607ad650e3..a630ead7b3 100644
--- a/code/modules/mob/dead/new_player/sprite_accessories/horns.dm
+++ b/code/modules/mob/dead/new_player/sprite_accessories/horns.dm
@@ -1,5 +1,6 @@
/datum/sprite_accessory/horns
icon = 'icons/mob/mutant_bodyparts.dmi'
+ color_src = HORNCOLOR
/datum/sprite_accessory/horns/none
name = "None"
@@ -23,4 +24,13 @@
/datum/sprite_accessory/horns/angler
name = "Angeler"
- icon_state = "angler"
\ No newline at end of file
+ icon_state = "angler"
+ color_src = MUTCOLORS
+
+/datum/sprite_accessory/horns/antler
+ name = "Deer Antlers"
+ icon_state = "deer"
+
+/datum/sprite_accessory/horns/guilmon
+ name = "Guilmon"
+ icon_state = "guilmon"
\ No newline at end of file
diff --git a/code/modules/mob/dead/new_player/sprite_accessories/ipc_synths.dm b/code/modules/mob/dead/new_player/sprite_accessories/ipc_synths.dm
new file mode 100644
index 0000000000..6d2ab1a39b
--- /dev/null
+++ b/code/modules/mob/dead/new_player/sprite_accessories/ipc_synths.dm
@@ -0,0 +1,158 @@
+
+/******************************************
+************** IPC SCREENS ****************
+*******************************************/
+/datum/sprite_accessory/screen
+ icon = 'modular_citadel/icons/mob/ipc_screens.dmi'
+ color_src = null
+
+/datum/sprite_accessory/screen/blank
+ name = "Blank"
+ icon_state = "blank"
+
+/datum/sprite_accessory/screen/pink
+ name = "Pink"
+ icon_state = "pink"
+
+/datum/sprite_accessory/screen/green
+ name = "Green"
+ icon_state = "green"
+
+/datum/sprite_accessory/screen/red
+ name = "Red"
+ icon_state = "red"
+
+/datum/sprite_accessory/screen/blue
+ name = "Blue"
+ icon_state = "blue"
+
+/datum/sprite_accessory/screen/yellow
+ name = "Yellow"
+ icon_state = "yellow"
+
+/datum/sprite_accessory/screen/shower
+ name = "Shower"
+ icon_state = "shower"
+
+/datum/sprite_accessory/screen/nature
+ name = "Nature"
+ icon_state = "nature"
+
+/datum/sprite_accessory/screen/eight
+ name = "Eight"
+ icon_state = "eight"
+
+/datum/sprite_accessory/screen/goggles
+ name = "Goggles"
+ icon_state = "goggles"
+
+/datum/sprite_accessory/screen/heart
+ name = "Heart"
+ icon_state = "heart"
+
+/datum/sprite_accessory/screen/monoeye
+ name = "Mono eye"
+ icon_state = "monoeye"
+
+/datum/sprite_accessory/screen/breakout
+ name = "Breakout"
+ icon_state = "breakout"
+
+/datum/sprite_accessory/screen/purple
+ name = "Purple"
+ icon_state = "purple"
+
+/datum/sprite_accessory/screen/scroll
+ name = "Scroll"
+ icon_state = "scroll"
+
+/datum/sprite_accessory/screen/console
+ name = "Console"
+ icon_state = "console"
+
+/datum/sprite_accessory/screen/rgb
+ name = "RGB"
+ icon_state = "rgb"
+
+/datum/sprite_accessory/screen/golglider
+ name = "Gol Glider"
+ icon_state = "golglider"
+
+/datum/sprite_accessory/screen/rainbow
+ name = "Rainbow"
+ icon_state = "rainbow"
+
+/datum/sprite_accessory/screen/sunburst
+ name = "Sunburst"
+ icon_state = "sunburst"
+
+/datum/sprite_accessory/screen/static
+ name = "Static"
+ icon_state = "static"
+
+//Oracle Station sprites
+
+/datum/sprite_accessory/screen/bsod
+ name = "BSOD"
+ icon_state = "bsod"
+
+/datum/sprite_accessory/screen/redtext
+ name = "Red Text"
+ icon_state = "retext"
+
+/datum/sprite_accessory/screen/sinewave
+ name = "Sine wave"
+ icon_state = "sinewave"
+
+/datum/sprite_accessory/screen/squarewave
+ name = "Square wave"
+ icon_state = "squarwave"
+
+/datum/sprite_accessory/screen/ecgwave
+ name = "ECG wave"
+ icon_state = "ecgwave"
+
+/datum/sprite_accessory/screen/eyes
+ name = "Eyes"
+ icon_state = "eyes"
+
+/datum/sprite_accessory/screen/textdrop
+ name = "Text drop"
+ icon_state = "textdrop"
+
+/datum/sprite_accessory/screen/stars
+ name = "Stars"
+ icon_state = "stars"
+
+
+/******************************************
+************** IPC Antennas ***************
+*******************************************/
+
+/datum/sprite_accessory/antenna
+ icon = 'modular_citadel/icons/mob/ipc_antennas.dmi'
+ color_src = MUTCOLORS2
+
+/datum/sprite_accessory/antenna/none
+ name = "None"
+ icon_state = "None"
+
+/datum/sprite_accessory/antenna/antennae
+ name = "Angled Antennae"
+ icon_state = "antennae"
+
+/datum/sprite_accessory/antenna/tvantennae
+ name = "TV Antennae"
+ icon_state = "tvantennae"
+
+/datum/sprite_accessory/antenna/cyberhead
+ name = "Cyberhead"
+ icon_state = "cyberhead"
+
+/datum/sprite_accessory/antenna/antlers
+ name = "Antlers"
+ icon_state = "antlers"
+
+/datum/sprite_accessory/antenna/crowned
+ name = "Crowned"
+ icon_state = "crowned"
diff --git a/code/modules/mob/dead/new_player/sprite_accessories/legs.dm b/code/modules/mob/dead/new_player/sprite_accessories/legs.dm
deleted file mode 100644
index 7663100822..0000000000
--- a/code/modules/mob/dead/new_player/sprite_accessories/legs.dm
+++ /dev/null
@@ -1,8 +0,0 @@
-/datum/sprite_accessory/legs //legs are a special case, they aren't actually sprite_accessories but are updated with them.
- icon = null //These datums exist for selecting legs on preference, and little else
-
-/datum/sprite_accessory/legs/none
- name = "Normal Legs"
-
-/datum/sprite_accessory/legs/digitigrade_lizard
- name = "Digitigrade Legs"
\ No newline at end of file
diff --git a/code/modules/mob/dead/new_player/sprite_accessories/legs_and_taurs.dm b/code/modules/mob/dead/new_player/sprite_accessories/legs_and_taurs.dm
new file mode 100644
index 0000000000..15640a2699
--- /dev/null
+++ b/code/modules/mob/dead/new_player/sprite_accessories/legs_and_taurs.dm
@@ -0,0 +1,124 @@
+/datum/sprite_accessory/legs //legs are a special case, they aren't actually sprite_accessories but are updated with them. -- OR SO THEY USED TO BE
+ icon = null //These datums exist for selecting legs on preference, and little else
+
+/******************************************
+***************** Leggy *******************
+*******************************************/
+
+/datum/sprite_accessory/legs/none
+ name = "Plantigrade"
+
+/datum/sprite_accessory/legs/digitigrade_lizard
+ name = "Digitigrade"
+
+/datum/sprite_accessory/legs/digitigrade_bird
+ name = "Avian"
+
+
+/******************************************
+************** Taur Bodies ****************
+*******************************************/
+
+/datum/sprite_accessory/taur
+ icon = 'modular_citadel/icons/mob/mam_taur.dmi'
+ extra_icon = 'modular_citadel/icons/mob/mam_taur.dmi'
+ extra = TRUE
+ extra2_icon = 'modular_citadel/icons/mob/mam_taur.dmi'
+ extra2 = TRUE
+ center = TRUE
+ dimension_x = 64
+ var/taur_mode = NOT_TAURIC
+ color_src = MATRIXED
+
+/datum/sprite_accessory/taur/none
+ name = "None"
+ icon_state = "None"
+
+/datum/sprite_accessory/taur/cow
+ name = "Cow"
+ icon_state = "cow"
+ taur_mode = HOOF_TAURIC
+
+/datum/sprite_accessory/taur/deer
+ name = "Deer"
+ icon_state = "deer"
+ taur_mode = HOOF_TAURIC
+ color_src = MUTCOLORS
+
+/datum/sprite_accessory/taur/drake
+ name = "Drake"
+ icon_state = "drake"
+ taur_mode = PAW_TAURIC
+
+/datum/sprite_accessory/taur/drider
+ name = "Drider"
+ icon_state = "drider"
+ color_src = MUTCOLORS
+
+/datum/sprite_accessory/taur/eevee
+ name = "Eevee"
+ icon_state = "eevee"
+ taur_mode = PAW_TAURIC
+ color_src = MUTCOLORS
+
+/datum/sprite_accessory/taur/fox
+ name = "Fox"
+ icon_state = "fox"
+ taur_mode = PAW_TAURIC
+
+/datum/sprite_accessory/taur/husky
+ name = "Husky"
+ icon_state = "husky"
+ taur_mode = PAW_TAURIC
+
+/datum/sprite_accessory/taur/horse
+ name = "Horse"
+ icon_state = "horse"
+ taur_mode = HOOF_TAURIC
+
+/datum/sprite_accessory/taur/lab
+ name = "Lab"
+ icon_state = "lab"
+ taur_mode = PAW_TAURIC
+
+/datum/sprite_accessory/taur/naga
+ name = "Naga"
+ icon_state = "naga"
+ taur_mode = SNEK_TAURIC
+
+/datum/sprite_accessory/taur/otie
+ name = "Otie"
+ icon_state = "otie"
+ taur_mode = PAW_TAURIC
+
+/datum/sprite_accessory/taur/pede
+ name = "Scolipede"
+ icon_state = "pede"
+ taur_mode = PAW_TAURIC
+ color_src = MUTCOLORS
+
+/datum/sprite_accessory/taur/panther
+ name = "Panther"
+ icon_state = "panther"
+ taur_mode = PAW_TAURIC
+
+/datum/sprite_accessory/taur/shepherd
+ name = "Shepherd"
+ icon_state = "shepherd"
+ taur_mode = PAW_TAURIC
+
+/datum/sprite_accessory/taur/tentacle
+ name = "Tentacle"
+ icon_state = "tentacle"
+ taur_mode = SNEK_TAURIC
+ color_src = MUTCOLORS
+
+/datum/sprite_accessory/taur/tiger
+ name = "Tiger"
+ icon_state = "tiger"
+ taur_mode = PAW_TAURIC
+
+/datum/sprite_accessory/taur/wolf
+ name = "Wolf"
+ icon_state = "wolf"
+ taur_mode = PAW_TAURIC
diff --git a/code/modules/mob/dead/new_player/sprite_accessories/moth_wings.dm b/code/modules/mob/dead/new_player/sprite_accessories/moth_wings.dm
deleted file mode 100644
index 6b8036bd69..0000000000
--- a/code/modules/mob/dead/new_player/sprite_accessories/moth_wings.dm
+++ /dev/null
@@ -1,68 +0,0 @@
-/datum/sprite_accessory/moth_wings
- icon = 'icons/mob/wings.dmi'
- color_src = null
-
-/datum/sprite_accessory/moth_wings/plain
- name = "Plain"
- icon_state = "plain"
-
-/datum/sprite_accessory/moth_wings/monarch
- name = "Monarch"
- icon_state = "monarch"
-
-/datum/sprite_accessory/moth_wings/luna
- name = "Luna"
- icon_state = "luna"
-
-/datum/sprite_accessory/moth_wings/atlas
- name = "Atlas"
- icon_state = "atlas"
-
-/datum/sprite_accessory/moth_wings/reddish
- name = "Reddish"
- icon_state = "redish"
-
-/datum/sprite_accessory/moth_wings/royal
- name = "Royal"
- icon_state = "royal"
-
-/datum/sprite_accessory/moth_wings/gothic
- name = "Gothic"
- icon_state = "gothic"
-
-/datum/sprite_accessory/moth_wings/lovers
- name = "Lovers"
- icon_state = "lovers"
-
-/datum/sprite_accessory/moth_wings/whitefly
- name = "White Fly"
- icon_state = "whitefly"
-
-/datum/sprite_accessory/moth_wings/punished
- name = "Burnt Off"
- icon_state = "punished"
- locked = TRUE
-
-/datum/sprite_accessory/moth_wings/firewatch
- name = "Firewatch"
- icon_state = "firewatch"
-
-/datum/sprite_accessory/moth_wings/deathhead
- name = "Deathshead"
- icon_state = "deathhead"
-
-/datum/sprite_accessory/moth_wings/poison
- name = "Poison"
- icon_state = "poison"
-
-/datum/sprite_accessory/moth_wings/ragged
- name = "Ragged"
- icon_state = "ragged"
-
-/datum/sprite_accessory/moth_wings/moonfly
- name = "Moon Fly"
- icon_state = "moonfly"
-
-/datum/sprite_accessory/moth_wings/snow
- name = "Snow"
- icon_state = "snow"
diff --git a/code/modules/mob/dead/new_player/sprite_accessories/snouts.dm b/code/modules/mob/dead/new_player/sprite_accessories/snouts.dm
index c663c08d69..7252f85324 100644
--- a/code/modules/mob/dead/new_player/sprite_accessories/snouts.dm
+++ b/code/modules/mob/dead/new_player/sprite_accessories/snouts.dm
@@ -15,4 +15,359 @@
/datum/sprite_accessory/snouts/roundlight
name = "Round + Light"
- icon_state = "roundlight"
\ No newline at end of file
+ icon_state = "roundlight"
+
+/datum/sprite_accessory/snout/guilmon
+ name = "Guilmon"
+ icon_state = "guilmon"
+ color_src = MATRIXED
+
+//christ this was a mistake, but it's here just in case someone wants to selectively fix -- Pooj
+/************* Lizard compatable snoots ***********
+/datum/sprite_accessory/snouts/bird
+ name = "Beak"
+ icon_state = "bird"
+ icon = 'modular_citadel/icons/mob/mam_snouts.dmi'
+ color_src = MATRIXED
+
+/datum/sprite_accessory/snouts/bigbeak
+ name = "Big Beak"
+ icon_state = "bigbeak"
+ icon = 'modular_citadel/icons/mob/mam_snouts.dmi'
+ color_src = MATRIXED
+
+/datum/sprite_accessory/snouts/bug
+ name = "Bug"
+ icon_state = "bug"
+ icon = 'modular_citadel/icons/mob/mam_snouts.dmi'
+ extra2 = TRUE
+ extra2_color_src = MUTCOLORS3
+
+/datum/sprite_accessory/snouts/elephant
+ name = "Elephant"
+ icon_state = "elephant"
+ icon = 'modular_citadel/icons/mob/mam_snouts.dmi'
+ color_src = MATRIXED
+ extra = TRUE
+ extra_color_src = MUTCOLORS3
+
+/datum/sprite_accessory/snouts/lcanid
+ name = "Mammal, Long"
+ icon_state = "lcanid"
+ icon = 'modular_citadel/icons/mob/mam_snouts.dmi'
+ color_src = MATRIXED
+
+/datum/sprite_accessory/snouts/lcanidalt
+ name = "Mammal, Long ALT"
+ icon_state = "lcanidalt"
+ icon = 'modular_citadel/icons/mob/mam_snouts.dmi'
+ color_src = MATRIXED
+
+/datum/sprite_accessory/snouts/scanid
+ name = "Mammal, Short"
+ icon_state = "scanid"
+ icon = 'modular_citadel/icons/mob/mam_snouts.dmi'
+ color_src = MATRIXED
+
+/datum/sprite_accessory/snouts/scanidalt
+ name = "Mammal, Short ALT"
+ icon_state = "scanidalt"
+ icon = 'modular_citadel/icons/mob/mam_snouts.dmi'
+ color_src = MATRIXED
+
+/datum/sprite_accessory/snouts/wolf
+ name = "Mammal, Thick"
+ icon_state = "wolf"
+ icon = 'modular_citadel/icons/mob/mam_snouts.dmi'
+ color_src = MATRIXED
+
+/datum/sprite_accessory/snouts/wolfalt
+ name = "Mammal, Thick ALT"
+ icon_state = "wolfalt"
+ icon = 'modular_citadel/icons/mob/mam_snouts.dmi'
+ color_src = MATRIXED
+
+/datum/sprite_accessory/snouts/redpanda
+ name = "WahCoon"
+ icon_state = "wah"
+ icon = 'modular_citadel/icons/mob/mam_snouts.dmi'
+ color_src = MATRIXED
+
+/datum/sprite_accessory/snouts/rhino
+ name = "Horn"
+ icon_state = "rhino"
+ icon = 'modular_citadel/icons/mob/mam_snouts.dmi'
+ color_src = MATRIXED
+ extra = TRUE
+ extra = MUTCOLORS3
+
+/datum/sprite_accessory/snouts/rodent
+ name = "Rodent"
+ icon_state = "rodent"
+ icon = 'modular_citadel/icons/mob/mam_snouts.dmi'
+ color_src = MATRIXED
+
+/datum/sprite_accessory/snouts/husky
+ name = "Husky"
+ icon_state = "husky"
+ icon = 'modular_citadel/icons/mob/mam_snouts.dmi'
+ color_src = MATRIXED
+
+/datum/sprite_accessory/snouts/otie
+ name = "Otie"
+ icon_state = "otie"
+ icon = 'modular_citadel/icons/mob/mam_snouts.dmi'
+ color_src = MATRIXED
+
+/datum/sprite_accessory/snouts/pede
+ name = "Scolipede"
+ icon_state = "pede"
+ icon = 'modular_citadel/icons/mob/mam_snouts.dmi'
+ color_src = MATRIXED
+
+/datum/sprite_accessory/snouts/sergal
+ name = "Sergal"
+ icon_state = "sergal"
+ icon = 'modular_citadel/icons/mob/mam_snouts.dmi'
+ color_src = MATRIXED
+
+/datum/sprite_accessory/snouts/shark
+ name = "Shark"
+ icon_state = "shark"
+ color_src = MATRIXED
+ icon = 'modular_citadel/icons/mob/mam_snouts.dmi'
+
+/datum/sprite_accessory/snouts/toucan
+ name = "Toucan"
+ icon_state = "toucan"
+ icon = 'modular_citadel/icons/mob/mam_snouts.dmi'
+ color_src = MATRIXED
+*/
+
+/******************************************
+************** Mammal Snouts **************
+*******************************************/
+
+/datum/sprite_accessory/mam_snouts
+ color_src = MATRIXED
+ icon = 'modular_citadel/icons/mob/mam_snouts.dmi'
+
+/datum/sprite_accessory/mam_snouts/none
+ name = "None"
+ icon_state = "none"
+
+
+/datum/sprite_accessory/mam_snouts/bird
+ name = "Beak"
+ icon_state = "bird"
+
+/datum/sprite_accessory/mam_snouts/bigbeak
+ name = "Big Beak"
+ icon_state = "bigbeak"
+
+/datum/sprite_accessory/mam_snouts/bug
+ name = "Bug"
+ icon_state = "bug"
+ color_src = MUTCOLORS
+ extra2 = TRUE
+ extra2_color_src = MUTCOLORS3
+
+/datum/sprite_accessory/mam_snouts/elephant
+ name = "Elephant"
+ icon_state = "elephant"
+ extra = TRUE
+ extra_color_src = MUTCOLORS3
+
+/datum/sprite_accessory/mam_snouts/lcanid
+ name = "Mammal, Long"
+ icon_state = "lcanid"
+
+/datum/sprite_accessory/mam_snouts/lcanidalt
+ name = "Mammal, Long ALT"
+ icon_state = "lcanidalt"
+
+/datum/sprite_accessory/mam_snouts/scanid
+ name = "Mammal, Short"
+ icon_state = "scanid"
+
+/datum/sprite_accessory/mam_snouts/scanidalt
+ name = "Mammal, Short ALT"
+ icon_state = "scanidalt"
+
+/datum/sprite_accessory/mam_snouts/wolf
+ name = "Mammal, Thick"
+ icon_state = "wolf"
+
+/datum/sprite_accessory/mam_snouts/wolfalt
+ name = "Mammal, Thick ALT"
+ icon_state = "wolfalt"
+
+/datum/sprite_accessory/mam_snouts/redpanda
+ name = "WahCoon"
+ icon_state = "wah"
+
+/datum/sprite_accessory/mam_snouts/redpandaalt
+ name = "WahCoon ALT"
+ icon_state = "wahalt"
+
+/datum/sprite_accessory/mam_snouts/rhino
+ name = "Horn"
+ icon_state = "rhino"
+ extra = TRUE
+ extra = MUTCOLORS3
+
+/datum/sprite_accessory/mam_snouts/rodent
+ name = "Rodent"
+ icon_state = "rodent"
+
+/datum/sprite_accessory/mam_snouts/husky
+ name = "Husky"
+ icon_state = "husky"
+
+/datum/sprite_accessory/mam_snouts/otie
+ name = "Otie"
+ icon_state = "otie"
+
+/datum/sprite_accessory/mam_snouts/pede
+ name = "Scolipede"
+ icon_state = "pede"
+
+/datum/sprite_accessory/mam_snouts/sergal
+ name = "Sergal"
+ icon_state = "sergal"
+
+/datum/sprite_accessory/mam_snouts/shark
+ name = "Shark"
+ icon_state = "shark"
+
+/datum/sprite_accessory/mam_snouts/toucan
+ name = "Toucan"
+ icon_state = "toucan"
+
+/datum/sprite_accessory/mam_snouts/sharp
+ name = "Sharp"
+ icon_state = "sharp"
+ color_src = MUTCOLORS
+
+/datum/sprite_accessory/mam_snouts/round
+ name = "Round"
+ icon_state = "round"
+ color_src = MUTCOLORS
+
+/datum/sprite_accessory/mam_snouts/sharplight
+ name = "Sharp + Light"
+ icon_state = "sharplight"
+ color_src = MUTCOLORS
+
+/datum/sprite_accessory/mam_snouts/roundlight
+ name = "Round + Light"
+ icon_state = "roundlight"
+ color_src = MUTCOLORS
+
+
+/******************************************
+**************** Snouts *******************
+*************but higher up*****************/
+
+/datum/sprite_accessory/mam_snouts/fbird
+ name = "Beak (Top)"
+ icon_state = "fbird"
+
+/datum/sprite_accessory/mam_snouts/fbigbeak
+ name = "Big Beak (Top)"
+ icon_state = "fbigbeak"
+
+/datum/sprite_accessory/mam_snouts/fbug
+ name = "Bug (Top)"
+ icon_state = "fbug"
+ color_src = MUTCOLORS
+ extra2 = TRUE
+ extra2_color_src = MUTCOLORS3
+
+/datum/sprite_accessory/mam_snouts/felephant
+ name = "Elephant (Top)"
+ icon_state = "felephant"
+ extra = TRUE
+ extra_color_src = MUTCOLORS3
+
+/datum/sprite_accessory/mam_snouts/flcanid
+ name = "Mammal, Long (Top)"
+ icon_state = "flcanid"
+
+/datum/sprite_accessory/mam_snouts/flcanidalt
+ name = "Mammal, Long ALT (Top)"
+ icon_state = "flcanidalt"
+
+/datum/sprite_accessory/mam_snouts/fscanid
+ name = "Mammal, Short (Top)"
+ icon_state = "fscanid"
+
+/datum/sprite_accessory/mam_snouts/fscanidalt
+ name = "Mammal, Short ALT (Top)"
+ icon_state = "fscanidalt"
+
+/datum/sprite_accessory/mam_snouts/fwolf
+ name = "Mammal, Thick (Top)"
+ icon_state = "fwolf"
+
+/datum/sprite_accessory/mam_snouts/fwolfalt
+ name = "Mammal, Thick ALT (Top)"
+ icon_state = "fwolfalt"
+
+/datum/sprite_accessory/mam_snouts/fredpanda
+ name = "WahCoon (Top)"
+ icon_state = "fwah"
+
+/datum/sprite_accessory/mam_snouts/frhino
+ name = "Horn (Top)"
+ icon_state = "frhino"
+ extra = TRUE
+ extra = MUTCOLORS3
+
+/datum/sprite_accessory/mam_snouts/frodent
+ name = "Rodent (Top)"
+ icon_state = "frodent"
+
+/datum/sprite_accessory/mam_snouts/fhusky
+ name = "Husky (Top)"
+ icon_state = "fhusky"
+
+/datum/sprite_accessory/mam_snouts/fotie
+ name = "Otie (Top)"
+ icon_state = "fotie"
+
+/datum/sprite_accessory/mam_snouts/fpede
+ name = "Scolipede (Top)"
+ icon_state = "fpede"
+
+/datum/sprite_accessory/mam_snouts/fsergal
+ name = "Sergal (Top)"
+ icon_state = "fsergal"
+
+/datum/sprite_accessory/mam_snouts/fshark
+ name = "Shark (Top)"
+ icon_state = "fshark"
+
+/datum/sprite_accessory/mam_snouts/ftoucan
+ name = "Toucan (Top)"
+ icon_state = "ftoucan"
+
+/datum/sprite_accessory/mam_snouts/fsharp
+ name = "Sharp (Top)"
+ icon_state = "fsharp"
+ color_src = MUTCOLORS
+
+/datum/sprite_accessory/mam_snouts/fround
+ name = "Round (Top)"
+ icon_state = "fround"
+ color_src = MUTCOLORS
+
+/datum/sprite_accessory/mam_snouts/fsharplight
+ name = "Sharp + Light (Top)"
+ icon_state = "fsharplight"
+ color_src = MUTCOLORS
+
+/datum/sprite_accessory/mam_snouts/froundlight
+ name = "Round + Light (Top)"
+ icon_state = "froundlight"
+ color_src = MUTCOLORS
\ No newline at end of file
diff --git a/code/modules/mob/dead/new_player/sprite_accessories/socks.dm b/code/modules/mob/dead/new_player/sprite_accessories/socks.dm
index 3384f3754b..524c1f0f13 100644
--- a/code/modules/mob/dead/new_player/sprite_accessories/socks.dm
+++ b/code/modules/mob/dead/new_player/sprite_accessories/socks.dm
@@ -2,135 +2,132 @@
// Socks Definitions //
///////////////////////
-/datum/sprite_accessory/socks
- icon = 'icons/mob/underwear.dmi'
-
-/datum/sprite_accessory/socks/nude
+/datum/sprite_accessory/underwear/socks/nude
name = "Nude"
icon_state = null
// please make sure they're sorted alphabetically and categorized
-/datum/sprite_accessory/socks/bee_knee
- name = "Knee-high (Bee)"
+/datum/sprite_accessory/underwear/socks/socks_knee
+ name = "Knee-high"
+ icon_state = "socks_knee"
+ has_color = TRUE
+
+/datum/sprite_accessory/underwear/socks/bee_knee
+ name = "Knee-high - Bee"
icon_state = "bee_knee"
-/datum/sprite_accessory/socks/black_knee
- name = "Knee-high (Black)"
- icon_state = "black_knee"
-
-/datum/sprite_accessory/socks/commie_knee
- name = "Knee-High (Commie)"
+/datum/sprite_accessory/underwear/socks/commie_knee
+ name = "Knee-High - Commie"
icon_state = "commie_knee"
-/datum/sprite_accessory/socks/usa_knee
- name = "Knee-High (Freedom)"
+/datum/sprite_accessory/underwear/socks/usa_knee
+ name = "Knee-High - Freedom"
icon_state = "assblastusa_knee"
-/datum/sprite_accessory/socks/rainbow_knee
- name = "Knee-high (Rainbow)"
+/datum/sprite_accessory/underwear/socks/rainbow_knee
+ name = "Knee-high - Rainbow"
icon_state = "rainbow_knee"
-/datum/sprite_accessory/socks/striped_knee
- name = "Knee-high (Striped)"
+/datum/sprite_accessory/underwear/socks/striped_knee
+ name = "Knee-high - Striped"
icon_state = "striped_knee"
+ has_color = TRUE
-/datum/sprite_accessory/socks/thin_knee
- name = "Knee-high (Thin)"
+/datum/sprite_accessory/underwear/socks/thin_knee
+ name = "Knee-high - Thin"
icon_state = "thin_knee"
+ has_color = TRUE
-/datum/sprite_accessory/socks/uk_knee
- name = "Knee-High (UK)"
+/datum/sprite_accessory/underwear/socks/uk_knee
+ name = "Knee-High - UK"
icon_state = "uk_knee"
-/datum/sprite_accessory/socks/white_knee
- name = "Knee-high (White)"
- icon_state = "white_knee"
+/datum/sprite_accessory/underwear/socks/socks_norm
+ name = "Normal"
+ icon_state = "socks_norm"
+ has_color = TRUE
-/datum/sprite_accessory/socks/black_norm
- name = "Normal (Black)"
- icon_state = "black_norm"
+/datum/sprite_accessory/underwear/socks/bee_norm
+ name = "Normal - Bee"
+ icon_state = "bee_norm"
-/datum/sprite_accessory/socks/white_norm
- name = "Normal (White)"
- icon_state = "white_norm"
-
-/datum/sprite_accessory/socks/pantyhose
+/datum/sprite_accessory/underwear/socks/pantyhose
name = "Pantyhose"
icon_state = "pantyhose"
-/datum/sprite_accessory/socks/black_short
- name = "Short (Black)"
- icon_state = "black_short"
+/datum/sprite_accessory/underwear/socks/socks_short
+ name = "Short"
+ icon_state = "socks_short"
+ has_color = TRUE
-/datum/sprite_accessory/socks/white_short
- name = "Short (White)"
- icon_state = "white_short"
-
-/datum/sprite_accessory/socks/stockings_blue
- name = "Stockings (Blue)"
+/datum/sprite_accessory/underwear/socks/stockings_blue
+ name = "Stockings - Blue"
icon_state = "stockings_blue"
-/datum/sprite_accessory/socks/stockings_cyan
- name = "Stockings (Cyan)"
+/datum/sprite_accessory/underwear/socks/stockings_cyan
+ name = "Stockings - Cyan"
icon_state = "stockings_cyan"
-/datum/sprite_accessory/socks/stockings_dpink
- name = "Stockings (Dark Pink)"
+/datum/sprite_accessory/underwear/socks/stockings_dpink
+ name = "Stockings - Dark Pink"
icon_state = "stockings_dpink"
-/datum/sprite_accessory/socks/stockings_green
- name = "Stockings (Green)"
+/datum/sprite_accessory/underwear/socks/stockings_green
+ name = "Stockings - Green"
icon_state = "stockings_black"
-/datum/sprite_accessory/socks/stockings_orange
- name = "Stockings (Orange)"
+/datum/sprite_accessory/underwear/socks/stockings_orange
+ name = "Stockings - Orange"
icon_state = "stockings_orange"
-/datum/sprite_accessory/socks/stockings_programmer
- name = "Stockings (Programmer)"
+/datum/sprite_accessory/underwear/socks/stockings_programmer
+ name = "Stockings - Programmer"
icon_state = "stockings_lpink"
-/datum/sprite_accessory/socks/stockings_purple
- name = "Stockings (Purple)"
+/datum/sprite_accessory/underwear/socks/stockings_purple
+ name = "Stockings - Purple"
icon_state = "stockings_purple"
-/datum/sprite_accessory/socks/stockings_yellow
- name = "Stockings (Yellow)"
+/datum/sprite_accessory/underwear/socks/stockings_yellow
+ name = "Stockings - Yellow"
icon_state = "stockings_yellow"
-/datum/sprite_accessory/socks/bee_thigh
- name = "Thigh-high (Bee)"
+/datum/sprite_accessory/underwear/socks/socks_thigh
+ name = "Thigh-high"
+ icon_state = "socks_thigh"
+ has_color = TRUE
+
+/datum/sprite_accessory/underwear/socks/bee_thigh
+ name = "Thigh-high - Bee"
icon_state = "bee_thigh"
-/datum/sprite_accessory/socks/black_thigh
- name = "Thigh-high (Black)"
- icon_state = "black_thigh"
-
-/datum/sprite_accessory/socks/commie_thigh
- name = "Thigh-high (Commie)"
+/datum/sprite_accessory/underwear/socks/commie_thigh
+ name = "Thigh-high - Commie"
icon_state = "commie_thigh"
-/datum/sprite_accessory/socks/usa_thigh
- name = "Thigh-high (Freedom)"
+/datum/sprite_accessory/underwear/socks/usa_thigh
+ name = "Thigh-high - Freedom"
icon_state = "assblastusa_thigh"
-/datum/sprite_accessory/socks/rainbow_thigh
- name = "Thigh-high (Rainbow)"
+/datum/sprite_accessory/underwear/socks/fishnet
+ name = "Thigh-high - Fishnet"
+ icon_state = "fishnet"
+
+/datum/sprite_accessory/underwear/socks/rainbow_thigh
+ name = "Thigh-high - Rainbow"
icon_state = "rainbow_thigh"
-/datum/sprite_accessory/socks/striped_thigh
- name = "Thigh-high (Striped)"
+/datum/sprite_accessory/underwear/socks/striped_thigh
+ name = "Thigh-high - Striped"
icon_state = "striped_thigh"
+ has_color = TRUE
-/datum/sprite_accessory/socks/thin_thigh
- name = "Thigh-high (Thin)"
+/datum/sprite_accessory/underwear/socks/thin_thigh
+ name = "Thigh-high - Thin"
icon_state = "thin_thigh"
+ has_color = TRUE
-/datum/sprite_accessory/socks/uk_thigh
- name = "Thigh-high (UK)"
+/datum/sprite_accessory/underwear/socks/uk_thigh
+ name = "Thigh-high - UK"
icon_state = "uk_thigh"
-
-/datum/sprite_accessory/socks/white_thigh
- name = "Thigh-high (White)"
- icon_state = "white_thigh"
diff --git a/code/modules/mob/dead/new_player/sprite_accessories/tails.dm b/code/modules/mob/dead/new_player/sprite_accessories/tails.dm
index 31faabf663..3975f7bafc 100644
--- a/code/modules/mob/dead/new_player/sprite_accessories/tails.dm
+++ b/code/modules/mob/dead/new_player/sprite_accessories/tails.dm
@@ -4,6 +4,10 @@
/datum/sprite_accessory/tails_animated
icon = 'icons/mob/mutant_bodyparts.dmi'
+/******************************************
+************* Lizard Tails ****************
+*******************************************/
+
/datum/sprite_accessory/tails/lizard/smooth
name = "Smooth"
icon_state = "smooth"
@@ -36,6 +40,48 @@
name = "Spikes"
icon_state = "spikes"
+/datum/sprite_accessory/tails/lizard/none
+ name = "None"
+ icon_state = "None"
+
+/datum/sprite_accessory/tails_animated/lizard/none
+ name = "None"
+ icon_state = "None"
+
+/datum/sprite_accessory/tails/lizard/axolotl
+ name = "Axolotl"
+ icon_state = "axolotl"
+ color_src = MATRIXED
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+
+/datum/sprite_accessory/tails_animated/lizard/axolotl
+ name = "Axolotl"
+ icon_state = "axolotl"
+ color_src = MATRIXED
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+
+/datum/sprite_accessory/body_markings/guilmon
+ name = "Guilmon"
+ icon_state = "guilmon"
+ color_src = MATRIXED
+ icon = 'modular_citadel/icons/mob/markings_notmammals.dmi'
+
+/datum/sprite_accessory/tails/lizard/guilmon
+ name = "Guilmon"
+ icon_state = "guilmon"
+ color_src = MATRIXED
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+
+/datum/sprite_accessory/tails_animated/lizard/guilmon
+ name = "Guilmon"
+ icon_state = "guilmon"
+ color_src = MATRIXED
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+
+/******************************************
+************** Human Tails ****************
+*******************************************/
+
/datum/sprite_accessory/tails/human/none
name = "None"
icon_state = "none"
@@ -43,13 +89,626 @@
/datum/sprite_accessory/tails_animated/human/none
name = "None"
icon_state = "none"
-/*
+
+/datum/sprite_accessory/tails/human/ailurus
+ name = "Red Panda"
+ icon_state = "wah"
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+ color_src = MATRIXED
+
+/datum/sprite_accessory/tails_animated/human/ailurus
+ name = "Red Panda"
+ icon_state = "wah"
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+ color_src = MATRIXED
+
+/datum/sprite_accessory/tails/human/axolotl
+ name = "Axolotl"
+ icon_state = "axolotl"
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+ color_src = MATRIXED
+
+/datum/sprite_accessory/tails_animated/human/axolotl
+ name = "Axolotl"
+ icon_state = "axolotl"
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+ color_src = MATRIXED
+
+/datum/sprite_accessory/tails/human/bee
+ name = "Bee"
+ icon_state = "bee"
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+ color_src = MATRIXED
+
+/datum/sprite_accessory/tails_animated/human/bee
+ name = "Bee"
+ icon_state = "bee"
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+ color_src = MATRIXED
+
/datum/sprite_accessory/tails/human/cat
name = "Cat"
icon_state = "cat"
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
color_src = HAIR
/datum/sprite_accessory/tails_animated/human/cat
name = "Cat"
icon_state = "cat"
- color_src = HAIR*/
\ No newline at end of file
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+ color_src = HAIR
+
+/datum/sprite_accessory/tails/human/catbig
+ name = "Cat, Big"
+ icon_state = "catbig"
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+ color_src = MATRIXED
+
+/datum/sprite_accessory/tails_animated/human/catbig
+ name = "Cat, Big"
+ icon_state = "catbig"
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+ color_src = MATRIXED
+
+/datum/sprite_accessory/tails/human/cow
+ name = "Cow"
+ icon_state = "cow"
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+ color_src = MATRIXED
+
+/datum/sprite_accessory/tails_animated/human/cow
+ name = "Cow"
+ icon_state = "cow"
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+ color_src = MATRIXED
+
+/datum/sprite_accessory/tails/human/corvid
+ name = "Corvid"
+ icon_state = "crow"
+
+/datum/sprite_accessory/tails_animated/human/corvid
+ name = "Corvid"
+ icon_state = "crow"
+
+/datum/sprite_accessory/tails/human/eevee
+ name = "Eevee"
+ icon_state = "eevee"
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+ color_src = MATRIXED
+
+/datum/sprite_accessory/tails_animated/human/eevee
+ name = "Eevee"
+ icon_state = "eevee"
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+ color_src = MATRIXED
+
+/datum/sprite_accessory/tails/human/fennec
+ name = "Fennec"
+ icon_state = "fennec"
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+ color_src = MATRIXED
+
+/datum/sprite_accessory/tails_animated/human/fennec
+ name = "Fennec"
+ icon_state = "fennec"
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+ color_src = MATRIXED
+
+/datum/sprite_accessory/tails/human/fish
+ name = "Fish"
+ icon_state = "fish"
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+ color_src = MATRIXED
+
+/datum/sprite_accessory/tails_animated/human/fish
+ name = "Fish"
+ icon_state = "fish"
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+ color_src = MATRIXED
+
+/datum/sprite_accessory/tails/human/fox
+ name = "Fox"
+ icon_state = "fox"
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+ color_src = MATRIXED
+
+/datum/sprite_accessory/tails_animated/human/fox
+ name = "Fox"
+ icon_state = "fox"
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+ color_src = MATRIXED
+
+/datum/sprite_accessory/tails/human/horse
+ name = "Horse"
+ icon_state = "horse"
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+ color_src = HAIR
+
+/datum/sprite_accessory/tails_animated/human/horse
+ name = "Horse"
+ icon_state = "horse"
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+ color_src = HAIR
+
+/datum/sprite_accessory/tails/human/husky
+ name = "Husky"
+ icon_state = "husky"
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+ color_src = MATRIXED
+
+/datum/sprite_accessory/tails_animated/human/husky
+ name = "Husky"
+ icon_state = "husky"
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+ color_src = MATRIXED
+
+/datum/sprite_accessory/tails/human/insect
+ name = "Insect"
+ icon_state = "insect"
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+ color_src = MATRIXED
+
+/datum/sprite_accessory/tails_animated/human/insect
+ name = "insect"
+ icon_state = "insect"
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+ color_src = MATRIXED
+
+/datum/sprite_accessory/tails/human/kitsune
+ name = "Kitsune"
+ icon_state = "kitsune"
+ color_src = MATRIXED
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+
+/datum/sprite_accessory/tails_animated/human/kitsune
+ name = "Kitsune"
+ icon_state = "kitsune"
+ color_src = MATRIXED
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+
+/datum/sprite_accessory/tails/human/murid
+ name = "Murid"
+ icon_state = "murid"
+ color_src = MATRIXED
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+
+/datum/sprite_accessory/tails_animated/human/murid
+ name = "Murid"
+ icon_state = "murid"
+ color_src = MATRIXED
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+
+/datum/sprite_accessory/tails/human/otie
+ name = "Otusian"
+ icon_state = "otie"
+ color_src = MATRIXED
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+
+/datum/sprite_accessory/tails_animated/human/otie
+ name = "Otusian"
+ icon_state = "otie"
+ color_src = MATRIXED
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+
+/datum/sprite_accessory/tails/orca
+ name = "Orca"
+ icon_state = "orca"
+ color_src = MATRIXED
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+
+/datum/sprite_accessory/tails_animated/orca
+ name = "Orca"
+ icon_state = "orca"
+ color_src = MATRIXED
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+
+/datum/sprite_accessory/tails/human/pede
+ name = "Scolipede"
+ icon_state = "pede"
+ color_src = MATRIXED
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+
+/datum/sprite_accessory/tails_animated/human/pede
+ name = "Scolipede"
+ icon_state = "pede"
+ color_src = MATRIXED
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+
+/datum/sprite_accessory/tails/human/rabbit
+ name = "Rabbit"
+ icon_state = "rabbit"
+ color_src = MATRIXED
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+
+/datum/sprite_accessory/tails_animated/human/rabbit
+ name = "Rabbit"
+ icon_state = "rabbit"
+ color_src = MATRIXED
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+
+/datum/sprite_accessory/tails/human/sergal
+ name = "Sergal"
+ icon_state = "sergal"
+ color_src = MATRIXED
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+
+/datum/sprite_accessory/tails_animated/human/sergal
+ name = "Sergal"
+ icon_state = "sergal"
+ color_src = MATRIXED
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+
+/datum/sprite_accessory/tails/human/skunk
+ name = "skunk"
+ icon_state = "skunk"
+ color_src = MATRIXED
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+
+/datum/sprite_accessory/tails_animated/human/skunk
+ name = "skunk"
+ icon_state = "skunk"
+ color_src = MATRIXED
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+
+/datum/sprite_accessory/tails/human/shark
+ name = "Shark"
+ icon_state = "shark"
+ color_src = MATRIXED
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+
+/datum/sprite_accessory/tails_animated/human/shark
+ name = "Shark"
+ icon_state = "shark"
+ color_src = MATRIXED
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+
+/datum/sprite_accessory/tails/human/datashark
+ name = "datashark"
+ icon_state = "datashark"
+ color_src = MATRIXED
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+
+/datum/sprite_accessory/tails_animated/human/datashark
+ name = "datashark"
+ icon_state = "datashark"
+ color_src = MATRIXED
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+
+/datum/sprite_accessory/tails/human/straighttail
+ name = "Straight Tail"
+ icon_state = "straighttail"
+
+/datum/sprite_accessory/tails_animated/human/straighttail
+ name = "Straight Tail"
+ icon_state = "straighttail"
+
+/datum/sprite_accessory/tails/human/squirrel
+ name = "Squirrel"
+ icon_state = "squirrel"
+ color_src = MATRIXED
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+
+/datum/sprite_accessory/tails_animated/human/squirrel
+ name = "Squirrel"
+ icon_state = "squirrel"
+ color_src = MATRIXED
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+
+/datum/sprite_accessory/tails/human/tentacle
+ name = "Tentacle"
+ icon_state = "tentacle"
+ color_src = MATRIXED
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+
+/datum/sprite_accessory/tails_animated/human/tentacle
+ name = "Tentacle"
+ icon_state = "tentacle"
+ color_src = MATRIXED
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+
+/datum/sprite_accessory/tails/human/tiger
+ name = "Tiger"
+ icon_state = "tiger"
+ color_src = MATRIXED
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+
+/datum/sprite_accessory/tails_animated/human/tiger
+ name = "Tiger"
+ icon_state = "tiger"
+ color_src = MATRIXED
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+
+/datum/sprite_accessory/tails/human/wolf
+ name = "Wolf"
+ icon_state = "wolf"
+ color_src = MATRIXED
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+
+/datum/sprite_accessory/tails_animated/human/wolf
+ name = "Wolf"
+ icon_state = "wolf"
+ color_src = MATRIXED
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+
+/******************************************
+************** Furry Tails ****************
+*******************************************/
+
+/datum/sprite_accessory/mam_tails
+ color_src = MATRIXED
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+
+/datum/sprite_accessory/mam_tails/none
+ name = "None"
+ icon_state = "none"
+
+/datum/sprite_accessory/mam_tails_animated
+ color_src = MATRIXED
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+
+/datum/sprite_accessory/mam_tails_animated/none
+ name = "None"
+ icon_state = "none"
+ color_src = MATRIXED
+
+/datum/sprite_accessory/mam_tails/ailurus
+ name = "Red Panda"
+ icon_state = "wah"
+ extra = TRUE
+
+/datum/sprite_accessory/mam_tails_animated/ailurus
+ name = "Red Panda"
+ icon_state = "wah"
+ extra = TRUE
+
+/datum/sprite_accessory/mam_tails/axolotl
+ name = "Axolotl"
+ icon_state = "axolotl"
+
+/datum/sprite_accessory/mam_tails_animated/axolotl
+ name = "Axolotl"
+ icon_state = "axolotl"
+
+/datum/sprite_accessory/mam_tails/bee
+ name = "Bee"
+ icon_state = "bee"
+
+/datum/sprite_accessory/mam_tails_animated/bee
+ name = "Bee"
+ icon_state = "bee"
+
+/datum/sprite_accessory/mam_tails/cat
+ name = "Cat"
+ icon_state = "cat"
+ color_src = HAIR
+
+/datum/sprite_accessory/mam_tails_animated/cat
+ name = "Cat"
+ icon_state = "cat"
+ color_src = HAIR
+
+/datum/sprite_accessory/mam_tails/catbig
+ name = "Cat, Big"
+ icon_state = "catbig"
+
+/datum/sprite_accessory/mam_tails_animated/catbig
+ name = "Cat, Big"
+ icon_state = "catbig"
+
+/datum/sprite_accessory/mam_tails/corvid
+ name = "Corvid"
+ icon_state = "crow"
+
+/datum/sprite_accessory/mam_tails_animated/corvid
+ name = "Corvid"
+ icon_state = "crow"
+
+/datum/sprite_accessory/mam_tail/cow
+ name = "Cow"
+ icon_state = "cow"
+
+/datum/sprite_accessory/mam_tails_animated/cow
+ name = "Cow"
+ icon_state = "cow"
+
+/datum/sprite_accessory/mam_tails/eevee
+ name = "Eevee"
+ icon_state = "eevee"
+
+/datum/sprite_accessory/mam_tails_animated/eevee
+ name = "Eevee"
+ icon_state = "eevee"
+
+/datum/sprite_accessory/mam_tails/fennec
+ name = "Fennec"
+ icon_state = "fennec"
+
+/datum/sprite_accessory/mam_tails_animated/fennec
+ name = "Fennec"
+ icon_state = "fennec"
+
+/datum/sprite_accessory/mam_tails/human/fish
+ name = "Fish"
+ icon_state = "fish"
+
+/datum/sprite_accessory/mam_tails_animated/human/fish
+ name = "Fish"
+ icon_state = "fish"
+
+/datum/sprite_accessory/mam_tails/fox
+ name = "Fox"
+ icon_state = "fox"
+
+/datum/sprite_accessory/mam_tails_animated/fox
+ name = "Fox"
+ icon_state = "fox"
+
+/datum/sprite_accessory/mam_tails/hawk
+ name = "Hawk"
+ icon_state = "hawk"
+
+/datum/sprite_accessory/mam_tails_animated/hawk
+ name = "Hawk"
+ icon_state = "hawk"
+
+/datum/sprite_accessory/mam_tails/horse
+ name = "Horse"
+ icon_state = "horse"
+ color_src = HAIR
+
+/datum/sprite_accessory/mam_tails_animated/horse
+ name = "Horse"
+ icon_state = "horse"
+ color_src = HAIR
+
+/datum/sprite_accessory/mam_tails/husky
+ name = "Husky"
+ icon_state = "husky"
+
+/datum/sprite_accessory/mam_tails_animated/husky
+ name = "Husky"
+ icon_state = "husky"
+
+datum/sprite_accessory/mam_tails/insect
+ name = "Insect"
+ icon_state = "insect"
+
+/datum/sprite_accessory/mam_tails_animated/insect
+ name = "Insect"
+ icon_state = "insect"
+
+/datum/sprite_accessory/mam_tails/kangaroo
+ name = "kangaroo"
+ icon_state = "kangaroo"
+
+/datum/sprite_accessory/mam_tails_animated/kangaroo
+ name = "kangaroo"
+ icon_state = "kangaroo"
+
+/datum/sprite_accessory/mam_tails/kitsune
+ name = "Kitsune"
+ icon_state = "kitsune"
+
+/datum/sprite_accessory/mam_tails_animated/kitsune
+ name = "Kitsune"
+ icon_state = "kitsune"
+
+/datum/sprite_accessory/mam_tails/lab
+ name = "Lab"
+ icon_state = "lab"
+
+/datum/sprite_accessory/mam_tails_animated/lab
+ name = "Lab"
+ icon_state = "lab"
+
+/datum/sprite_accessory/mam_tails/murid
+ name = "Murid"
+ icon_state = "murid"
+
+/datum/sprite_accessory/mam_tails_animated/murid
+ name = "Murid"
+ icon_state = "murid"
+
+/datum/sprite_accessory/mam_tails/otie
+ name = "Otusian"
+ icon_state = "otie"
+
+/datum/sprite_accessory/mam_tails_animated/otie
+ name = "Otusian"
+ icon_state = "otie"
+
+/datum/sprite_accessory/mam_tails/orca
+ name = "Orca"
+ icon_state = "orca"
+
+/datum/sprite_accessory/mam_tails_animated/orca
+ name = "Orca"
+ icon_state = "orca"
+
+/datum/sprite_accessory/mam_tails/pede
+ name = "Scolipede"
+ icon_state = "pede"
+
+/datum/sprite_accessory/mam_tails_animated/pede
+ name = "Scolipede"
+ icon_state = "pede"
+
+/datum/sprite_accessory/mam_tails/rabbit
+ name = "Rabbit"
+ icon_state = "rabbit"
+
+/datum/sprite_accessory/mam_tails_animated/rabbit
+ name = "Rabbit"
+ icon_state = "rabbit"
+
+/datum/sprite_accessory/mam_tails/sergal
+ name = "Sergal"
+ icon_state = "sergal"
+
+/datum/sprite_accessory/mam_tails_animated/sergal
+ name = "Sergal"
+ icon_state = "sergal"
+
+/datum/sprite_accessory/mam_tails/skunk
+ name = "Skunk"
+ icon_state = "skunk"
+
+/datum/sprite_accessory/mam_tails_animated/skunk
+ name = "Skunk"
+ icon_state = "skunk"
+
+/datum/sprite_accessory/mam_tails/shark
+ name = "Shark"
+ icon_state = "shark"
+
+/datum/sprite_accessory/mam_tails_animated/shark
+ name = "Shark"
+ icon_state = "shark"
+
+/datum/sprite_accessory/mam_tails/shepherd
+ name = "Shepherd"
+ icon_state = "shepherd"
+
+/datum/sprite_accessory/mam_tails_animated/shepherd
+ name = "Shepherd"
+ icon_state = "shepherd"
+
+/datum/sprite_accessory/mam_tails/straighttail
+ name = "Straight Tail"
+ icon_state = "straighttail"
+
+/datum/sprite_accessory/mam_tails_animated/straighttail
+ name = "Straight Tail"
+ icon_state = "straighttail"
+
+/datum/sprite_accessory/mam_tails/squirrel
+ name = "Squirrel"
+ icon_state = "squirrel"
+
+/datum/sprite_accessory/mam_tails_animated/squirrel
+ name = "Squirrel"
+ icon_state = "squirrel"
+
+/datum/sprite_accessory/mam_tails/tentacle
+ name = "Tentacle"
+ icon_state = "tentacle"
+
+/datum/sprite_accessory/mam_tails_animated/tentacle
+ name = "Tentacle"
+ icon_state = "tentacle"
+
+/datum/sprite_accessory/mam_tails/tiger
+ name = "Tiger"
+ icon_state = "tiger"
+
+/datum/sprite_accessory/mam_tails_animated/tiger
+ name = "Tiger"
+ icon_state = "tiger"
+
+/datum/sprite_accessory/mam_tails/wolf
+ name = "Wolf"
+ icon_state = "wolf"
+
+/datum/sprite_accessory/mam_tails_animated/wolf
+ name = "Wolf"
+ icon_state = "wolf"
diff --git a/code/modules/mob/dead/new_player/sprite_accessories/undershirt.dm b/code/modules/mob/dead/new_player/sprite_accessories/undershirt.dm
index f5af9a3849..fb40563ccf 100644
--- a/code/modules/mob/dead/new_player/sprite_accessories/undershirt.dm
+++ b/code/modules/mob/dead/new_player/sprite_accessories/undershirt.dm
@@ -2,311 +2,312 @@
// Undershirt Definitions //
////////////////////////////
-/datum/sprite_accessory/undershirt
- icon = 'icons/mob/underwear.dmi'
- gender = NEUTER
-
-/datum/sprite_accessory/undershirt/nude
+/datum/sprite_accessory/underwear/top/nude
name = "Nude"
icon_state = null
// please make sure they're sorted alphabetically and categorized
-/datum/sprite_accessory/undershirt/bluejersey
+/datum/sprite_accessory/underwear/top/longjon
+ name = "Long John Shirt"
+ icon_state = "ljont"
+ has_color = TRUE
+
+/datum/sprite_accessory/underwear/top/longstripe_black
+ name = "Longsleeve Striped Shirt - Black"
+ icon_state = "longstripe"
+
+/datum/sprite_accessory/underwear/top/longstripe_blue
+ name = "Longsleeve Striped Shirt - Blue"
+ icon_state = "longstripe_blue"
+
+/datum/sprite_accessory/underwear/top/shirt
+ name = "Shirt"
+ icon_state = "undershirt"
+ has_color = TRUE
+
+/datum/sprite_accessory/underwear/top/bowlingw
+ name = "Shirt - Bowling"
+ icon_state = "bowlingw"
+ has_color = TRUE
+
+/datum/sprite_accessory/underwear/top/bowling
+ name = "Shirt, Bowling - Red"
+ icon_state = "bowling"
+
+/datum/sprite_accessory/underwear/top/bowlingp
+ name = "Shirt, Bowling - Pink"
+ icon_state = "bowlingp"
+
+/datum/sprite_accessory/underwear/top/bowlinga
+ name = "Shirt, Bowling - Aqua"
+ icon_state = "bowlinga"
+
+/datum/sprite_accessory/underwear/top/bluejersey
name = "Shirt, Jersey - Blue"
icon_state = "shirt_bluejersey"
-/datum/sprite_accessory/undershirt/redjersey
+/datum/sprite_accessory/underwear/top/redjersey
name = "Shirt, Jersey - Red"
icon_state = "shirt_redjersey"
-/datum/sprite_accessory/undershirt/bluepolo
- name = "Shirt, Polo - Blue"
- icon_state = "bluepolo"
+/datum/sprite_accessory/underwear/top/polo
+ name = "Shirt - Polo"
+ icon_state = "polo"
+ has_color = TRUE
-/datum/sprite_accessory/undershirt/grayyellowpolo
- name = "Shirt, Polo - Gray, Yellow"
- icon_state = "grayyellowpolo"
-
-/datum/sprite_accessory/undershirt/redpolo
- name = "Shirt, Polo - Red"
- icon_state = "redpolo"
-
-/datum/sprite_accessory/undershirt/whitepolo
- name = "Shirt, Polo - White"
- icon_state = "whitepolo"
-
-/datum/sprite_accessory/undershirt/alienshirt
+/datum/sprite_accessory/underwear/top/alienshirt
name = "Shirt - Alien"
icon_state = "shirt_alien"
-/datum/sprite_accessory/undershirt/mondmondjaja
+/datum/sprite_accessory/underwear/top/mondmondjaja
name = "Shirt - Band"
icon_state = "band"
-/datum/sprite_accessory/undershirt/shirt_black
- name = "Shirt - Black"
- icon_state = "shirt_black"
+/datum/sprite_accessory/underwear/top/shirt_bee
+ name = "Shirt - Bee"
+ icon_state = "bee_shirt"
-/datum/sprite_accessory/undershirt/blueshirt
- name = "Shirt - Blue"
- icon_state = "shirt_blue"
-
-/datum/sprite_accessory/undershirt/clownshirt
+/datum/sprite_accessory/underwear/top/clownshirt
name = "Shirt - Clown"
icon_state = "shirt_clown"
-/datum/sprite_accessory/undershirt/commie
+/datum/sprite_accessory/underwear/top/commie
name = "Shirt - Commie"
icon_state = "shirt_commie"
-/datum/sprite_accessory/undershirt/greenshirt
- name = "Shirt - Green"
- icon_state = "shirt_green"
-
-/datum/sprite_accessory/undershirt/shirt_grey
- name = "Shirt - Grey"
- icon_state = "shirt_grey"
-
-/datum/sprite_accessory/undershirt/ian
+/datum/sprite_accessory/underwear/top/ian
name = "Shirt - Ian"
icon_state = "ian"
-/datum/sprite_accessory/undershirt/ilovent
+/datum/sprite_accessory/underwear/top/ilovent
name = "Shirt - I Love NT"
icon_state = "ilovent"
-/datum/sprite_accessory/undershirt/lover
+/datum/sprite_accessory/underwear/top/lover
name = "Shirt - Lover"
icon_state = "lover"
-/datum/sprite_accessory/undershirt/matroska
+/datum/sprite_accessory/underwear/top/matroska
name = "Shirt - Matroska"
icon_state = "matroska"
-/datum/sprite_accessory/undershirt/meat
+/datum/sprite_accessory/underwear/top/meat
name = "Shirt - Meat"
icon_state = "shirt_meat"
-/datum/sprite_accessory/undershirt/nano
+/datum/sprite_accessory/underwear/top/nano
name = "Shirt - Nanotrasen"
icon_state = "shirt_nano"
-/datum/sprite_accessory/undershirt/peace
+/datum/sprite_accessory/underwear/top/peace
name = "Shirt - Peace"
icon_state = "peace"
-/datum/sprite_accessory/undershirt/pacman
+/datum/sprite_accessory/underwear/top/pacman
name = "Shirt - Pogoman"
icon_state = "pogoman"
-/datum/sprite_accessory/undershirt/question
+/datum/sprite_accessory/underwear/top/question
name = "Shirt - Question"
icon_state = "shirt_question"
-/datum/sprite_accessory/undershirt/redshirt
- name = "Shirt - Red"
- icon_state = "shirt_red"
-
-/datum/sprite_accessory/undershirt/skull
+/datum/sprite_accessory/underwear/top/skull
name = "Shirt - Skull"
icon_state = "shirt_skull"
-/datum/sprite_accessory/undershirt/ss13
+/datum/sprite_accessory/underwear/top/ss13
name = "Shirt - SS13"
icon_state = "shirt_ss13"
+ has_color = TRUE
-/datum/sprite_accessory/undershirt/stripe
+/datum/sprite_accessory/underwear/top/stripe
name = "Shirt - Striped"
icon_state = "shirt_stripes"
-/datum/sprite_accessory/undershirt/tiedye
+/datum/sprite_accessory/underwear/top/tiedye
name = "Shirt - Tie-dye"
icon_state = "shirt_tiedye"
-/datum/sprite_accessory/undershirt/uk
+/datum/sprite_accessory/underwear/top/uk
name = "Shirt - UK"
icon_state = "uk"
-/datum/sprite_accessory/undershirt/usa
+/datum/sprite_accessory/underwear/top/usa
name = "Shirt - USA"
icon_state = "shirt_assblastusa"
-/datum/sprite_accessory/undershirt/shirt_white
- name = "Shirt - White"
- icon_state = "shirt_white"
+/datum/sprite_accessory/underwear/top/shortsleeve
+ name = "Shirt - Short Sleeved"
+ icon_state = "shortsleeve"
+ has_color = TRUE
-/datum/sprite_accessory/undershirt/blackshortsleeve
- name = "Shirt, Short Sleeved - Black"
- icon_state = "blackshortsleeve"
-
-/datum/sprite_accessory/undershirt/blueshortsleeve
- name = "Shirt, Short Sleeved - Blue"
- icon_state = "blueshortsleeve"
-
-/datum/sprite_accessory/undershirt/greenshortsleeve
- name = "Shirt, Short Sleeved - Green"
- icon_state = "greenshortsleeve"
-
-/datum/sprite_accessory/undershirt/purpleshortsleeve
- name = "Shirt, Short Sleeved - Purple"
- icon_state = "purpleshortsleeve"
-
-/datum/sprite_accessory/undershirt/whiteshortsleeve
- name = "Shirt, Short Sleeved - White"
- icon_state = "whiteshortsleeve"
-
-/datum/sprite_accessory/undershirt/blueshirtsport
+/datum/sprite_accessory/underwear/top/blueshirtsport
name = "Shirt, Sports - Blue"
icon_state = "blueshirtsport"
- gender = NEUTER
-/datum/sprite_accessory/undershirt/greenshirtsport
+/datum/sprite_accessory/underwear/top/greenshirtsport
name = "Shirt, Sports - Green"
icon_state = "greenshirtsport"
- gender = NEUTER
-/datum/sprite_accessory/undershirt/redshirtsport
+/datum/sprite_accessory/underwear/top/redshirtsport
name = "Shirt, Sports - Red"
icon_state = "redshirtsport"
- gender = NEUTER
-/datum/sprite_accessory/undershirt/redtop
- name = "Shirt, Short - Red"
- icon_state = "redtop"
-
-/datum/sprite_accessory/undershirt/whitetop
- name = "Shirt, Short - White"
- icon_state = "whitetop"
-
-/datum/sprite_accessory/undershirt/tshirt_blue
- name = "T-Shirt - Blue"
- icon_state = "blueshirt"
-
-/datum/sprite_accessory/undershirt/tshirt_green
- name = "T-Shirt - Green"
- icon_state = "greenshirt"
-
-/datum/sprite_accessory/undershirt/tshirt_red
- name = "T-Shirt - Red"
- icon_state = "redshirt"
-
-/datum/sprite_accessory/undershirt/yellowshirt
- name = "T-Shirt - Yellow"
- icon_state = "yellowshirt"
-
-/datum/sprite_accessory/undershirt/tank_black
- name = "Tank Top - Black"
- icon_state = "tank_black"
-
-/datum/sprite_accessory/undershirt/tankfire
+/datum/sprite_accessory/underwear/top/tankfire
name = "Tank Top - Fire"
icon_state = "tank_fire"
-/datum/sprite_accessory/undershirt/tank_grey
- name = "Tank Top - Grey"
- icon_state = "tank_grey"
+/datum/sprite_accessory/underwear/top/tanktop
+ name = "Tank Top"
+ icon_state = "tanktop"
+ has_color = TRUE
-/datum/sprite_accessory/undershirt/female_midriff
+/datum/sprite_accessory/underwear/top/tanktop_alt
+ name = "Tank Top - Alt"
+ icon_state = "tanktop_alt"
+ has_color = TRUE
+
+/datum/sprite_accessory/underwear/top/tanktop_midriff
name = "Tank Top - Midriff"
icon_state = "tank_midriff"
+ has_color = TRUE
+ gender = FEMALE
-/datum/sprite_accessory/undershirt/tank_red
- name = "Tank Top - Red"
- icon_state = "tank_red"
+/datum/sprite_accessory/underwear/top/tanktop_midriff_alt
+ name = "Tank Top - Midriff Halterneck"
+ icon_state = "tank_midriff_alt"
+ has_color = TRUE
+ gender = FEMALE
-/datum/sprite_accessory/undershirt/tankstripe
+/datum/sprite_accessory/underwear/top/tankstripe
name = "Tank Top - Striped"
icon_state = "tank_stripes"
-/datum/sprite_accessory/undershirt/tank_white
- name = "Tank Top - White"
- icon_state = "tank_white"
+/datum/sprite_accessory/underwear/top/tank_top_sun
+ name = "Tank top - Sun"
+ icon_state = "tank_sun"
-/datum/sprite_accessory/undershirt/female_red
- name = "Bra - Red"
- icon_state = "bra_red"
+/datum/sprite_accessory/underwear/top/babydoll
+ name = "Baby-Doll"
+ icon_state = "babydoll"
+ has_color = TRUE
+ gender = FEMALE
-/datum/sprite_accessory/undershirt/female_pink
- name = "Bra - Pink"
- icon_state = "bra_pink"
+/datum/sprite_accessory/underwear/top/bra
+ name = "Bra"
+ icon_state = "bra"
+ has_color = TRUE
+ gender = FEMALE
-/datum/sprite_accessory/undershirt/female_kinky
+/datum/sprite_accessory/underwear/top/bra_alt
+ name = "Bra - Alt"
+ icon_state = "bra_alt"
+ has_color = TRUE
+ gender = FEMALE
+
+/datum/sprite_accessory/underwear/top/bra_thin
+ name = "Bra - Thin"
+ icon_state = "bra_thin"
+ has_color = TRUE
+ gender = FEMALE
+
+/datum/sprite_accessory/underwear/top/bra_kinky
name = "Bra - Kinky Black"
icon_state = "bra_kinky"
+ gender = FEMALE
-/datum/sprite_accessory/undershirt/female_green
- name = "Bra - Green"
- icon_state = "bra_green"
-
-/datum/sprite_accessory/undershirt/female_commie
+/datum/sprite_accessory/underwear/top/bra_freedom
name = "Bra - Freedom"
icon_state = "bra_assblastusa"
+ gender = FEMALE
-/datum/sprite_accessory/undershirt/female_commie
+/datum/sprite_accessory/underwear/top/bra_commie
name = "Bra - Commie"
icon_state = "bra_commie"
+ gender = FEMALE
-/datum/sprite_accessory/undershirt/female_babyblue
- name = "Bra - Baby Blue"
- icon_state = "bra_babyblue"
-
-/datum/sprite_accessory/undershirt/female_beekini
+/datum/sprite_accessory/underwear/top/bra_beekini
name = "Bra - Bee-kini"
icon_state = "bra_bee-kini"
+ gender = FEMALE
-/datum/sprite_accessory/undershirt/female_black
- name = "Bra - Black"
- icon_state = "bra_black"
-
-/datum/sprite_accessory/undershirt/female_uk
+/datum/sprite_accessory/underwear/top/bra_uk
name = "Bra - UK"
icon_state = "bra_uk"
+ gender = FEMALE
-/datum/sprite_accessory/undershirt/female_white
- name = "Bra - White"
- icon_state = "bra_white"
+/datum/sprite_accessory/underwear/top/bra_neko
+ name = "Bra - Neko"
+ icon_state = "bra_neko"
+ has_color = TRUE
+ gender = FEMALE
-/datum/sprite_accessory/undershirt/female_white_neko
- name = "Bra, Neko - white"
- icon_state = "bra_neko_white"
+/datum/sprite_accessory/underwear/top/halterneck_bra
+ name = "Bra - Halterneck"
+ icon_state = "halterneck_bra"
+ has_color = TRUE
+ gender = FEMALE
-/datum/sprite_accessory/undershirt/female_black_neko
- name = "Bra, Neko - Black"
- icon_state = "bra_neko_black"
-
-/datum/sprite_accessory/undershirt/female_blackalt
- name = "Bra, Sports - Black"
- icon_state = "bra_sports_black"
-
-/datum/sprite_accessory/undershirt/sports_bra
- name = "Bra, Sports 1 - White"
+/datum/sprite_accessory/underwear/top/sports_bra
+ name = "Bra, Sports"
icon_state = "sports_bra"
+ has_color = TRUE
+ gender = FEMALE
-/datum/sprite_accessory/undershirt/female_whitealt
- name = "Bra, Sports 2 - White"
- icon_state = "bra_sports_white"
-
-/datum/sprite_accessory/undershirt/sports_bra2
- name = "Bra, Sports 3 - White"
+/datum/sprite_accessory/underwear/top/sports_bra_alt
+ name = "Bra, Sports - Alt"
icon_state = "sports_bra_alt"
+ has_color = TRUE
+ gender = FEMALE
-/datum/sprite_accessory/undershirt/female_yellow
- name = "Bra - Yellow"
- icon_state = "bra_yellow"
+/datum/sprite_accessory/underwear/top/bra_strapless
+ name = "Bra, Strapless"
+ icon_state = "bra_strapless"
+ has_color = TRUE
+ gender = FEMALE
-/datum/sprite_accessory/undershirt/female_thong
- name = "Bra, Strapless - Pink"
- icon_state = "bra_strapless_pink"
-
-/datum/sprite_accessory/undershirt/female_blue
- name = "Bra, Strapless - Blue"
+/datum/sprite_accessory/underwear/top/bra_strapless_alt
+ name = "Bra, Strapless - Alt"
icon_state = "bra_blue"
+ has_color = TRUE
+ gender = FEMALE
-/datum/sprite_accessory/undershirt/swimsuit_green
- name = "Swimsuit, Top - Green"
- icon_state = "bra_swimming_green"
+/datum/sprite_accessory/underwear/top/striped_bra
+ name = "Bra - Striped"
+ icon_state = "striped_bra"
+ has_color = TRUE
+ gender = FEMALE
-/datum/sprite_accessory/undershirt/swimsuit_purple
- name = "Swimsuit, Top - Purple"
- icon_state = "bra_swimming_purple"
\ No newline at end of file
+/datum/sprite_accessory/underwear/top/fishnet_sleeves
+ name = "Fishnet - sleeves"
+ icon_state = "fishnet_sleeves"
+ gender = FEMALE
+
+/datum/sprite_accessory/underwear/top/fishnet_gloves
+ name = "Fishnet - gloves"
+ icon_state = "fishnet_gloves"
+ gender = FEMALE
+
+/datum/sprite_accessory/underwear/top/fishnet_base
+ name = "Fishnet - top"
+ icon_state = "fishnet_body"
+ gender = FEMALE
+
+/datum/sprite_accessory/underwear/top/swimsuit
+ name = "Swimsuit Top"
+ icon_state = "bra_swimming"
+ has_color = TRUE
+ gender = FEMALE
+
+/datum/sprite_accessory/underwear/top/swimsuit_alt
+ name = "Swimsuit Top - Strapless"
+ icon_state = "bra_swimming_alt"
+ has_color = TRUE
+ gender = FEMALE
+
+/datum/sprite_accessory/underwear/top/tubetop
+ name = "Tube Top"
+ icon_state = "tubetop"
+ has_color = TRUE
+ gender = FEMALE
\ No newline at end of file
diff --git a/code/modules/mob/dead/new_player/sprite_accessories/underwear.dm b/code/modules/mob/dead/new_player/sprite_accessories/underwear.dm
index e7179eeb87..3356804cb3 100644
--- a/code/modules/mob/dead/new_player/sprite_accessories/underwear.dm
+++ b/code/modules/mob/dead/new_player/sprite_accessories/underwear.dm
@@ -1,174 +1,172 @@
///////////////////////////
// Underwear Definitions //
///////////////////////////
-/datum/sprite_accessory/underwear
- icon = 'icons/mob/underwear.dmi'
- gender = NEUTER
-/datum/sprite_accessory/underwear/nude
+/datum/sprite_accessory/underwear/bottom/nude
name = "Nude"
icon_state = null
-/datum/sprite_accessory/underwear/mankini
- name = "Mankini - Green"
- icon_state = "mankini_green"
+/datum/sprite_accessory/underwear/bottom/mankini
+ name = "Mankini"
+ icon_state = "mankini"
+ has_color = TRUE
+ gender = MALE
-/datum/sprite_accessory/underwear/male_kinky
- name = "Jockstrap - White"
- icon_state = "jockstrap_white"
+/datum/sprite_accessory/underwear/bottom/male_kinky
+ name = "Jockstrap"
+ icon_state = "jockstrap"
+ has_color = TRUE
+ gender = MALE
-/datum/sprite_accessory/underwear/male_white
- name = "Briefs - White"
- icon_state = "briefs_white"
+/datum/sprite_accessory/underwear/bottom/briefs
+ name = "Briefs"
+ icon_state = "briefs"
+ has_color = TRUE
+ gender = MALE
-/datum/sprite_accessory/underwear/male_black
- name = "Briefs - Black"
- icon_state = "briefs_black"
+/datum/sprite_accessory/underwear/bottom/boxers
+ name = "Boxers"
+ icon_state = "boxers"
+ has_color = TRUE
+ gender = MALE
-/datum/sprite_accessory/underwear/male_grey
- name = "Briefs - Grey"
- icon_state = "briefs_grey"
+/datum/sprite_accessory/underwear/bottom/male_bee
+ name = "Boxers - Bee"
+ icon_state = "bee_shorts"
+ gender = MALE
-/datum/sprite_accessory/underwear/male_red
- name = "Briefs - Red"
- icon_state = "briefs_red"
-
-/datum/sprite_accessory/underwear/male_green
- name = "Briefs - Green"
- icon_state = "briefs_green"
-
-/datum/sprite_accessory/underwear/male_blue
- name = "Briefs - Blue"
- icon_state = "briefs_blue"
-
-/datum/sprite_accessory/underwear/male_blackalt
- name = "Boxers - Black"
- icon_state = "boxers_black"
-
-/datum/sprite_accessory/underwear/male_greyalt
- name = "Boxers - Grey"
- icon_state = "boxers_grey"
-
-/datum/sprite_accessory/underwear/male_hearts
+/datum/sprite_accessory/underwear/bottom/male_hearts
name = "Boxers - Heart"
icon_state = "boxers_heart"
+ gender = MALE
-/datum/sprite_accessory/underwear/male_stripe
+/datum/sprite_accessory/underwear/bottom/male_stripe
name = "Boxers - Striped"
icon_state = "boxers_striped"
+ gender = MALE
-/datum/sprite_accessory/underwear/male_commie
+/datum/sprite_accessory/underwear/bottom/male_commie
name = "Boxers - Striped Communist"
icon_state = "boxers_commie"
+ gender = MALE
-/datum/sprite_accessory/underwear/male_usastripe
+/datum/sprite_accessory/underwear/bottom/male_usastripe
name = "Boxers - Striped Freedom"
icon_state = "boxers_assblastusa"
+ gender = MALE
-/datum/sprite_accessory/underwear/male_uk
+/datum/sprite_accessory/underwear/bottom/male_uk
name = "Boxers - Striped UK"
icon_state = "boxers_uk"
+ gender = MALE
+/datum/sprite_accessory/underwear/bottom/boxer_briefs
+ name = "Boxer Briefs"
+ icon_state = "boxer_briefs"
+ has_color = TRUE
-/datum/sprite_accessory/underwear/female_whitealt
- name = "Boxer Briefs - White"
- icon_state = "boxer_briefs_white"
+/datum/sprite_accessory/underwear/bottom/panties
+ name = "Panties"
+ icon_state = "panties"
+ has_color = TRUE
+ gender = FEMALE
-/datum/sprite_accessory/underwear/female_blackalt
- name = "Boxer Briefs - Black"
- icon_state = "boxer_briefs_black"
+/datum/sprite_accessory/underwear/bottom/panties_alt
+ name = "Panties - Alt"
+ icon_state = "panties_alt"
+ has_color = TRUE
+ gender = FEMALE
-/datum/sprite_accessory/underwear/female_pink
- name = "Boxer Briefs - Pink"
- icon_state = "boxer_briefs_pink"
+/datum/sprite_accessory/underwear/bottom/fishnet_lower
+ name = "Panties - Fishnet"
+ icon_state = "fishnet_lower"
+ gender = FEMALE
-/datum/sprite_accessory/underwear/female_babyblue
- name = "Boxer Briefs - Baby Blue"
- icon_state = "boxer_briefs_babyblue"
-
-/datum/sprite_accessory/underwear/female_yellow
- name = "Boxer Briefs - Yellow"
- icon_state = "boxer_briefs_yellow"
-
-/datum/sprite_accessory/underwear/female_beekini
+/datum/sprite_accessory/underwear/bottom/female_beekini
name = "Panties - Bee-kini"
icon_state = "panties_bee-kini"
+ gender = FEMALE
-/datum/sprite_accessory/underwear/female_black
- name = "Panties - Black"
- icon_state = "panties_black"
-
-/datum/sprite_accessory/underwear/female_blue
- name = "Panties - Blue"
- icon_state = "panties_blue"
-
-/datum/sprite_accessory/underwear/female_commie
+/datum/sprite_accessory/underwear/bottom/female_commie
name = "Panties - Commie"
icon_state = "panties_commie"
+ gender = FEMALE
-/datum/sprite_accessory/underwear/female_usastripe
+/datum/sprite_accessory/underwear/bottom/female_usastripe
name = "Panties - Freedom"
icon_state = "panties_assblastusa"
+ gender = FEMALE
-/datum/sprite_accessory/underwear/female_green
- name = "Panties - Green"
- icon_state = "panties_green"
-
-/datum/sprite_accessory/underwear/female_kinky
+/datum/sprite_accessory/underwear/bottom/female_kinky
name = "Panties - Kinky Black"
icon_state = "panties_kinky"
+ gender = FEMALE
-/datum/sprite_accessory/underwear/female_red
- name = "Panties - Red"
- icon_state = "panties_red"
-
-/datum/sprite_accessory/underwear/female_uk
+/datum/sprite_accessory/underwear/bottom/panties_uk
name = "Panties - UK"
icon_state = "panties_uk"
+ gender = FEMALE
-/datum/sprite_accessory/underwear/female_white
- name = "Panties - White"
- icon_state = "panties_white"
+/datum/sprite_accessory/underwear/bottom/panties_neko
+ name = "Panties - Neko"
+ icon_state = "panties_neko"
+ has_color = TRUE
+ gender = FEMALE
-/datum/sprite_accessory/underwear/female_white_neko
- name = "Panties, Neko - White"
- icon_state = "panties_neko_white"
+/datum/sprite_accessory/underwear/bottom/panties_slim
+ name = "Panties - Slim"
+ icon_state = "panties_slim"
+ has_color = TRUE
+ gender = FEMALE
-/datum/sprite_accessory/underwear/female_black_neko
- name = "Panties, Neko - Black"
- icon_state = "panties_neko_black"
+/datum/sprite_accessory/underwear/bottom/striped_panties
+ name = "Panties - Striped"
+ icon_state = "striped_panties"
+ has_color = TRUE
+ gender = FEMALE
+/datum/sprite_accessory/underwear/bottom/panties_swimsuit
+ name = "Panties - Swimsuit"
+ icon_state = "panties_swimming"
+ has_color = TRUE
+ gender = FEMALE
-/datum/sprite_accessory/underwear/swimsuit_red
+/datum/sprite_accessory/underwear/bottom/panties_thin
+ name = "Panties - Thin"
+ icon_state = "panties_thin"
+ has_color = TRUE
+ gender = FEMALE
+
+/datum/sprite_accessory/underwear/bottom/longjon
+ name = "Long John Bottoms"
+ icon_state = "ljonb"
+ has_color = TRUE
+
+/datum/sprite_accessory/underwear/bottom/swimsuit_red
name = "Swimsuit, One Piece - Red"
icon_state = "swimming_red"
+ gender = FEMALE
-/datum/sprite_accessory/underwear/swimsuit
+/datum/sprite_accessory/underwear/bottom/swimsuit
name = "Swimsuit, One Piece - Black"
icon_state = "swimming_black"
+ gender = FEMALE
-/datum/sprite_accessory/underwear/swimsuit_blue
+/datum/sprite_accessory/underwear/bottom/swimsuit_blue
name = "Swimsuit, One Piece - Striped Blue"
icon_state = "swimming_blue"
+ gender = FEMALE
-/datum/sprite_accessory/underwear/swimsuit_green
- name = "Swimsuit, Bottom - Green"
- icon_state = "panties_swimming_green"
+/datum/sprite_accessory/underwear/bottom/thong
+ name = "Thong"
+ icon_state = "thong"
+ has_color = TRUE
+ gender = FEMALE
-/datum/sprite_accessory/underwear/swimsuit_purple
- name = "Swimsuit, Bottom - Purple"
- icon_state = "panties_swimming_purple"
-
-/datum/sprite_accessory/underwear/female_thong_black
- name = "Thong - Black"
- icon_state = "thong_black"
-
-/datum/sprite_accessory/underwear/female_thong
- name = "Thong - Pink"
- icon_state = "thong_pink"
-
-/datum/sprite_accessory/underwear/female_babydoll
- name = "Babydoll - Black"
- icon_state = "babydoll"
+/datum/sprite_accessory/underwear/bottom/thong_babydoll
+ name = "Thong - Alt"
+ icon_state = "thong_babydoll"
+ has_color = TRUE
+ gender = FEMALE
diff --git a/code/modules/mob/dead/new_player/sprite_accessories/wings.dm b/code/modules/mob/dead/new_player/sprite_accessories/wings.dm
index d051b2f07a..554b7edfdb 100644
--- a/code/modules/mob/dead/new_player/sprite_accessories/wings.dm
+++ b/code/modules/mob/dead/new_player/sprite_accessories/wings.dm
@@ -1,3 +1,5 @@
+//Angel Wings
+
/datum/sprite_accessory/wings/none
name = "None"
icon_state = "none"
@@ -23,4 +25,201 @@
dimension_x = 46
center = TRUE
dimension_y = 34
- locked = TRUE
\ No newline at end of file
+ locked = TRUE
+
+// Decorative wings
+/datum/sprite_accessory/deco_wings
+ icon = 'icons/mob/wings.dmi'
+
+/datum/sprite_accessory/deco_wings/plain
+ name = "Plain"
+ icon_state = "plain"
+
+/datum/sprite_accessory/deco_wings/monarch
+ name = "Monarch"
+ icon_state = "monarch"
+
+/datum/sprite_accessory/deco_wings/luna
+ name = "Luna"
+ icon_state = "luna"
+
+/datum/sprite_accessory/deco_wings/atlas
+ name = "Atlas"
+ icon_state = "atlas"
+
+/datum/sprite_accessory/deco_wings/reddish
+ name = "Reddish"
+ icon_state = "redish"
+
+/datum/sprite_accessory/deco_wings/royal
+ name = "Royal"
+ icon_state = "royal"
+
+/datum/sprite_accessory/deco_wings/gothic
+ name = "Gothic"
+ icon_state = "gothic"
+
+/datum/sprite_accessory/deco_wings/lovers
+ name = "Lovers"
+ icon_state = "lovers"
+
+/datum/sprite_accessory/deco_wings/whitefly
+ name = "White Fly"
+ icon_state = "whitefly"
+
+/datum/sprite_accessory/deco_wings/punished
+ name = "Burnt Off"
+ icon_state = "punished"
+ locked = TRUE
+
+/datum/sprite_accessory/deco_wings/firewatch
+ name = "Firewatch"
+ icon_state = "firewatch"
+
+/datum/sprite_accessory/deco_wings/deathhead
+ name = "Deathshead"
+ icon_state = "deathhead"
+
+/datum/sprite_accessory/deco_wings/poison
+ name = "Poison"
+ icon_state = "poison"
+
+/datum/sprite_accessory/deco_wings/ragged
+ name = "Ragged"
+ icon_state = "ragged"
+
+/datum/sprite_accessory/deco_wings/moonfly
+ name = "Moon Fly"
+ icon_state = "moonfly"
+
+/datum/sprite_accessory/deco_wings/snow
+ name = "Snow"
+ icon_state = "snow"
+
+/datum/sprite_accessory/deco_wings/angel
+ name = "Angel"
+ icon_state = "angel"
+ color_src = 0
+ dimension_x = 46
+ center = TRUE
+ dimension_y = 34
+
+/datum/sprite_accessory/deco_wings/none
+ name = "None"
+ icon_state = "none"
+
+
+//INSECT WINGS
+
+/datum/sprite_accessory/insect_wings
+ icon = 'icons/mob/wings.dmi'
+ color_src = null
+
+/datum/sprite_accessory/insect_wings/none
+ name = "None"
+ icon_state = "none"
+
+/datum/sprite_accessory/insect_wings/plain
+ name = "Plain"
+ icon_state = "plain"
+
+/datum/sprite_accessory/insect_wings/monarch
+ name = "Monarch"
+ icon_state = "monarch"
+
+/datum/sprite_accessory/insect_wings/luna
+ name = "Luna"
+ icon_state = "luna"
+
+/datum/sprite_accessory/insect_wings/atlas
+ name = "Atlas"
+ icon_state = "atlas"
+
+/datum/sprite_accessory/insect_wings/reddish
+ name = "Reddish"
+ icon_state = "redish"
+
+/datum/sprite_accessory/insect_wings/royal
+ name = "Royal"
+ icon_state = "royal"
+
+/datum/sprite_accessory/insect_wings/gothic
+ name = "Gothic"
+ icon_state = "gothic"
+
+/datum/sprite_accessory/insect_wings/lovers
+ name = "Lovers"
+ icon_state = "lovers"
+
+/datum/sprite_accessory/insect_wings/whitefly
+ name = "White Fly"
+ icon_state = "whitefly"
+
+/datum/sprite_accessory/insect_wings/punished
+ name = "Burnt Off"
+ icon_state = "punished"
+
+/datum/sprite_accessory/insect_wings/firewatch
+ name = "Firewatch"
+ icon_state = "firewatch"
+
+/datum/sprite_accessory/insect_wings/deathhead
+ name = "Deathshead"
+ icon_state = "deathhead"
+
+/datum/sprite_accessory/insect_wings/poison
+ name = "Poison"
+ icon_state = "poison"
+
+/datum/sprite_accessory/insect_wings/ragged
+ name = "Ragged"
+ icon_state = "ragged"
+
+/datum/sprite_accessory/insect_wings/moonfly
+ name = "Moon Fly"
+ icon_state = "moonfly"
+
+/datum/sprite_accessory/insect_wings/snow
+ name = "Snow"
+ icon_state = "snow"
+
+/datum/sprite_accessory/insect_wings/colored
+ name = "Colored (Hair)"
+ icon_state = "snowplain"
+ color_src = HAIR
+
+/datum/sprite_accessory/insect_fluff/colored1
+ name = "Colored (Primary)"
+ icon_state = "snowplain"
+ color_src = MUTCOLORS
+
+/datum/sprite_accessory/insect_fluff/colored2
+ name = "Colored (Secondary)"
+ icon_state = "snowplain"
+ color_src = MUTCOLORS2
+
+/datum/sprite_accessory/insect_fluff/colored3
+ name = "Colored (Tertiary)"
+ icon_state = "snowplain"
+ color_src = MUTCOLORS3
+
+/datum/sprite_accessory/insect_wings/bee
+ name = "Bee"
+ icon_state = "bee"
+
+/datum/sprite_accessory/insect_wings/bee_color
+ name = "Bee (Hair colored)"
+ icon_state = "bee"
+ color_src = HAIR
+
+/datum/sprite_accessory/insect_wings/fairy
+ name = "Fairy"
+ icon_state = "fairy"
+
+/datum/sprite_accessory/insect_wings/bat
+ name = "Bat"
+ icon_state = "bat"
+
+/datum/sprite_accessory/insect_wings/feathery
+ name = "Feathery"
+ icon_state = "feathery"
diff --git a/code/modules/mob/dead/observer/observer.dm b/code/modules/mob/dead/observer/observer.dm
index fefa032e4f..d599d55886 100644
--- a/code/modules/mob/dead/observer/observer.dm
+++ b/code/modules/mob/dead/observer/observer.dm
@@ -260,16 +260,16 @@ Transfer_mind is there to check if mob is being deleted/not going to have a body
Works together with spawning an observer, noted above.
*/
-/mob/proc/ghostize(can_reenter_corpse = 1)
- if(key)
- if(!cmptext(copytext(key,1,2),"@")) // Skip aghosts.
- stop_sound_channel(CHANNEL_HEARTBEAT) //Stop heartbeat sounds because You Are A Ghost Now
- var/mob/dead/observer/ghost = new(src) // Transfer safety to observer spawning proc.
- SStgui.on_transfer(src, ghost) // Transfer NanoUIs.
- ghost.can_reenter_corpse = can_reenter_corpse
- ghost.can_reenter_round = (can_reenter_corpse && !suiciding)
- ghost.key = key
- return ghost
+/mob/proc/ghostize(can_reenter_corpse = TRUE, special = FALSE)
+ if(!key || cmptext(copytext(key,1,2),"@") || (!special && SEND_SIGNAL(src, COMSIG_MOB_GHOSTIZE, can_reenter_corpse, special) & COMPONENT_BLOCK_GHOSTING))
+ return //mob has no key, is an aghost or some component hijacked.
+ stop_sound_channel(CHANNEL_HEARTBEAT) //Stop heartbeat sounds because You Are A Ghost Now
+ var/mob/dead/observer/ghost = new(src) // Transfer safety to observer spawning proc.
+ SStgui.on_transfer(src, ghost) // Transfer NanoUIs.
+ ghost.can_reenter_corpse = can_reenter_corpse
+ ghost.can_reenter_round = (can_reenter_corpse && !suiciding)
+ transfer_ckey(ghost, FALSE)
+ return ghost
/*
This is the proc mobs get to turn into a ghost. Forked from ghostize due to compatibility issues.
@@ -280,6 +280,9 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
set name = "Ghost"
set desc = "Relinquish your life and enter the land of the dead."
+ if(SEND_SIGNAL(src, COMSIG_MOB_GHOSTIZE, (stat == DEAD) ? TRUE : FALSE, FALSE) & COMPONENT_BLOCK_GHOSTING)
+ return
+
// CITADEL EDIT
if(istype(loc, /obj/machinery/cryopod))
var/response = alert(src, "Are you -sure- you want to ghost?\n(You are alive. If you ghost whilst still alive you won't be able to re-enter this round! You can't change your mind so choose wisely!!)","Are you sure you want to ghost?","Ghost","Stay in body")
@@ -306,6 +309,9 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
set name = "Ghost"
set desc = "Relinquish your life and enter the land of the dead."
+ if(SEND_SIGNAL(src, COMSIG_MOB_GHOSTIZE, FALSE, FALSE) & COMPONENT_BLOCK_GHOSTING)
+ return
+
var/response = alert(src, "Are you -sure- you want to ghost?\n(You are alive. If you ghost whilst still alive you won't be able to re-enter this round! You can't change your mind so choose wisely!!)","Are you sure you want to ghost?","Ghost","Stay in body")
if(response != "Ghost")
return
@@ -348,7 +354,7 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
return
client.change_view(CONFIG_GET(string/default_view))
SStgui.on_transfer(src, mind.current) // Transfer NanoUIs.
- mind.current.key = key
+ transfer_ckey(mind.current, FALSE)
return 1
/mob/dead/observer/proc/notify_cloning(var/message, var/sound, var/atom/source, flashwindow = TRUE)
@@ -628,7 +634,7 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
to_chat(src, "Someone has taken this body while you were choosing!")
return 0
- target.key = key
+ transfer_ckey(target, FALSE)
target.faction = list("neutral")
return 1
diff --git a/code/modules/mob/dead/observer/say.dm b/code/modules/mob/dead/observer/say.dm
index d521ef179f..7eeab05466 100644
--- a/code/modules/mob/dead/observer/say.dm
+++ b/code/modules/mob/dead/observer/say.dm
@@ -4,14 +4,14 @@
return
var/message_mode = get_message_mode(message)
- if(client && (message_mode == "admin" || message_mode == "deadmin"))
+ if(client && (message_mode == MODE_ADMIN || message_mode == MODE_DEADMIN))
message = copytext(message, 3)
if(findtext(message, " ", 1, 2))
message = copytext(message, 2)
- if(message_mode == "admin")
+ if(message_mode == MODE_ADMIN)
client.cmd_admin_say(message)
- else if(message_mode == "deadmin")
+ else if(message_mode == MODE_DEADMIN)
client.dsay(message)
return
diff --git a/code/modules/mob/inventory.dm b/code/modules/mob/inventory.dm
index c2518bfc9b..760fb7a29f 100644
--- a/code/modules/mob/inventory.dm
+++ b/code/modules/mob/inventory.dm
@@ -158,7 +158,7 @@
//Returns if a certain item can be equipped to a certain slot.
// Currently invalid for two-handed items - call obj/item/mob_can_equip() instead.
-/mob/proc/can_equip(obj/item/I, slot, disable_warning = 0)
+/mob/proc/can_equip(obj/item/I, slot, disable_warning = FALSE, bypass_equip_delay_self = FALSE)
return FALSE
/mob/proc/can_put_in_hand(I, hand_index)
@@ -275,7 +275,7 @@
/mob/proc/canUnEquip(obj/item/I, force)
if(!I)
return TRUE
- if((I.item_flags & NODROP) && !force)
+ if(HAS_TRAIT(I, TRAIT_NODROP) && !force)
return FALSE
return TRUE
@@ -309,13 +309,13 @@
//DO NOT CALL THIS PROC
//use one of the above 3 helper procs
//you may override it, but do not modify the args
-/mob/proc/doUnEquip(obj/item/I, force, newloc, no_move, invdrop = TRUE) //Force overrides NODROP_1 for things like wizarditis and admin undress.
+/mob/proc/doUnEquip(obj/item/I, force, newloc, no_move, invdrop = TRUE) //Force overrides TRAIT_NODROP for things like wizarditis and admin undress.
//Use no_move if the item is just gonna be immediately moved afterward
//Invdrop is used to prevent stuff in pockets dropping. only set to false if it's going to immediately be replaced
- if(!I) //If there's nothing to drop, the drop is automatically succesfull. If(unEquip) should generally be used to check for NODROP_1.
+ if(!I) //If there's nothing to drop, the drop is automatically succesfull. If(unEquip) should generally be used to check for TRAIT_NODROP.
return TRUE
- if((I.item_flags & NODROP) && !force)
+ if(HAS_TRAIT(I, TRAIT_NODROP) && !force)
return FALSE
var/hand_index = get_held_index_of_item(I)
diff --git a/code/modules/mob/living/blood.dm b/code/modules/mob/living/blood.dm
index 815184c63d..ca1a961a92 100644
--- a/code/modules/mob/living/blood.dm
+++ b/code/modules/mob/living/blood.dm
@@ -18,10 +18,10 @@
/mob/living/carbon/monkey/handle_blood()
if(bodytemperature >= TCRYO && !(HAS_TRAIT(src, TRAIT_NOCLONE))) //cryosleep or husked people do not pump the blood.
//Blood regeneration if there is some space
- if(blood_volume < BLOOD_VOLUME_NORMAL)
+ if(blood_volume < (BLOOD_VOLUME_NORMAL * blood_ratio))
blood_volume += 0.1 // regenerate blood VERY slowly
- if(blood_volume < BLOOD_VOLUME_OKAY)
- adjustOxyLoss(round((BLOOD_VOLUME_NORMAL - blood_volume) * 0.02, 1))
+ if(blood_volume < (BLOOD_VOLUME_OKAY * blood_ratio))
+ adjustOxyLoss(round(((BLOOD_VOLUME_NORMAL * blood_ratio) - blood_volume) * 0.02, 1))
// Takes care blood loss and regeneration
/mob/living/carbon/human/handle_blood()
@@ -33,7 +33,7 @@
if(bodytemperature >= TCRYO && !(HAS_TRAIT(src, TRAIT_NOCLONE))) //cryosleep or husked people do not pump the blood.
//Blood regeneration if there is some space
- if(blood_volume < BLOOD_VOLUME_NORMAL && !HAS_TRAIT(src, TRAIT_NOHUNGER))
+ if(blood_volume < (BLOOD_VOLUME_NORMAL * blood_ratio) && !HAS_TRAIT(src, TRAIT_NOHUNGER))
var/nutrition_ratio = 0
switch(nutrition)
if(0 to NUTRITION_LEVEL_STARVING)
@@ -46,20 +46,22 @@
nutrition_ratio = 0.8
else
nutrition_ratio = 1
+ if(HAS_TRAIT(src, TRAIT_HIGH_BLOOD))
+ nutrition_ratio *= 1.2
if(satiety > 80)
nutrition_ratio *= 1.25
nutrition = max(0, nutrition - nutrition_ratio * HUNGER_FACTOR)
- blood_volume = min(BLOOD_VOLUME_NORMAL, blood_volume + 0.5 * nutrition_ratio)
+ blood_volume = min((BLOOD_VOLUME_NORMAL * blood_ratio), blood_volume + 0.5 * nutrition_ratio)
//Effects of bloodloss
var/word = pick("dizzy","woozy","faint")
- switch(blood_volume)
+ switch(blood_volume * INVERSE(blood_ratio))
if(BLOOD_VOLUME_OKAY to BLOOD_VOLUME_SAFE)
if(prob(5))
to_chat(src, "You feel [word].")
- adjustOxyLoss(round((BLOOD_VOLUME_NORMAL - blood_volume) * 0.01, 1))
+ adjustOxyLoss(round(((BLOOD_VOLUME_NORMAL * blood_ratio) - blood_volume) * 0.01, 1))
if(BLOOD_VOLUME_BAD to BLOOD_VOLUME_OKAY)
- adjustOxyLoss(round((BLOOD_VOLUME_NORMAL - blood_volume) * 0.02, 1))
+ adjustOxyLoss(round(((BLOOD_VOLUME_NORMAL * blood_ratio) - blood_volume) * 0.02, 1))
if(prob(5))
blur_eyes(6)
to_chat(src, "You feel very [word].")
@@ -111,7 +113,7 @@
blood_volume = initial(blood_volume)
/mob/living/carbon/human/restore_blood()
- blood_volume = BLOOD_VOLUME_NORMAL
+ blood_volume = (BLOOD_VOLUME_NORMAL * blood_ratio)
bleed_rate = 0
/****************************************************
@@ -122,7 +124,7 @@
/mob/living/proc/transfer_blood_to(atom/movable/AM, amount, forced)
if(!blood_volume || !AM.reagents)
return 0
- if(blood_volume < BLOOD_VOLUME_BAD && !forced)
+ if(blood_volume < (BLOOD_VOLUME_BAD * blood_ratio) && !forced)
return 0
if(blood_volume < amount)
@@ -161,7 +163,7 @@
return
/mob/living/carbon/get_blood_data(blood_id)
- if(blood_id == "blood") //actual blood reagent
+ if(blood_id == "blood") //actual blood reagent
var/blood_data = list()
//set the blood data
blood_data["donor"] = src
@@ -204,6 +206,21 @@
if(istype(ling))
blood_data["changeling_loudness"] = ling.loudfactor
return blood_data
+ if(blood_id == "slimejelly") //Just so MKUltra works. Takes the minimum required data. Sishen is testing if this breaks stuff.
+ var/blood_data = list()
+ if(mind)
+ blood_data["mind"] = mind
+ else if(last_mind)
+ blood_data["mind"] = last_mind
+ if(ckey)
+ blood_data["ckey"] = ckey
+ else if(last_mind)
+ blood_data["ckey"] = ckey(last_mind.key)
+ blood_data["gender"] = gender
+ blood_data["real_name"] = real_name
+ return blood_data
+
+
//get the id of the substance this mob use as blood.
/mob/proc/get_blood_id()
@@ -300,3 +317,24 @@
var/obj/effect/decal/cleanable/oil/B = locate() in T.contents
if(!B)
B = new(T)
+
+//This is a terrible way of handling it.
+/mob/living/proc/ResetBloodVol()
+ if(ishuman(src))
+ var/mob/living/carbon/human/H = src
+ if (HAS_TRAIT(src, TRAIT_HIGH_BLOOD))
+ blood_ratio = 1.2
+ H.handle_blood()
+ return
+ blood_ratio = 1
+ H.handle_blood()
+ return
+ blood_ratio = 1
+
+/mob/living/proc/AdjustBloodVol(var/value)
+ if(blood_ratio == value)
+ return
+ blood_ratio = value
+ if(ishuman(src))
+ var/mob/living/carbon/human/H = src
+ H.handle_blood()
diff --git a/code/modules/mob/living/bloodcrawl.dm b/code/modules/mob/living/bloodcrawl.dm
index 2a5fdeaa33..cc11c0e8bb 100644
--- a/code/modules/mob/living/bloodcrawl.dm
+++ b/code/modules/mob/living/bloodcrawl.dm
@@ -138,7 +138,11 @@
name = "blood crawl"
desc = "You are unable to hold anything while in this form."
icon = 'icons/effects/blood.dmi'
- item_flags = NODROP | ABSTRACT
+ item_flags = ABSTRACT
+
+/obj/item/bloodcrawl/Initialize()
+ . = ..()
+ ADD_TRAIT(src, TRAIT_NODROP, ABSTRACT_ITEM_TRAIT)
/mob/living/proc/exit_blood_effect(obj/effect/decal/cleanable/B)
playsound(get_turf(src), 'sound/magic/exit_blood.ogg', 100, 1, -1)
diff --git a/code/modules/mob/living/brain/MMI.dm b/code/modules/mob/living/brain/MMI.dm
index bacdc4524b..d84ba0d7a1 100644
--- a/code/modules/mob/living/brain/MMI.dm
+++ b/code/modules/mob/living/brain/MMI.dm
@@ -57,13 +57,14 @@
newbrain.brainmob = null
brainmob.forceMove(src)
brainmob.container = src
- if(!newbrain.damaged_brain) // the brain organ hasn't been beaten to death.
+ if(!(newbrain.organ_flags & ORGAN_FAILING)) // the brain organ hasn't been beaten to death.
brainmob.stat = CONSCIOUS //we manually revive the brain mob
GLOB.dead_mob_list -= brainmob
GLOB.alive_mob_list += brainmob
brainmob.reset_perspective()
brain = newbrain
+ brain.organ_flags |= ORGAN_FROZEN
name = "Man-Machine Interface: [brainmob.real_name]"
update_icon()
@@ -100,6 +101,7 @@
user.put_in_hands(brain) //puts brain in the user's hand or otherwise drops it on the user's turf
else
brain.forceMove(get_turf(src))
+ brain.organ_flags &= ~ORGAN_FROZEN
brain = null //No more brain in here
diff --git a/code/modules/mob/living/brain/brain.dm b/code/modules/mob/living/brain/brain.dm
index 76b416772e..97b29ca4e1 100644
--- a/code/modules/mob/living/brain/brain.dm
+++ b/code/modules/mob/living/brain/brain.dm
@@ -5,6 +5,7 @@
var/datum/dna/stored/stored_dna // dna var for brain. Used to store dna, brain dna is not considered like actual dna, brain.has_dna() returns FALSE.
stat = DEAD //we start dead by default
see_invisible = SEE_INVISIBLE_LIVING
+ speech_span = SPAN_ROBOT
/mob/living/brain/Initialize()
. = ..()
diff --git a/code/modules/mob/living/brain/brain_item.dm b/code/modules/mob/living/brain/brain_item.dm
index dafc6cf5e0..4192c2235b 100644
--- a/code/modules/mob/living/brain/brain_item.dm
+++ b/code/modules/mob/living/brain/brain_item.dm
@@ -7,11 +7,20 @@
layer = ABOVE_MOB_LAYER
zone = BODY_ZONE_HEAD
slot = ORGAN_SLOT_BRAIN
- vital = TRUE
+ organ_flags = ORGAN_VITAL
attack_verb = list("attacked", "slapped", "whacked")
+ ///The brain's organ variables are significantly more different than the other organs, with half the decay rate for balance reasons, and twice the maxHealth
+ decay_factor = STANDARD_ORGAN_DECAY / 4 //30 minutes of decaying to result in a fully damaged brain, since a fast decay rate would be unfun gameplay-wise
+ healing_factor = STANDARD_ORGAN_HEALING / 2
+
+ maxHealth = BRAIN_DAMAGE_DEATH
+ low_threshold = 45
+ high_threshold = 120
var/mob/living/brain/brainmob = null
- var/damaged_brain = FALSE //whether the brain organ is damaged.
+ var/brain_death = FALSE //if the brainmob was intentionally killed by attacking the brain after removal, or by severe braindamage
var/decoy_override = FALSE //I apologize to the security players, and myself, who abused this, but this is going to go.
+ //two variables necessary for calculating whether we get a brain trauma or not
+ var/damage_delta = 0
var/list/datum/brain_trauma/traumas = list()
@@ -34,7 +43,7 @@
if(brainmob.mind)
brainmob.mind.transfer_to(C)
else
- C.key = brainmob.key
+ brainmob.transfer_ckey(C)
QDEL_NULL(brainmob)
@@ -90,22 +99,89 @@
if(brainmob)
O.attack(brainmob, user) //Oh noooeeeee
-/obj/item/organ/brain/examine(mob/user)
- ..()
+ if(istype(O, /obj/item/organ_storage)) //BUG_PROBABLE_CAUSE
+ return //Borg organ bags shouldn't be killing brains
- if(brainmob)
- if(brainmob.client)
- if(brainmob.health <= HEALTH_THRESHOLD_DEAD)
- to_chat(user, "It's lifeless and severely damaged.")
+ if((organ_flags & ORGAN_FAILING) && O.is_drainable() && O.reagents.has_reagent("neurine")) //Neurine fixes dead brains
+ . = TRUE //don't do attack animation.
+ var/cached_Bdamage = brainmob?.health
+ var/datum/reagent/medicine/neurine/N = reagents.has_reagent("neurine")
+ var/datum/reagent/medicine/mannitol/M1 = reagents.has_reagent("mannitol")
+
+ if(O.reagents.has_reagent("mannitol"))//Just a quick way to bolster the effects if someone mixes up a batch.
+ N.volume *= (M1.volume*0.5)
+
+ if(!O.reagents.has_reagent("neurine", 10))
+ to_chat(user, "There's not enough neurine in [O] to restore [src]!")
+ return
+
+ user.visible_message("[user] starts to pour the contents of [O] onto [src].", "You start to slowly pour the contents of [O] onto [src].")
+ if(!do_after(user, 60, TRUE, src))
+ to_chat(user, "You failed to pour [O] onto [src]!")
+ return
+
+ user.visible_message("[user] pours the contents of [O] onto [src], causing it to reform its original shape and turn a slightly brighter shade of pink.", "You pour the contents of [O] onto [src], causing it to reform its original shape and turn a slightly brighter shade of pink.")
+ setOrganDamage((damage - (0.10 * maxHealth)*(N.volume/10))) //heals a small amount, and by using "setorgandamage", we clear the failing variable if that was up
+ O.reagents.clear_reagents()
+
+ if(cached_Bdamage <= HEALTH_THRESHOLD_DEAD) //Fixing dead brains yeilds a trauma
+ if((cached_Bdamage <= HEALTH_THRESHOLD_DEAD) && (brainmob.health > HEALTH_THRESHOLD_DEAD))
+ if(prob(80))
+ gain_trauma_type(BRAIN_TRAUMA_MILD)
+ else if(prob(50))
+ gain_trauma_type(BRAIN_TRAUMA_SEVERE)
+ else
+ gain_trauma_type(BRAIN_TRAUMA_SPECIAL)
+ return
+
+ if((organ_flags & ORGAN_FAILING) && O.is_drainable() && O.reagents.has_reagent("mannitol")) //attempt to heal the brain
+ . = TRUE //don't do attack animation.
+ var/datum/reagent/medicine/mannitol/M = reagents.has_reagent("mannitol")
+ if(brain_death || brainmob?.health <= HEALTH_THRESHOLD_DEAD) //if the brain is fucked anyway, do nothing
+ to_chat(user, "[src] is far too damaged, you'll have to use neurine on it!")
+ return
+
+ if(!O.reagents.has_reagent("mannitol", 10))
+ to_chat(user, "There's not enough mannitol in [O] to restore [src]!")
+ return
+
+ user.visible_message("[user] starts to pour the contents of [O] onto [src].", "You start to slowly pour the contents of [O] onto [src].")
+ if(!do_after(user, 60, TRUE, src))
+ to_chat(user, "You failed to pour [O] onto [src]!")
+ return
+
+ user.visible_message("[user] pours the contents of [O] onto [src], causing it to reform its original shape and turn a slightly brighter shade of pink.", "You pour the contents of [O] onto [src], causing it to reform its original shape and turn a slightly brighter shade of pink.")
+ setOrganDamage((damage - (0.05 * maxHealth)*(M.volume/10))) //heals a small amount, and by using "setorgandamage", we clear the failing variable if that was up
+ O.reagents.clear_reagents()
+ return
+
+
+
+/obj/item/organ/brain/examine(mob/user)//BUG_PROBABLE_CAUSE to_chats changed to . +=
+ . = ..()
+
+ if(user.suiciding)
+ . += "It's started turning slightly grey. They must not have been able to handle the stress of it all."
+ else if(brainmob)
+ if(brainmob.get_ghost(FALSE, TRUE))
+ if(brain_death || brainmob.health <= HEALTH_THRESHOLD_DEAD)
+ . += "It's lifeless and severely damaged, only the strongest of chems will save it."
+ else if(organ_flags & ORGAN_FAILING)
+ . += "It seems to still have a bit of energy within it, but it's rather damaged... You may be able to restore it with some mannitol."
else
- to_chat(user, "You can feel the small spark of life still left in this one.")
+ . += "You can feel the small spark of life still left in this one."
+ else if(organ_flags & ORGAN_FAILING)
+ . += "It seems particularly lifeless and is rather damaged... You may be able to restore it with some mannitol incase it becomes functional again later."
else
- to_chat(user, "This one seems particularly lifeless. Perhaps it will regain some of its luster later.")
+ . += "This one seems particularly lifeless. Perhaps it will regain some of its luster later."
else
if(decoy_override)
- to_chat(user, "This one seems particularly lifeless. Perhaps it will regain some of its luster later.")
+ if(organ_flags & ORGAN_FAILING)
+ . += "It seems particularly lifeless and is rather damaged... You may be able to restore it with some mannitol incase it becomes functional again later."
+ else
+ . += "This one seems particularly lifeless. Perhaps it will regain some of its luster later."
else
- to_chat(user, "This one is completely devoid of life.")
+ . += "This one is completely devoid of life."
/obj/item/organ/brain/attack(mob/living/carbon/C, mob/user)
if(!istype(C))
@@ -141,7 +217,7 @@
Insert(C)
else
..()
-
+/* TO BE REMOVED, KEPT IN CASE OF BUGS
/obj/item/organ/brain/proc/get_brain_damage()
var/brain_damage_threshold = max_integrity * BRAIN_DAMAGE_INTEGRITY_MULTIPLIER
var/offset_integrity = obj_integrity - (max_integrity - brain_damage_threshold)
@@ -165,6 +241,56 @@
else if(adjusted_amount <= -DAMAGE_PRECISION)
obj_integrity = min(max_integrity, obj_integrity-adjusted_amount)
. = adjusted_amount
+*/
+
+/obj/item/organ/brain/on_life()
+ if(damage >= BRAIN_DAMAGE_DEATH) //rip
+ to_chat(owner, "The last spark of life in your brain fizzles out...")
+ owner.death()
+ brain_death = TRUE
+ return
+ ..()
+
+/obj/item/organ/brain/on_death()
+ if(damage <= BRAIN_DAMAGE_DEATH) //rip
+ brain_death = FALSE
+ ..()
+
+
+/obj/item/organ/brain/applyOrganDamage(var/d, var/maximum = maxHealth)
+ ..()
+
+
+/obj/item/organ/brain/check_damage_thresholds(mob/M)
+ . = ..()
+ //if we're not more injured than before, return without gambling for a trauma
+ if(damage <= prev_damage)
+ return
+ damage_delta = damage - prev_damage
+ if(damage > BRAIN_DAMAGE_MILD)
+ if(prob(damage_delta * (1 + max(0, (damage - BRAIN_DAMAGE_MILD)/100)))) //Base chance is the hit damage; for every point of damage past the threshold the chance is increased by 1% //learn how to do your bloody math properly goddamnit
+ gain_trauma_type(BRAIN_TRAUMA_MILD)
+ if(damage > BRAIN_DAMAGE_SEVERE)
+ if(prob(damage_delta * (1 + max(0, (damage - BRAIN_DAMAGE_SEVERE)/100)))) //Base chance is the hit damage; for every point of damage past the threshold the chance is increased by 1%
+ if(prob(20))
+ gain_trauma_type(BRAIN_TRAUMA_SPECIAL)
+ else
+ gain_trauma_type(BRAIN_TRAUMA_SEVERE)
+
+ if (owner)
+ if(owner.stat < UNCONSCIOUS) //conscious or soft-crit
+ var/brain_message
+ if(prev_damage < BRAIN_DAMAGE_MILD && damage >= BRAIN_DAMAGE_MILD)
+ brain_message = "You feel lightheaded."
+ else if(prev_damage < BRAIN_DAMAGE_SEVERE && damage >= BRAIN_DAMAGE_SEVERE)
+ brain_message = "You feel less in control of your thoughts."
+ else if(prev_damage < (BRAIN_DAMAGE_DEATH - 20) && damage >= (BRAIN_DAMAGE_DEATH - 20))
+ brain_message = "You can feel your mind flickering on and off..."
+
+ if(.)
+ . += "\n[brain_message]"
+ else
+ return brain_message
/obj/item/organ/brain/Destroy() //copypasted from MMIs.
if(brainmob)
@@ -200,6 +326,10 @@
return FALSE
if(!resilience)
resilience = initial(trauma.resilience)
+ if(!owner)
+ return FALSE
+ if(owner.stat == DEAD)
+ return FALSE
var/resilience_tier_count = 0
for(var/X in traumas)
diff --git a/code/modules/mob/living/brain/life.dm b/code/modules/mob/living/brain/life.dm
index 786bb0b55c..51be1f6971 100644
--- a/code/modules/mob/living/brain/life.dm
+++ b/code/modules/mob/living/brain/life.dm
@@ -20,7 +20,7 @@
else if(istype(loc, /obj/item/organ/brain))
BR = loc
if(BR)
- BR.damaged_brain = 1 //beaten to a pulp
+ BR.brain_death = TRUE
/mob/living/brain/proc/handle_emp_damage()
if(emp_damage)
diff --git a/code/modules/mob/living/brain/say.dm b/code/modules/mob/living/brain/say.dm
index ce0a09c27f..b7d7e1d7fc 100644
--- a/code/modules/mob/living/brain/say.dm
+++ b/code/modules/mob/living/brain/say.dm
@@ -10,14 +10,11 @@
..()
-/mob/living/brain/get_spans()
- return ..() | SPAN_ROBOT
-
/mob/living/brain/radio(message, message_mode, list/spans, language)
if(message_mode == MODE_HEADSET && istype(container, /obj/item/mmi))
var/obj/item/mmi/R = container
if(R.radio)
- R.radio.talk_into(src, message, , get_spans(), language)
+ R.radio.talk_into(src, message, language = language)
return ITALICS | REDUCE_RANGE
else
return ..()
diff --git a/code/modules/mob/living/carbon/alien/humanoid/humanoid.dm b/code/modules/mob/living/carbon/alien/humanoid/humanoid.dm
index 8403d533c4..edf0fde83e 100644
--- a/code/modules/mob/living/carbon/alien/humanoid/humanoid.dm
+++ b/code/modules/mob/living/carbon/alien/humanoid/humanoid.dm
@@ -91,7 +91,7 @@
else
return initial(pixel_x)
-/mob/living/carbon/alien/humanoid/get_permeability_protection()
+/mob/living/carbon/alien/humanoid/get_permeability_protection(list/target_zones)
return 0.8
/mob/living/carbon/alien/humanoid/alien_evolve(mob/living/carbon/alien/humanoid/new_xeno)
diff --git a/code/modules/mob/living/carbon/alien/humanoid/queen.dm b/code/modules/mob/living/carbon/alien/humanoid/queen.dm
index 79eed6b82b..76ede9276c 100644
--- a/code/modules/mob/living/carbon/alien/humanoid/queen.dm
+++ b/code/modules/mob/living/carbon/alien/humanoid/queen.dm
@@ -103,9 +103,13 @@
name = "\improper royal parasite"
desc = "Inject this into one of your grown children to promote her to a Praetorian!"
icon_state = "alien_medal"
- item_flags = ABSTRACT | NODROP | DROPDEL
+ item_flags = ABSTRACT | DROPDEL
icon = 'icons/mob/alien.dmi'
+/obj/item/queenpromote/Initialize()
+ . = ..()
+ ADD_TRAIT(src, TRAIT_NODROP, ABSTRACT_ITEM_TRAIT)
+
/obj/item/queenpromote/attack(mob/living/M, mob/living/carbon/alien/humanoid/user)
if(!isalienadult(M) || isalienroyal(M))
to_chat(user, "You may only use this with your adult, non-royal children!")
diff --git a/code/modules/mob/living/carbon/alien/humanoid/update_icons.dm b/code/modules/mob/living/carbon/alien/humanoid/update_icons.dm
index e1a7752e9d..d2788075e2 100644
--- a/code/modules/mob/living/carbon/alien/humanoid/update_icons.dm
+++ b/code/modules/mob/living/carbon/alien/humanoid/update_icons.dm
@@ -59,15 +59,19 @@
/mob/living/carbon/alien/humanoid/update_inv_handcuffed()
remove_overlay(HANDCUFF_LAYER)
- var/cuff_icon = "aliencuff"
- var/dmi_file = 'icons/mob/alien.dmi'
-
- if(mob_size == MOB_SIZE_LARGE)
- cuff_icon = "aliencuff_[caste]"
- dmi_file = 'icons/mob/alienqueen.dmi'
if(handcuffed)
- overlays_standing[HANDCUFF_LAYER] = mutable_appearance(dmi_file, cuff_icon, -HANDCUFF_LAYER)
+ var/cuff_icon = handcuffed.item_state
+ var/dmi_file = 'icons/mob/alien.dmi'
+
+ if(mob_size == MOB_SIZE_LARGE)
+ cuff_icon += "_[caste]"
+ dmi_file = 'icons/mob/alienqueen.dmi'
+
+ var/mutable_appearance/cuffs = mutable_appearance(dmi_file, cuff_icon, -HANDCUFF_LAYER)
+ cuffs.color = handcuffed.color
+
+ overlays_standing[HANDCUFF_LAYER] = cuffs
apply_overlay(HANDCUFF_LAYER)
//Royals have bigger sprites, so inhand things must be handled differently.
diff --git a/code/modules/mob/living/carbon/alien/larva/emote.dm b/code/modules/mob/living/carbon/alien/larva/emote.dm
deleted file mode 100644
index 62cb620ee4..0000000000
--- a/code/modules/mob/living/carbon/alien/larva/emote.dm
+++ /dev/null
@@ -1,113 +0,0 @@
-/mob/living/carbon/alien/larva/emote(act,m_type=1,message = null)
-
- var/param = null
- if (findtext(act, "-", 1, null))
- var/t1 = findtext(act, "-", 1, null)
- param = copytext(act, t1 + 1, length(act) + 1)
- act = copytext(act, 1, t1)
-
- var/muzzled = is_muzzled()
-
- switch(act) //Alphabetically sorted please.
- if ("burp","burps")
- if (!muzzled)
- message = "[src] burps."
- m_type = 2
- if ("choke","chokes")
- message = "[src] chokes."
- m_type = 2
- if ("collapse","collapses")
- Paralyse(2)
- message = "[src] collapses!"
- m_type = 2
- if ("dance","dances")
- if (!src.restrained())
- message = "[src] dances around happily."
- m_type = 1
- if ("deathgasp","deathgasps")
- message = "[src] lets out a sickly hiss of air and falls limply to the floor..."
- m_type = 2
- if ("drool","drools")
- message = "[src] drools."
- m_type = 1
- if ("gasp","gasps")
- message = "[src] gasps."
- m_type = 2
- if ("gnarl","gnarls")
- if (!muzzled)
- message = "[src] gnarls and shows its teeth.."
- m_type = 2
- if ("hiss","hisses")
- message = "[src] hisses softly."
- m_type = 1
- if ("jump","jumps")
- message = "[src] jumps!"
- m_type = 1
- if ("me")
- ..()
- return
- if ("moan","moans")
- message = "[src] moans!"
- m_type = 2
- if ("nod","nods")
- message = "[src] nods its head."
- m_type = 1
- if ("roar","roars")
- if (!muzzled)
- message = "[src] softly roars."
- m_type = 2
- if ("roll","rolls")
- if (!src.restrained())
- message = "[src] rolls."
- m_type = 1
- if ("scratch","scratches")
- if (!src.restrained())
- message = "[src] scratches."
- m_type = 1
- if ("screech","screeches") //This orignally was called scretch, changing it. -Sum99
- if (!muzzled)
- message = "[src] screeches."
- m_type = 2
- if ("shake","shakes")
- message = "[src] shakes its head."
- m_type = 1
- if ("shiver","shivers")
- message = "[src] shivers."
- m_type = 2
- if ("sign","signs")
- if (!src.restrained())
- message = text("[src] signs[].", (text2num(param) ? text(" the number []", text2num(param)) : null))
- m_type = 1
- if ("snore","snores")
- message = "[src] snores."
- m_type = 2
- if ("sulk","sulks")
- message = "[src] sulks down sadly."
- m_type = 1
- if ("sway","sways")
- message = "[src] sways around dizzily."
- m_type = 1
- if ("tail")
- message = "[src] waves its tail."
- m_type = 1
- if ("twitch")
- message = "[src] twitches violently."
- m_type = 1
- if ("whimper","whimpers")
- if (!muzzled)
- message = "[src] whimpers."
- m_type = 2
-
- if ("help") //"The exception"
- src << "Help for larva emotes. You can use these emotes with say \"*emote\":\n\nburp, choke, collapse, dance, deathgasp, drool, gasp, gnarl, hiss, jump, me, moan, nod, roll, roar, scratch, screech, shake, shiver, sign-#, sulk, sway, tail, twitch, whimper"
-
- else
- src << "Unusable emote '[act]'. Say *help for a list."
-
- if ((message && src.stat == 0))
- log_emote("[name]/[key] : [message]")
- if (m_type & 1)
- visible_message(message)
- else
- audible_message(message)
- return
diff --git a/code/modules/mob/living/carbon/alien/organs.dm b/code/modules/mob/living/carbon/alien/organs.dm
index 155f203708..df1be454ee 100644
--- a/code/modules/mob/living/carbon/alien/organs.dm
+++ b/code/modules/mob/living/carbon/alien/organs.dm
@@ -1,6 +1,7 @@
/obj/item/organ/alien
icon_state = "xgibmid2"
var/list/alien_powers = list()
+ organ_flags = ORGAN_NO_SPOIL
/obj/item/organ/alien/Initialize()
. = ..()
diff --git a/code/modules/mob/living/carbon/alien/say.dm b/code/modules/mob/living/carbon/alien/say.dm
index b921aea67e..5f1e1a2830 100644
--- a/code/modules/mob/living/carbon/alien/say.dm
+++ b/code/modules/mob/living/carbon/alien/say.dm
@@ -4,7 +4,7 @@
if(!message)
return
- var/message_a = say_quote(message, get_spans())
+ var/message_a = say_quote(message)
var/rendered = "Hivemind, [shown_name][message_a]"
for(var/mob/S in GLOB.player_list)
if(!S.stat && S.hivecheck())
diff --git a/code/modules/mob/living/carbon/alien/special/alien_embryo.dm b/code/modules/mob/living/carbon/alien/special/alien_embryo.dm
index b4739f943e..04a2e56857 100644
--- a/code/modules/mob/living/carbon/alien/special/alien_embryo.dm
+++ b/code/modules/mob/living/carbon/alien/special/alien_embryo.dm
@@ -22,6 +22,7 @@
return S
/obj/item/organ/body_egg/alien_embryo/on_life()
+ . = ..()
switch(stage)
if(2, 3)
if(prob(2))
@@ -63,7 +64,7 @@
-/obj/item/organ/body_egg/alien_embryo/proc/AttemptGrow(gib_on_success=TRUE)
+/obj/item/organ/body_egg/alien_embryo/proc/AttemptGrow(var/kill_on_sucess=TRUE)
if(!owner || bursting)
return
@@ -86,7 +87,7 @@
var/atom/xeno_loc = get_turf(owner)
var/mob/living/carbon/alien/larva/new_xeno = new(xeno_loc)
- new_xeno.key = ghost.key
+ ghost.transfer_ckey(new_xeno, FALSE)
SEND_SOUND(new_xeno, sound('sound/voice/hiss5.ogg',0,0,0,100)) //To get the player's attention
new_xeno.canmove = 0 //so we don't move during the bursting animation
new_xeno.notransform = 1
@@ -102,10 +103,12 @@
new_xeno.notransform = 0
new_xeno.invisibility = 0
- if(gib_on_success)
- new_xeno.visible_message("[new_xeno] bursts out of [owner] in a shower of gore!", "You exit [owner], your previous host.", "You hear organic matter ripping and tearing!")
- owner.gib(TRUE)
- else
+ if(kill_on_sucess) //ITS TOO LATE
+ new_xeno.visible_message("[new_xeno] bursts out of [owner]!", "You exit [owner], your previous host.", "You hear organic matter ripping and tearing!")
+ owner.apply_damage(rand(100,300),BRUTE,zone,FALSE) //Random high damage to torso so health sensors don't metagame.
+ owner.spill_organs(TRUE,FALSE,TRUE) //Lets still make the death gruesome and impossible to just simply defib someone.
+ owner.death(FALSE) //Just in case some freak occurance occurs where you somehow survive all your organs being removed from you and the 100-300 brute damage.
+ else //When it is removed via surgery at a late stage, rather than forced.
new_xeno.visible_message("[new_xeno] wriggles out of [owner]!", "You exit [owner], your previous host.")
owner.adjustBruteLoss(40)
owner.cut_overlay(overlay)
diff --git a/code/modules/mob/living/carbon/alien/special/facehugger.dm b/code/modules/mob/living/carbon/alien/special/facehugger.dm
index c2c8904aa1..e66d70f492 100644
--- a/code/modules/mob/living/carbon/alien/special/facehugger.dm
+++ b/code/modules/mob/living/carbon/alien/special/facehugger.dm
@@ -15,7 +15,7 @@
icon_state = "facehugger"
item_state = "facehugger"
w_class = WEIGHT_CLASS_TINY //note: can be picked up by aliens unlike most other items of w_class below 4
- clothing_flags = MASKINTERNALS
+ clothing_flags = ALLOWINTERNALS
throw_range = 5
tint = 3
flags_cover = MASKCOVERSEYES | MASKCOVERSMOUTH
@@ -33,16 +33,18 @@
/obj/item/clothing/mask/facehugger/lamarr
name = "Lamarr"
- sterile = 1
+ sterile = TRUE
/obj/item/clothing/mask/facehugger/dead
icon_state = "facehugger_dead"
item_state = "facehugger_inactive"
+ sterile = TRUE
stat = DEAD
/obj/item/clothing/mask/facehugger/impregnated
icon_state = "facehugger_impregnated"
item_state = "facehugger_impregnated"
+ sterile = TRUE
stat = DEAD
/obj/item/clothing/mask/facehugger/take_damage(damage_amount, damage_type = BRUTE, damage_flag = 0, sound_effect = 1, attack_dir)
diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm
index 92a993958a..263edf42c2 100644
--- a/code/modules/mob/living/carbon/carbon.dm
+++ b/code/modules/mob/living/carbon/carbon.dm
@@ -6,6 +6,7 @@
create_reagents(1000)
update_body_parts() //to update the carbon's new bodyparts appearance
GLOB.carbon_list += src
+ blood_volume = (BLOOD_VOLUME_NORMAL * blood_ratio)
/mob/living/carbon/Destroy()
//This must be done first, so the mob ghosts correctly before DNA etc is nulled
@@ -175,7 +176,7 @@
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(!(I.item_flags & (NODROP | ABSTRACT)))
+ else if(!CHECK_BITFIELD(I.item_flags, ABSTRACT) && !HAS_TRAIT(I, TRAIT_NODROP))
thrown_thing = I
dropItemToGround(I)
@@ -238,7 +239,7 @@
if(href_list["internal"])
var/slot = text2num(href_list["internal"])
var/obj/item/ITEM = get_item_by_slot(slot)
- if(ITEM && istype(ITEM, /obj/item/tank) && wear_mask && (wear_mask.clothing_flags & MASKINTERNALS))
+ if(ITEM && istype(ITEM, /obj/item/tank) && wear_mask && (wear_mask.clothing_flags & ALLOWINTERNALS))
visible_message("[usr] tries to [internal ? "close" : "open"] the valve on [src]'s [ITEM.name].", \
"[usr] tries to [internal ? "close" : "open"] the valve on [src]'s [ITEM.name].")
if(do_mob(usr, src, POCKET_STRIP_DELAY))
@@ -246,7 +247,7 @@
internal = null
update_internals_hud_icon(0)
else if(ITEM && istype(ITEM, /obj/item/tank))
- if((wear_mask && (wear_mask.clothing_flags & MASKINTERNALS)) || getorganslot(ORGAN_SLOT_BREATHING_TUBE))
+ if((wear_mask && (wear_mask.clothing_flags & ALLOWINTERNALS)) || getorganslot(ORGAN_SLOT_BREATHING_TUBE))
internal = ITEM
update_internals_hud_icon(1)
@@ -270,9 +271,13 @@
if(restrained())
changeNext_move(CLICK_CD_BREAKOUT)
last_special = world.time + CLICK_CD_BREAKOUT
+ var/buckle_cd = 600
+ if(handcuffed)
+ var/obj/item/restraints/O = src.get_item_by_slot(SLOT_HANDCUFFED)
+ buckle_cd = O.breakouttime
visible_message("[src] attempts to unbuckle [p_them()]self!", \
- "You attempt to unbuckle yourself... (This will take around one minute and you need to stay still.)")
- if(do_after(src, 600, 0, target = src))
+ "You attempt to unbuckle yourself... (This will take around [round(buckle_cd/600,1)] minute\s, and you need to stay still.)")
+ if(do_after(src, buckle_cd, 0, target = src))
if(!buckled)
return
buckled.user_unbuckle_mob(src,src)
@@ -409,7 +414,7 @@
return initial(pixel_y)
/mob/living/carbon/proc/accident(obj/item/I)
- if(!I || (I.item_flags & (NODROP | ABSTRACT)))
+ if(!I || (I.item_flags & ABSTRACT) || HAS_TRAIT(I, TRAIT_NODROP))
return
//dropItemToGround(I) CIT CHANGE - makes it so the item doesn't drop if the modifier rolls above 100
@@ -477,11 +482,13 @@
if(message)
visible_message("[src] throws up all over [p_them()]self!", \
"You throw up all over yourself!")
+ SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "vomit", /datum/mood_event/vomitself)
distance = 0
else
if(message)
visible_message("[src] throws up!", "You throw up!")
-
+ if(!isflyperson(src))
+ SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "vomit", /datum/mood_event/vomit)
if(stun)
Stun(80)
@@ -634,6 +641,18 @@
else
. += INFINITY
+/mob/living/carbon/get_permeability_protection(list/target_zones = list(HANDS,CHEST,GROIN,LEGS,FEET,ARMS,HEAD))
+ var/list/tally = list()
+ for(var/obj/item/I in get_equipped_items())
+ for(var/zone in target_zones)
+ if(I.body_parts_covered & zone)
+ tally["[zone]"] = max(1 - I.permeability_coefficient, target_zones["[zone]"])
+ var/protection = 0
+ for(var/key in tally)
+ protection += tally[key]
+ protection *= INVERSE(target_zones.len)
+ return protection
+
//this handles hud updates
/mob/living/carbon/update_damage_hud()
@@ -687,9 +706,10 @@
clear_fullscreen("critvision")
//Oxygen damage overlay
- if(oxyloss)
+ var/windedup = getOxyLoss() + getStaminaLoss() * 0.2
+ if(windedup)
var/severity = 0
- switch(oxyloss)
+ switch(windedup)
if(10 to 20)
severity = 1
if(20 to 25)
@@ -785,7 +805,8 @@
drop_all_held_items()
stop_pulling()
throw_alert("handcuffed", /obj/screen/alert/restrained/handcuffed, new_master = src.handcuffed)
- SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "handcuffed", /datum/mood_event/handcuffed)
+ if(handcuffed.demoralize_criminals)
+ SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "handcuffed", /datum/mood_event/handcuffed)
else
clear_alert("handcuffed")
SEND_SIGNAL(src, COMSIG_CLEAR_MOOD_EVENT, "handcuffed")
@@ -798,7 +819,7 @@
reagents.clear_reagents()
var/obj/item/organ/brain/B = getorgan(/obj/item/organ/brain)
if(B)
- B.damaged_brain = FALSE
+ B.brain_death = FALSE
for(var/thing in diseases)
var/datum/disease/D = thing
if(D.severity != DISEASE_SEVERITY_POSITIVE)
@@ -916,3 +937,17 @@
/mob/living/carbon/can_resist()
return bodyparts.len > 2 && ..()
+
+/mob/living/carbon/proc/hypnosis_vulnerable()//unused atm, but added in case
+ if(HAS_TRAIT(src, TRAIT_MINDSHIELD))
+ return FALSE
+ if(hallucinating())
+ return TRUE
+ if(IsSleeping())
+ return TRUE
+ if(HAS_TRAIT(src, TRAIT_DUMB))
+ return TRUE
+ GET_COMPONENT_FROM(mood, /datum/component/mood, src)
+ if(mood)
+ if(mood.sanity < SANITY_UNSTABLE)
+ return TRUE
diff --git a/code/modules/mob/living/carbon/carbon_defense.dm b/code/modules/mob/living/carbon/carbon_defense.dm
index 57483f64e7..bc64c8303b 100644
--- a/code/modules/mob/living/carbon/carbon_defense.dm
+++ b/code/modules/mob/living/carbon/carbon_defense.dm
@@ -405,16 +405,16 @@
if(istype(ears) && (deafen_pwr || damage_pwr))
var/ear_damage = damage_pwr * effect_amount
- var/deaf = max(ears.deaf, deafen_pwr * effect_amount)
+ var/deaf = deafen_pwr * effect_amount
adjustEarDamage(ear_damage,deaf)
- if(ears.ear_damage >= 15)
+ if(ears.damage >= 15)
to_chat(src, "Your ears start to ring badly!")
- if(prob(ears.ear_damage - 5))
+ if(prob(ears.damage - 5))
to_chat(src, "You can't hear anything!")
- ears.ear_damage = min(ears.ear_damage, UNHEALING_EAR_DAMAGE)
+ ears.damage = min(ears.damage, ears.maxHealth)
// you need earmuffs, inacusiate, or replacement
- else if(ears.ear_damage >= 5)
+ else if(ears.damage >= 5)
to_chat(src, "Your ears start to ring!")
SEND_SOUND(src, sound('sound/weapons/flash_ring.ogg',0,1,0,250))
return effect_amount //how soundbanged we are
diff --git a/code/modules/mob/living/carbon/carbon_defines.dm b/code/modules/mob/living/carbon/carbon_defines.dm
index f782289e18..41daf642f2 100644
--- a/code/modules/mob/living/carbon/carbon_defines.dm
+++ b/code/modules/mob/living/carbon/carbon_defines.dm
@@ -2,7 +2,7 @@
gender = MALE
pressure_resistance = 15
possible_a_intents = list(INTENT_HELP, INTENT_HARM)
- hud_possible = list(HEALTH_HUD,STATUS_HUD,ANTAG_HUD,GLAND_HUD,NANITE_HUD,DIAG_NANITE_FULL_HUD)
+ hud_possible = list(HEALTH_HUD,STATUS_HUD,ANTAG_HUD,GLAND_HUD,NANITE_HUD,DIAG_NANITE_FULL_HUD,RAD_HUD)
has_limbs = 1
held_items = list(null, null)
var/list/stomach_contents = list()
@@ -11,8 +11,8 @@
var/silent = FALSE //Can't talk. Value goes down every life proc. //NOTE TO FUTURE CODERS: DO NOT INITIALIZE NUMERICAL VARS AS NULL OR I WILL MURDER YOU.
var/dreaming = 0 //How many dream images we have left to send
- var/obj/item/handcuffed = null //Whether or not the mob is handcuffed
- var/obj/item/legcuffed = null //Same as handcuffs but for legs. Bear traps use this.
+ var/obj/item/restraints/handcuffed //Whether or not the mob is handcuffed
+ var/obj/item/restraints/legcuffed //Same as handcuffs but for legs. Bear traps use this.
var/disgust = 0
diff --git a/code/modules/mob/living/carbon/damage_procs.dm b/code/modules/mob/living/carbon/damage_procs.dm
index 749ae3b5b0..cc0c0d7434 100644
--- a/code/modules/mob/living/carbon/damage_procs.dm
+++ b/code/modules/mob/living/carbon/damage_procs.dm
@@ -40,14 +40,13 @@
update_damage_overlays()
else
adjustStaminaLoss(damage * hit_percent)
- if(BRAIN)
- adjustBrainLoss(damage * hit_percent)
//citadel code
if(AROUSAL)
adjustArousalLoss(damage * hit_percent)
return TRUE
+
//These procs fetch a cumulative total damage from all bodyparts
/mob/living/carbon/getBruteLoss()
var/amount = 0
@@ -113,6 +112,51 @@
return
adjustStaminaLoss(diff, updating, forced)
+/** adjustOrganLoss
+ * inputs: slot (organ slot, like ORGAN_SLOT_HEART), amount (damage to be done), and maximum (currently an arbitrarily large number, can be set so as to limit damage)
+ * outputs:
+ * description: If an organ exists in the slot requested, and we are capable of taking damage (we don't have GODMODE on), call the damage proc on that organ.
+ */
+/mob/living/carbon/adjustOrganLoss(slot, amount, maximum)
+ var/obj/item/organ/O = getorganslot(slot)
+ if(O && !(status_flags & GODMODE))
+ if(!maximum)
+ maximum = O.maxHealth
+ O.applyOrganDamage(amount, maximum)
+ O.onDamage(amount, maximum)
+
+/** setOrganLoss
+ * inputs: slot (organ slot, like ORGAN_SLOT_HEART), amount(damage to be set to)
+ * outputs:
+ * description: If an organ exists in the slot requested, and we are capable of taking damage (we don't have GODMODE on), call the set damage proc on that organ, which can
+ * set or clear the failing variable on that organ, making it either cease or start functions again, unlike adjustOrganLoss.
+ */
+/mob/living/carbon/setOrganLoss(slot, amount)
+ var/obj/item/organ/O = getorganslot(slot)
+ if(O && !(status_flags & GODMODE))
+ O.setOrganDamage(amount)
+ O.onSetDamage(amount)
+
+/** getOrganLoss
+ * inputs: slot (organ slot, like ORGAN_SLOT_HEART)
+ * outputs: organ damage
+ * description: If an organ exists in the slot requested, return the amount of damage that organ has
+ */
+/mob/living/carbon/getOrganLoss(slot)
+ var/obj/item/organ/O = getorganslot(slot)
+ if(O)
+ return O.damage
+
+/mob/living/carbon/proc/adjustAllOrganLoss(amount, maximum)
+ for(var/obj/item/organ/O in internal_organs)
+ if(O && !(status_flags & GODMODE))
+ continue
+ if(!maximum)
+ maximum = O.maxHealth
+ O.applyOrganDamage(amount, maximum)
+ O.onDamage(amount, maximum)
+
+
////////////////////////////////////////////
//Returns a list of damaged bodyparts
@@ -213,24 +257,25 @@
update_damage_overlays()
update_stamina()
-/mob/living/carbon/getBrainLoss()
+/* TO_REMOVE
+/mob/living/carbon/getOrganLoss(ORGAN_SLOT_BRAIN)
. = 0
var/obj/item/organ/brain/B = getorganslot(ORGAN_SLOT_BRAIN)
if(B)
. = B.get_brain_damage()
//Some sources of brain damage shouldn't be deadly
-/mob/living/carbon/adjustBrainLoss(amount, maximum = BRAIN_DAMAGE_DEATH)
+/mob/living/carbon/adjustOrganLoss(ORGAN_SLOT_BRAIN, amount, maximum = BRAIN_DAMAGE_DEATH)
if(status_flags & GODMODE)
return FALSE
- var/prev_brainloss = getBrainLoss()
+ var/prev_brainloss = getOrganLoss(ORGAN_SLOT_BRAIN)
var/obj/item/organ/brain/B = getorganslot(ORGAN_SLOT_BRAIN)
if(!B)
return
B.adjust_brain_damage(amount, maximum)
if(amount <= 0) //cut this early
return
- var/brainloss = getBrainLoss()
+ var/brainloss = getOrganLoss(ORGAN_SLOT_BRAIN)
if(brainloss > BRAIN_DAMAGE_MILD)
if(prob(amount * ((2 * (100 + brainloss - BRAIN_DAMAGE_MILD)) / 100))) //Base chance is the hit damage; for every point of damage past the threshold the chance is increased by 2%
gain_trauma_type(BRAIN_TRAUMA_MILD)
@@ -253,3 +298,4 @@
if(B)
var/adjusted_amount = amount - B.get_brain_damage()
B.adjust_brain_damage(adjusted_amount, null)
+*/
diff --git a/code/modules/mob/living/carbon/examine.dm b/code/modules/mob/living/carbon/examine.dm
index 22da46346c..c029eac12b 100644
--- a/code/modules/mob/living/carbon/examine.dm
+++ b/code/modules/mob/living/carbon/examine.dm
@@ -91,6 +91,7 @@
if(combatmode)
msg += "[t_He] [t_is] visibly tense[resting ? "." : ", and [t_is] standing in combative stance."]\n"
+ msg += common_trait_examine()
GET_COMPONENT_FROM(mood, /datum/component/mood, src)
if(mood)
diff --git a/code/modules/mob/living/carbon/human/dummy.dm b/code/modules/mob/living/carbon/human/dummy.dm
index 3406d29b3a..6da188dd2d 100644
--- a/code/modules/mob/living/carbon/human/dummy.dm
+++ b/code/modules/mob/living/carbon/human/dummy.dm
@@ -4,6 +4,7 @@
status_flags = GODMODE|CANPUSH
mouse_drag_pointer = MOUSE_INACTIVE_POINTER
var/in_use = FALSE
+ no_vore = TRUE
INITIALIZE_IMMEDIATE(/mob/living/carbon/human/dummy)
diff --git a/code/modules/mob/living/carbon/human/examine.dm b/code/modules/mob/living/carbon/human/examine.dm
index 8550a0887f..6ebc4f8a32 100644
--- a/code/modules/mob/living/carbon/human/examine.dm
+++ b/code/modules/mob/living/carbon/human/examine.dm
@@ -247,7 +247,7 @@
if(DISGUST_LEVEL_DISGUSTED to INFINITY)
msg += "[t_He] look[p_s()] extremely disgusted.\n"
- if(blood_volume < BLOOD_VOLUME_SAFE)
+ if(blood_volume < (BLOOD_VOLUME_SAFE*blood_ratio))
msg += "[t_He] [t_has] pale skin.\n"
if(bleedsuppress)
@@ -281,6 +281,13 @@
if(91.01 to INFINITY)
msg += "[t_He] [t_is] a shitfaced, slobbering wreck.\n"
+ if(reagents.has_reagent("astral"))
+ msg += "[t_He] have wild, spacey eyes"
+ if(mind)
+ msg += " and have a strange, abnormal look to them.\n"
+ else
+ msg += " and don't look like they're all there.\n"
+
if(isliving(user))
var/mob/living/L = user
if(src != user && HAS_TRAIT(L, TRAIT_EMPATH) && !appears_dead)
@@ -304,6 +311,13 @@
msg += ""
+ var/obj/item/organ/vocal_cords/Vc = user.getorganslot(ORGAN_SLOT_VOICE)
+ if(Vc)
+ if(istype(Vc, /obj/item/organ/vocal_cords/velvet))
+ if(client?.prefs.lewdchem)
+ msg += "You feel your chords resonate looking at them.\n"
+
+
if(!appears_dead)
if(stat == UNCONSCIOUS)
msg += "[t_He] [t_is]n't responding to anything around [t_him] and seem[p_s()] to be asleep.\n"
@@ -321,6 +335,8 @@
if(digitalcamo)
msg += "[t_He] [t_is] moving [t_his] body in an unnatural and blatantly inhuman manner.\n"
+ msg += common_trait_examine()
+
var/traitstring = get_trait_string()
if(ishuman(user))
var/mob/living/carbon/human/H = user
diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm
index a4fca9dd78..02e6043462 100644
--- a/code/modules/mob/living/carbon/human/human.dm
+++ b/code/modules/mob/living/carbon/human/human.dm
@@ -252,7 +252,7 @@
var/delay_denominator = 1
if(pocket_item && !(pocket_item.item_flags & ABSTRACT))
- if(pocket_item.item_flags & NODROP)
+ if(HAS_TRAIT(pocket_item, TRAIT_NODROP))
to_chat(usr, "You try to empty [src]'s [pocket_side] pocket, it seems to be stuck!")
to_chat(usr, "You try to empty [src]'s [pocket_side] pocket.")
else if(place_item && place_item.mob_can_equip(src, usr, pocket_id, 1) && !(place_item.item_flags & ABSTRACT))
@@ -623,6 +623,7 @@
facial_hair_style = "Shaved"
hair_style = pick("Bedhead", "Bedhead 2", "Bedhead 3")
underwear = "Nude"
+ undershirt = "Nude"
update_body()
update_hair()
update_genitals()
@@ -801,6 +802,11 @@
else
hud_used.healthdoll.icon_state = "healthdoll_DEAD"
+ if(hud_used.staminas)
+ hud_used.staminas.icon_state = staminahudamount()
+ if(hud_used.staminabuffer)
+ hud_used.staminabuffer.icon_state = staminabufferhudamount()
+
/mob/living/carbon/human/fully_heal(admin_revive = 0)
if(admin_revive)
regenerate_limbs()
@@ -811,6 +817,8 @@
for(var/datum/mutation/human/HM in dna.mutations)
if(HM.quality != POSITIVE)
dna.remove_mutation(HM.name)
+ if(blood_volume < (BLOOD_VOLUME_NORMAL*blood_ratio))
+ blood_volume = (BLOOD_VOLUME_NORMAL*blood_ratio)
..()
/mob/living/carbon/human/check_weakness(obj/item/weapon, mob/living/attacker)
@@ -851,60 +859,89 @@
.["Copy outfit"] = "?_src_=vars;[HrefToken()];copyoutfit=[REF(src)]"
/mob/living/carbon/human/MouseDrop_T(mob/living/target, mob/living/user)
- //If they dragged themselves and we're currently aggressively grabbing them try to piggyback
- if(user == target && can_piggyback(target) && pulling == target && (HAS_TRAIT(src, TRAIT_PACIFISM) || grab_state >= GRAB_AGGRESSIVE) && stat == CONSCIOUS)
- buckle_mob(target,TRUE,TRUE)
+ if(pulling == target && grab_state >= GRAB_AGGRESSIVE && stat == CONSCIOUS)
+ //If they dragged themselves and we're currently aggressively grabbing them try to piggyback
+ if(user == target && can_piggyback(target))
+ piggyback(target)
+ return
+ //If you dragged them to you and you're aggressively grabbing try to fireman carry them
+ else if(user != target)
+ fireman_carry(target)
+ return
. = ..()
-/mob/living/carbon/human/proc/piggyback_instant(mob/living/M)
- return buckle_mob(M, TRUE, TRUE, FALSE, TRUE)
+//src is the user that will be carrying, target is the mob to be carried
+/mob/living/carbon/human/proc/can_piggyback(mob/living/carbon/target)
+ return (istype(target) && target.stat == CONSCIOUS)
-//Can C try to piggyback at all.
-/mob/living/carbon/human/proc/can_piggyback(mob/living/carbon/C)
- if(istype(C) && C.stat == CONSCIOUS)
- return TRUE
- return FALSE
+/mob/living/carbon/human/proc/can_be_firemanned(mob/living/carbon/target)
+ return (ishuman(target) && target.lying)
-/mob/living/carbon/human/buckle_mob(mob/living/M, force = FALSE, check_loc = TRUE, bypass_piggybacking = FALSE, no_delay = FALSE)
+/mob/living/carbon/human/proc/fireman_carry(mob/living/carbon/target)
+ if(can_be_firemanned(target))
+ visible_message("[src] starts lifting [target] onto their back...",
+ "You start lifting [target] onto your back...")
+ if(do_after(src, 30, TRUE, target))
+ //Second check to make sure they're still valid to be carried
+ if(can_be_firemanned(target) && !incapacitated(FALSE, TRUE))
+ target.resting = FALSE
+ buckle_mob(target, TRUE, TRUE, 90, 1, 0)
+ return
+ visible_message("[src] fails to fireman carry [target]!")
+ else
+ to_chat(src, "You can't fireman carry [target] while they're standing!")
+
+/mob/living/carbon/human/proc/piggyback(mob/living/carbon/target)
+ if(can_piggyback(target))
+ visible_message("[target] starts to climb onto [src]...")
+ if(do_after(target, 15, target = src))
+ if(can_piggyback(target))
+ if(target.incapacitated(FALSE, TRUE) || incapacitated(FALSE, TRUE))
+ target.visible_message("[target] can't hang onto [src]!")
+ return
+ buckle_mob(target, TRUE, TRUE, FALSE, 0, 2)
+ else
+ visible_message("[target] fails to climb onto [src]!")
+ else
+ to_chat(target, "You can't piggyback ride [src] right now!")
+
+/mob/living/carbon/human/buckle_mob(mob/living/target, force = FALSE, check_loc = TRUE, lying_buckle = FALSE, hands_needed = 0, target_hands_needed = 0)
if(!force)//humans are only meant to be ridden through piggybacking and special cases
return
- if(bypass_piggybacking)
- return ..()
- if(!is_type_in_typecache(M, can_ride_typecache))
- M.visible_message("[M] really can't seem to mount [src]...")
+ if(!is_type_in_typecache(target, can_ride_typecache))
+ target.visible_message("[target] really can't seem to mount [src]...")
return
+ buckle_lying = lying_buckle
var/datum/component/riding/human/riding_datum = LoadComponent(/datum/component/riding/human)
- riding_datum.ride_check_rider_incapacitated = TRUE
- riding_datum.ride_check_rider_restrained = TRUE
- riding_datum.set_riding_offsets(RIDING_OFFSET_ALL, list(TEXT_NORTH = list(0, 6), TEXT_SOUTH = list(0, 6), TEXT_EAST = list(-6, 4), TEXT_WEST = list( 6, 4)))
- if(buckled_mobs && ((M in buckled_mobs) || (buckled_mobs.len >= max_buckled_mobs)) || buckled || (M.stat != CONSCIOUS))
+ if(target_hands_needed)
+ riding_datum.ride_check_rider_restrained = TRUE
+ if(buckled_mobs && ((target in buckled_mobs) || (buckled_mobs.len >= max_buckled_mobs)) || buckled)
return
- if(can_piggyback(M))
- riding_datum.ride_check_ridden_incapacitated = TRUE
- visible_message("[M] starts to climb onto [src]...")
- if(no_delay || do_after(M, 15, target = src))
- if(can_piggyback(M))
- if(M.incapacitated(FALSE, TRUE) || incapacitated(FALSE, TRUE))
- M.visible_message("[M] can't hang onto [src]!")
- return
- if(!riding_datum.equip_buckle_inhands(M, 2)) //MAKE SURE THIS IS LAST!!
- M.visible_message("[M] can't climb onto [src]!")
- return
- . = ..(M, force, check_loc)
- stop_pulling()
- else
- visible_message("[M] fails to climb onto [src]!")
- else
- . = ..(M,force,check_loc)
- stop_pulling()
+ var/equipped_hands_self
+ var/equipped_hands_target
+ if(hands_needed)
+ equipped_hands_self = riding_datum.equip_buckle_inhands(src, hands_needed, target)
+ if(target_hands_needed)
+ equipped_hands_target = riding_datum.equip_buckle_inhands(target, target_hands_needed)
+
+ if(hands_needed || target_hands_needed)
+ if(hands_needed && !equipped_hands_self)
+ src.visible_message("[src] can't get a grip on [target] because their hands are full!",
+ "You can't get a grip on [target] because your hands are full!")
+ return
+ else if(target_hands_needed && !equipped_hands_target)
+ target.visible_message("[target] can't get a grip on [src] because their hands are full!",
+ "You can't get a grip on [src] because your hands are full!")
+ return
+
+ stop_pulling()
+ riding_datum.handle_vehicle_layer()
+ . = ..(target, force, check_loc)
/mob/living/carbon/human/proc/is_shove_knockdown_blocked() //If you want to add more things that block shove knockdown, extend this
- var/list/body_parts = list(head, wear_mask, wear_suit, w_uniform, back, gloves, shoes, belt, s_store, glasses, ears, wear_id) //Everything but pockets. Pockets are l_store and r_store. (if pockets were allowed, putting something armored, gloves or hats for example, would double up on the armor)
- for(var/bp in body_parts)
- if(istype(bp, /obj/item/clothing))
- var/obj/item/clothing/C = bp
- if(C.blocks_shove_knockdown)
- return TRUE
+ for(var/obj/item/clothing/C in get_equipped_items()) //doesn't include pockets
+ if(C.blocks_shove_knockdown)
+ return TRUE
return FALSE
/mob/living/carbon/human/proc/clear_shove_slowdown()
@@ -1029,8 +1066,8 @@
/mob/living/carbon/human/species/lizard/ashwalker
race = /datum/species/lizard/ashwalker
-/mob/living/carbon/human/species/moth
- race = /datum/species/moth
+/mob/living/carbon/human/species/insect
+ race = /datum/species/insect
/mob/living/carbon/human/species/mush
race = /datum/species/mush
diff --git a/code/modules/mob/living/carbon/human/human_defense.dm b/code/modules/mob/living/carbon/human/human_defense.dm
index b42346382f..a44779e05e 100644
--- a/code/modules/mob/living/carbon/human/human_defense.dm
+++ b/code/modules/mob/living/carbon/human/human_defense.dm
@@ -27,7 +27,7 @@
for(var/bp in body_parts)
if(!bp)
continue
- if(bp && istype(bp , /obj/item/clothing))
+ if(istype(bp, /obj/item/clothing))
var/obj/item/clothing/C = bp
if(C.body_parts_covered & def_zone.body_part)
protection += C.armor.getRating(d_type)
@@ -348,10 +348,15 @@
if(temp)
var/update = 0
var/dmg = rand(M.force/2, M.force)
+ var/atom/throw_target = get_edge_target_turf(src, M.dir)
switch(M.damtype)
if("brute")
- if(M.force > 20)
- Unconscious(20)
+ if(M.force > 35) // durand and other heavy mechas
+ Knockdown(50)
+ src.throw_at(throw_target, rand(1,5), 7)
+ else if(M.force >= 20 && !IsKnockdown()) // lightweight mechas like gygax
+ Knockdown(30)
+ src.throw_at(throw_target, rand(1,3), 7)
update |= temp.receive_damage(dmg, 0)
playsound(src, 'sound/weapons/punch4.ogg', 50, 1)
if("fire")
@@ -654,6 +659,7 @@
if(health >= 0)
if(src == M)
+ var/to_send = ""
visible_message("[src] examines [p_them()]self.", \
"You check yourself for injuries.")
@@ -700,53 +706,100 @@
var/no_damage
if(status == "OK" || status == "no damage")
no_damage = TRUE
- to_chat(src, "\t Your [LB.name] [HAS_TRAIT(src, TRAIT_SELF_AWARE) ? "has" : "is"] [status].")
+ to_send += "\t Your [LB.name] [HAS_TRAIT(src, TRAIT_SELF_AWARE) ? "has" : "is"] [status].\n"
for(var/obj/item/I in LB.embedded_objects)
- to_chat(src, "\t There is \a [I] embedded in your [LB.name]!")
+ to_send += "\t There is \a [I] embedded in your [LB.name]!\n"
for(var/t in missing)
- to_chat(src, "Your [parse_zone(t)] is missing!")
+ to_send += "Your [parse_zone(t)] is missing!\n"
if(bleed_rate)
- to_chat(src, "You are bleeding!")
+ to_send += "You are bleeding!\n"
if(getStaminaLoss())
if(getStaminaLoss() > 30)
- to_chat(src, "You're completely exhausted.")
+ to_send += "You're completely exhausted.\n"
else
- to_chat(src, "You feel fatigued.")
+ to_send += "You feel fatigued.\n"
if(HAS_TRAIT(src, TRAIT_SELF_AWARE))
if(toxloss)
if(toxloss > 10)
- to_chat(src, "You feel sick.")
+ to_send += "You feel sick.\n"
else if(toxloss > 20)
- to_chat(src, "You feel nauseated.")
+ to_send += "You feel nauseated.\n"
else if(toxloss > 40)
- to_chat(src, "You feel very unwell!")
+ to_send += "You feel very unwell!\n"
if(oxyloss)
if(oxyloss > 10)
- to_chat(src, "You feel lightheaded.")
+ to_send += "You feel lightheaded.\n"
else if(oxyloss > 20)
- to_chat(src, "Your thinking is clouded and distant.")
+ to_send += "Your thinking is clouded and distant.\n"
else if(oxyloss > 30)
- to_chat(src, "You're choking!")
+ to_send += "You're choking!\n"
switch(nutrition)
if(NUTRITION_LEVEL_FULL to INFINITY)
- to_chat(src, "You're completely stuffed!")
+ to_send += "You're completely stuffed!\n"
if(NUTRITION_LEVEL_WELL_FED to NUTRITION_LEVEL_FULL)
- to_chat(src, "You're well fed!")
+ to_send += "You're well fed!\n"
if(NUTRITION_LEVEL_FED to NUTRITION_LEVEL_WELL_FED)
- to_chat(src, "You're not hungry.")
+ to_send += "You're not hungry.\n"
if(NUTRITION_LEVEL_HUNGRY to NUTRITION_LEVEL_FED)
- to_chat(src, "You could use a bite to eat.")
+ to_send += "You could use a bite to eat.\n"
if(NUTRITION_LEVEL_STARVING to NUTRITION_LEVEL_HUNGRY)
- to_chat(src, "You feel quite hungry.")
+ to_send += "You feel quite hungry.\n"
if(0 to NUTRITION_LEVEL_STARVING)
- to_chat(src, "You're starving!")
+ to_send += "You're starving!\n"
+
+
+ //TODO: Convert these messages into vague messages, thereby encouraging actual dignosis.
+ //Compiles then shows the list of damaged organs and broken organs
+ var/list/broken = list()
+ var/list/damaged = list()
+ var/broken_message
+ var/damaged_message
+ var/broken_plural
+ var/damaged_plural
+ //Sets organs into their proper list
+ for(var/O in internal_organs)
+ var/obj/item/organ/organ = O
+ if(organ.organ_flags & ORGAN_FAILING)
+ if(broken.len)
+ broken += ", "
+ broken += organ.name
+ else if(organ.damage > organ.low_threshold)
+ if(damaged.len)
+ damaged += ", "
+ damaged += organ.name
+ //Checks to enforce proper grammar, inserts words as necessary into the list
+ if(broken.len)
+ if(broken.len > 1)
+ broken.Insert(broken.len, "and ")
+ broken_plural = TRUE
+ else
+ var/holder = broken[1] //our one and only element
+ if(holder[lentext(holder)] == "s")
+ broken_plural = TRUE
+ //Put the items in that list into a string of text
+ for(var/B in broken)
+ broken_message += B
+ to_chat(src, " Your [broken_message] [broken_plural ? "are" : "is"] non-functional!")
+ if(damaged.len)
+ if(damaged.len > 1)
+ damaged.Insert(damaged.len, "and ")
+ damaged_plural = TRUE
+ else
+ var/holder = damaged[1]
+ if(holder[lentext(holder)] == "s")
+ damaged_plural = TRUE
+ for(var/D in damaged)
+ damaged_message += D
+ to_chat(src, "Your [damaged_message] [damaged_plural ? "are" : "is"] hurt.")
if(roundstart_quirks.len)
- to_chat(src, "You have these quirks: [get_trait_string()].")
+ to_send += "You have these quirks: [get_trait_string()].\n"
+
+ to_chat(src, to_send)
else
if(wear_suit)
wear_suit.add_fingerprint(M)
diff --git a/code/modules/mob/living/carbon/human/human_defines.dm b/code/modules/mob/living/carbon/human/human_defines.dm
index de976da40f..e32d073500 100644
--- a/code/modules/mob/living/carbon/human/human_defines.dm
+++ b/code/modules/mob/living/carbon/human/human_defines.dm
@@ -1,5 +1,5 @@
/mob/living/carbon/human
- hud_possible = list(HEALTH_HUD,STATUS_HUD,ID_HUD,WANTED_HUD,IMPLOYAL_HUD,IMPCHEM_HUD,IMPTRACK_HUD, NANITE_HUD, DIAG_NANITE_FULL_HUD,ANTAG_HUD,GLAND_HUD,SENTIENT_DISEASE_HUD)
+ hud_possible = list(HEALTH_HUD,STATUS_HUD,ID_HUD,WANTED_HUD,IMPLOYAL_HUD,IMPCHEM_HUD,IMPTRACK_HUD, NANITE_HUD, DIAG_NANITE_FULL_HUD,ANTAG_HUD,GLAND_HUD,SENTIENT_DISEASE_HUD,RAD_HUD)
hud_type = /datum/hud/human
possible_a_intents = list(INTENT_HELP, INTENT_DISARM, INTENT_GRAB, INTENT_HARM)
pressure_resistance = 25
@@ -17,6 +17,8 @@
//Eye colour
var/eye_color = "000"
+ var/horn_color = "85615a" //specific horn colors, because why not?
+
var/skin_tone = "caucasian1" //Skin tone
var/lip_style = null //no lipstick by default- arguably misleading, as it could be used for general makeup
@@ -25,9 +27,13 @@
var/age = 30 //Player's age
var/underwear = "Nude" //Which underwear the player wants
+ var/undie_color = "FFFFFF"
var/undershirt = "Nude" //Which undershirt the player wants
+ var/shirt_color = "FFFFFF"
var/socks = "Nude" //Which socks the player wants
+ var/socks_color = "FFFFFF"
var/backbag = DBACKPACK //Which backpack type the player has chosen.
+ var/jumpsuit_style = PREF_SUIT //suit/skirt
//Equipment slots
var/obj/item/wear_suit = null
@@ -44,6 +50,7 @@
var/bleedsuppress = 0 //for stopping bloodloss, eventually this will be limb-based like bleeding
var/name_override //For temporary visible name changes
+ var/genital_override = FALSE //Force genitals on things incase of chems
var/nameless = FALSE //For drones of both the insectoid and robotic kind. And other types of nameless critters.
diff --git a/code/modules/mob/living/carbon/human/human_helpers.dm b/code/modules/mob/living/carbon/human/human_helpers.dm
index 0b40d3d26a..b65d62b63b 100644
--- a/code/modules/mob/living/carbon/human/human_helpers.dm
+++ b/code/modules/mob/living/carbon/human/human_helpers.dm
@@ -111,26 +111,6 @@
return ..()
-/mob/living/carbon/human/get_permeability_protection()
- var/list/prot = list("hands"=0, "chest"=0, "groin"=0, "legs"=0, "feet"=0, "arms"=0, "head"=0)
- for(var/obj/item/I in get_equipped_items())
- if(I.body_parts_covered & HANDS)
- prot["hands"] = max(1 - I.permeability_coefficient, prot["hands"])
- if(I.body_parts_covered & CHEST)
- prot["chest"] = max(1 - I.permeability_coefficient, prot["chest"])
- if(I.body_parts_covered & GROIN)
- prot["groin"] = max(1 - I.permeability_coefficient, prot["groin"])
- if(I.body_parts_covered & LEGS)
- prot["legs"] = max(1 - I.permeability_coefficient, prot["legs"])
- if(I.body_parts_covered & FEET)
- prot["feet"] = max(1 - I.permeability_coefficient, prot["feet"])
- if(I.body_parts_covered & ARMS)
- prot["arms"] = max(1 - I.permeability_coefficient, prot["arms"])
- if(I.body_parts_covered & HEAD)
- prot["head"] = max(1 - I.permeability_coefficient, prot["head"])
- var/protection = (prot["head"] + prot["arms"] + prot["feet"] + prot["legs"] + prot["groin"] + prot["chest"] + prot["hands"])/7
- return protection
-
/mob/living/carbon/human/can_use_guns(obj/item/G)
. = ..()
diff --git a/code/modules/mob/living/carbon/human/inventory.dm b/code/modules/mob/living/carbon/human/inventory.dm
index 176d967d52..d35df6b789 100644
--- a/code/modules/mob/living/carbon/human/inventory.dm
+++ b/code/modules/mob/living/carbon/human/inventory.dm
@@ -167,9 +167,9 @@
dropItemToGround(r_store, TRUE) //Again, makes sense for pockets to drop.
if(l_store)
dropItemToGround(l_store, TRUE)
- if(wear_id)
+ if(wear_id && !CHECK_BITFIELD(wear_id.item_flags, NO_UNIFORM_REQUIRED))
dropItemToGround(wear_id)
- if(belt)
+ if(belt && !CHECK_BITFIELD(belt.item_flags, NO_UNIFORM_REQUIRED))
dropItemToGround(belt)
w_uniform = null
update_suit_sensors()
diff --git a/code/modules/mob/living/carbon/human/life.dm b/code/modules/mob/living/carbon/human/life.dm
index 1af9dbc5f5..9395283aeb 100644
--- a/code/modules/mob/living/carbon/human/life.dm
+++ b/code/modules/mob/living/carbon/human/life.dm
@@ -39,6 +39,10 @@
//Stuff jammed in your limbs hurts
handle_embedded_objects()
+ if(stat != DEAD)
+ //process your dick energy
+ handle_arousal()
+
//Update our name based on whether our face is obscured/disfigured
name = get_visible_name()
@@ -54,7 +58,7 @@
var/obj/item/clothing/CH = head
if (CS.clothing_flags & CH.clothing_flags & STOPSPRESSUREDAMAGE)
return ONE_ATMOSPHERE
- if(istype(loc, /obj/belly)) //START OF CIT CHANGES - Makes it so you don't suffocate while inside vore organs. Remind me to modularize this some time - Bhijn
+ if(isbelly(loc)) //START OF CIT CHANGES - Makes it so you don't suffocate while inside vore organs. Remind me to modularize this some time - Bhijn
return ONE_ATMOSPHERE
if(istype(loc, /obj/item/dogborg/sleeper))
return ONE_ATMOSPHERE //END OF CIT CHANGES
@@ -70,7 +74,7 @@
else if(eye_blurry) //blurry eyes heal slowly
adjust_blurriness(-1)
- if (getBrainLoss() >= 30) //Citadel change to make memes more often.
+ if (getOrganLoss(ORGAN_SLOT_BRAIN) >= 30) //Citadel change to make memes more often.
SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "brain_damage", /datum/mood_event/brain_damage)
if(prob(3))
if(prob(25))
diff --git a/code/modules/mob/living/carbon/human/login.dm b/code/modules/mob/living/carbon/human/login.dm
deleted file mode 100644
index 1ac24cffa9..0000000000
--- a/code/modules/mob/living/carbon/human/login.dm
+++ /dev/null
@@ -1,9 +0,0 @@
-/mob/living/carbon/human/Login()
- ..()
- if(src.martial_art == default_martial_art && mind.stored_martial_art) //If the mind has a martial art stored and the body has the default one.
- src.mind.stored_martial_art.teach(src) //Running teach so that it deals with help verbs.
- else if(src.martial_art != default_martial_art && src.martial_art != mind.stored_martial_art) //If the body has a martial art which is not the default one and is not stored in the mind.
- if(src.martial_art_owner != mind)
- src.martial_art.remove(src)
- else
- src.mind.stored_martial_art = src.martial_art
diff --git a/code/modules/mob/living/carbon/human/say.dm b/code/modules/mob/living/carbon/human/say.dm
index 673249b186..eee425063d 100644
--- a/code/modules/mob/living/carbon/human/say.dm
+++ b/code/modules/mob/living/carbon/human/say.dm
@@ -10,29 +10,6 @@
else
. = ..()
-/mob/living/carbon/human/treat_message(message)
- message = dna.species.handle_speech(message,src)
- if(diseases.len)
- for(var/datum/disease/pierrot_throat/D in diseases)
- var/list/temp_message = splittext(message, " ") //List each word in the message
- var/list/pick_list = list()
- for(var/i = 1, i <= temp_message.len, i++) //Create a second list for excluding words down the line
- pick_list += i
- for(var/i=1, ((i <= D.stage) && (i <= temp_message.len)), i++) //Loop for each stage of the disease or until we run out of words
- if(prob(3 * D.stage)) //Stage 1: 3% Stage 2: 6% Stage 3: 9% Stage 4: 12%
- var/H = pick(pick_list)
- if(findtext(temp_message[H], "*") || findtext(temp_message[H], ";") || findtext(temp_message[H], ":"))
- continue
- temp_message[H] = "HONK"
- pick_list -= H //Make sure that you dont HONK the same word twice
- message = jointext(temp_message, " ")
- message = ..(message)
- message = dna.mutations_say_mods(message)
- return message
-
-/mob/living/carbon/human/get_spans()
- return ..() | dna.mutations_get_spans() | dna.species_get_spans()
-
/mob/living/carbon/human/GetVoice()
if(istype(wear_mask, /obj/item/clothing/mask/chameleon))
var/obj/item/clothing/mask/chameleon/V = wear_mask
@@ -76,14 +53,14 @@
if(ears)
var/obj/item/radio/headset/dongle = ears
if(!istype(dongle))
- return 0
+ return FALSE
if(dongle.translate_binary)
- return 1
+ return TRUE
/mob/living/carbon/human/radio(message, message_mode, list/spans, language)
. = ..()
- if(. != 0)
- return .
+ if(.)
+ return
switch(message_mode)
if(MODE_HEADSET)
diff --git a/code/modules/mob/living/carbon/human/species.dm b/code/modules/mob/living/carbon/human/species.dm
index 3324b07217..1f843483ee 100644
--- a/code/modules/mob/living/carbon/human/species.dm
+++ b/code/modules/mob/living/carbon/human/species.dm
@@ -1,6 +1,7 @@
// This code handles different species in the game.
GLOBAL_LIST_EMPTY(roundstart_races)
+GLOBAL_LIST_EMPTY(roundstart_race_names)
/datum/species
var/id // if the game needs to manually check your race to do something not included in a proc here, it will use this
@@ -8,13 +9,35 @@ GLOBAL_LIST_EMPTY(roundstart_races)
var/name // this is the fluff name. these will be left generic (such as 'Lizardperson' for the lizard race) so servers can change them to whatever
var/default_color = "#FFF" // if alien colors are disabled, this is the color that will be used by that race
- var/sexes = 1 // whether or not the race has sexual characteristics. at the moment this is only 0 for skeletons and shadows
+ var/sexes = 1 // whether or not the race has sexual characteristics. at the moment this is only 0 for skeletons and shadows
- var/list/offset_features = list(OFFSET_UNIFORM = list(0,0), OFFSET_ID = list(0,0), OFFSET_GLOVES = list(0,0), OFFSET_GLASSES = list(0,0), OFFSET_EARS = list(0,0), OFFSET_SHOES = list(0,0), OFFSET_S_STORE = list(0,0), OFFSET_FACEMASK = list(0,0), OFFSET_HEAD = list(0,0), OFFSET_FACE = list(0,0), OFFSET_BELT = list(0,0), OFFSET_BACK = list(0,0), OFFSET_SUIT = list(0,0), OFFSET_NECK = list(0,0))
+ //Species Icon Drawing Offsets - Pixel X, Pixel Y, Aka X = Horizontal and Y = Vertical, from bottom left corner
+ var/list/offset_features = list(
+ OFFSET_UNIFORM = list(0,0),
+ OFFSET_ID = list(0,0),
+ OFFSET_GLOVES = list(0,0),
+ OFFSET_GLASSES = list(0,0),
+ OFFSET_EARS = list(0,0),
+ OFFSET_SHOES = list(0,0),
+ OFFSET_S_STORE = list(0,0),
+ OFFSET_FACEMASK = list(0,0),
+ OFFSET_HEAD = list(0,0),
+ OFFSET_EYES = list(0,0),
+ OFFSET_LIPS = list(0,0),
+ OFFSET_BELT = list(0,0),
+ OFFSET_BACK = list(0,0),
+ OFFSET_HAIR = list(0,0),
+ OFFSET_FHAIR = list(0,0),
+ OFFSET_SUIT = list(0,0),
+ OFFSET_NECK = list(0,0),
+ OFFSET_MUTPARTS = list(0,0)
+ )
var/hair_color // this allows races to have specific hair colors... if null, it uses the H's hair/facial hair colors. if "mutcolor", it uses the H's mutant_color
var/hair_alpha = 255 // the alpha used by the hair. 255 is completely solid, 0 is transparent.
+ var/horn_color //specific horn colors, because why not?
+
var/use_skintones = 0 // does it use skintones or not? (spoiler alert this is only used by humans)
var/exotic_blood = "" // If your race wants to bleed something other than bog standard blood, change this to reagent id.
var/exotic_bloodtype = "" //If your race uses a non standard bloodtype (A+, O-, AB-, etc)
@@ -79,7 +102,7 @@ GLOBAL_LIST_EMPTY(roundstart_races)
var/fixed_mut_color3 = ""
var/whitelisted = 0 //Is this species restricted to certain players?
var/whitelist = list() //List the ckeys that can use this species, if it's whitelisted.: list("John Doe", "poopface666", "SeeALiggerPullTheTrigger") Spaces & capitalization can be included or ignored entirely for each key as it checks for both.
-
+ var/should_draw_citadel = FALSE
///////////
// PROCS //
@@ -98,6 +121,7 @@ GLOBAL_LIST_EMPTY(roundstart_races)
var/datum/species/S = new I
if(S.check_roundstart_eligible())
GLOB.roundstart_races += S.id
+ GLOB.roundstart_race_names["[S.name]"] = S.id
qdel(S)
if(!GLOB.roundstart_races.len)
GLOB.roundstart_races += "human"
@@ -129,10 +153,10 @@ GLOBAL_LIST_EMPTY(roundstart_races)
return
//Please override this locally if you want to define when what species qualifies for what rank if human authority is enforced.
-/datum/species/proc/qualifies_for_rank(rank, list/features)
- if(rank in GLOB.command_positions)
- return 0
- return 1
+/datum/species/proc/qualifies_for_rank(rank, list/features) //SPECIES JOB RESTRICTIONS
+ //if(rank in GLOB.command_positions) Left as an example: The format qualifies for rank takes.
+ // return 0 //It returns false when it runs the proc so they don't get jobs from the global list.
+ return 1 //It returns 1 to say they are a-okay to continue.
//Will regenerate missing organs
/datum/species/proc/regenerate_organs(mob/living/carbon/C,datum/species/old_species,replace_current=TRUE)
@@ -260,7 +284,7 @@ GLOBAL_LIST_EMPTY(roundstart_races)
C.hud_used.update_locked_slots()
// this needs to be FIRST because qdel calls update_body which checks if we have DIGITIGRADE legs or not and if not then removes DIGITIGRADE from species_traits
- if(("legs" in C.dna.species.mutant_bodyparts) && C.dna.features["legs"] == "Digitigrade Legs")
+ if(("legs" in C.dna.species.mutant_bodyparts) && (C.dna.features["legs"] == "Digitigrade" || C.dna.features["legs"] == "Avian"))
species_traits += DIGITIGRADE
if(DIGITIGRADE in species_traits)
C.Digitigrade_Leg_Swap(FALSE)
@@ -279,7 +303,7 @@ GLOBAL_LIST_EMPTY(roundstart_races)
if(mutanthands)
// Drop items in hands
- // If you're lucky enough to have a NODROP_1 item, then it stays.
+ // If you're lucky enough to have a TRAIT_NODROP item, then it stays.
for(var/V in C.held_items)
var/obj/item/I = V
if(istype(I))
@@ -294,8 +318,6 @@ GLOBAL_LIST_EMPTY(roundstart_races)
for(var/datum/disease/A in C.diseases)
A.cure(FALSE)
- SEND_SIGNAL(C, COMSIG_SPECIES_GAIN, src, old_species)
-
//CITADEL EDIT
if(NOAROUSAL in species_traits)
C.canbearoused = FALSE
@@ -306,6 +328,11 @@ GLOBAL_LIST_EMPTY(roundstart_races)
var/mob/living/carbon/human/H = C
if(NOGENITALS in H.dna.species.species_traits)
H.give_genitals(TRUE) //call the clean up proc to delete anything on the mob then return.
+ if("meat_type" in default_features) //I can't believe it's come to the meat
+ H.type_of_meat = GLOB.meat_types[H.dna.features["meat_type"]]
+
+ SEND_SIGNAL(C, COMSIG_SPECIES_GAIN, src, old_species)
+
// EDIT ENDS
@@ -317,6 +344,11 @@ GLOBAL_LIST_EMPTY(roundstart_races)
for(var/X in inherent_traits)
REMOVE_TRAIT(C, X, SPECIES_TRAIT)
+ if("meat_type" in default_features)
+ C.type_of_meat = GLOB.meat_types[C.dna.features["meat_type"]]
+ else
+ C.type_of_meat = initial(meat)
+
SEND_SIGNAL(C, COMSIG_SPECIES_LOSS, src)
/datum/species/proc/handle_hair(mob/living/carbon/human/H, forced_colour)
@@ -389,6 +421,10 @@ GLOBAL_LIST_EMPTY(roundstart_races)
facial_overlay.alpha = hair_alpha
+ if(OFFSET_FHAIR in H.dna.species.offset_features)
+ facial_overlay.pixel_x += H.dna.species.offset_features[OFFSET_FHAIR][1]
+ facial_overlay.pixel_y += H.dna.species.offset_features[OFFSET_FHAIR][2]
+
standing += facial_overlay
if(H.head)
@@ -446,9 +482,11 @@ GLOBAL_LIST_EMPTY(roundstart_races)
else
hair_overlay.color = forced_colour
hair_overlay.alpha = hair_alpha
- if(OFFSET_FACE in H.dna.species.offset_features)
- hair_overlay.pixel_x += H.dna.species.offset_features[OFFSET_FACE][1]
- hair_overlay.pixel_y += H.dna.species.offset_features[OFFSET_FACE][2]
+
+ if(OFFSET_HAIR in H.dna.species.offset_features)
+ hair_overlay.pixel_x += H.dna.species.offset_features[OFFSET_HAIR][1]
+ hair_overlay.pixel_y += H.dna.species.offset_features[OFFSET_HAIR][2]
+
if(hair_overlay.icon)
standing += hair_overlay
@@ -469,9 +507,11 @@ GLOBAL_LIST_EMPTY(roundstart_races)
if(H.lip_style && (LIPS in species_traits))
var/mutable_appearance/lip_overlay = mutable_appearance('icons/mob/human_face.dmi', "lips_[H.lip_style]", -BODY_LAYER)
lip_overlay.color = H.lip_color
- if(OFFSET_FACE in H.dna.species.offset_features)
- lip_overlay.pixel_x += H.dna.species.offset_features[OFFSET_FACE][1]
- lip_overlay.pixel_y += H.dna.species.offset_features[OFFSET_FACE][2]
+
+ if(OFFSET_LIPS in H.dna.species.offset_features)
+ lip_overlay.pixel_x += H.dna.species.offset_features[OFFSET_LIPS][1]
+ lip_overlay.pixel_y += H.dna.species.offset_features[OFFSET_LIPS][2]
+
standing += lip_overlay
// eyes
@@ -484,9 +524,11 @@ GLOBAL_LIST_EMPTY(roundstart_races)
eye_overlay = mutable_appearance('icons/mob/human_face.dmi', "eyes", -BODY_LAYER)
if((EYECOLOR in species_traits) && has_eyes)
eye_overlay.color = "#" + H.eye_color
- if(OFFSET_FACE in H.dna.species.offset_features)
- eye_overlay.pixel_x += H.dna.species.offset_features[OFFSET_FACE][1]
- eye_overlay.pixel_y += H.dna.species.offset_features[OFFSET_FACE][2]
+
+ if(OFFSET_EYES in H.dna.species.offset_features)
+ eye_overlay.pixel_x += H.dna.species.offset_features[OFFSET_EYES][1]
+ eye_overlay.pixel_y += H.dna.species.offset_features[OFFSET_EYES][2]
+
standing += eye_overlay
//Underwear, Undershirts & Socks
@@ -495,34 +537,42 @@ GLOBAL_LIST_EMPTY(roundstart_races)
if(H.hidden_underwear)
H.underwear = "Nude"
else
- H.underwear = H.saved_underwear
- var/datum/sprite_accessory/underwear/underwear = GLOB.underwear_list[H.underwear]
- if(underwear)
- standing += mutable_appearance(underwear.icon, underwear.icon_state, -BODY_LAYER)
+ H.saved_underwear = H.underwear
+ var/datum/sprite_accessory/underwear/bottom/B = GLOB.underwear_list[H.underwear]
+ if(B)
+ var/mutable_appearance/MA = mutable_appearance(B.icon, B.icon_state, -BODY_LAYER)
+ if(UNDIE_COLORABLE(B))
+ MA.color = "#[H.undie_color]"
+ standing += MA
if(H.undershirt)
if(H.hidden_undershirt)
H.undershirt = "Nude"
else
- H.undershirt = H.saved_undershirt
- var/datum/sprite_accessory/undershirt/undershirt = GLOB.undershirt_list[H.undershirt]
- if(undershirt)
- if(H.dna.species.sexes && H.gender == FEMALE)
- standing += wear_female_version(undershirt.icon_state, undershirt.icon, BODY_LAYER)
- else
- standing += mutable_appearance(undershirt.icon, undershirt.icon_state, -BODY_LAYER)
+ H.saved_undershirt = H.undershirt
+ var/datum/sprite_accessory/underwear/top/T = GLOB.undershirt_list[H.undershirt]
+ if(T)
+ var/mutable_appearance/MA
+ if(H.dna.species.sexes && H.gender == FEMALE)
+ MA = wear_female_version(T.icon_state, T.icon, BODY_LAYER)
+ else
+ MA = mutable_appearance(T.icon, T.icon_state, -BODY_LAYER)
+ if(UNDIE_COLORABLE(T))
+ MA.color = "#[H.shirt_color]"
+ standing += MA
if(H.socks && H.get_num_legs(FALSE) >= 2)
if(H.hidden_socks)
H.socks = "Nude"
else
- H.socks = H.saved_socks
- var/datum/sprite_accessory/socks/socks = GLOB.socks_list[H.socks]
- if(socks)
- if(DIGITIGRADE in species_traits)
- standing += mutable_appearance(socks.icon, socks.icon_state + "_d", -BODY_LAYER)
- else
- standing += mutable_appearance(socks.icon, socks.icon_state, -BODY_LAYER)
+ H.saved_socks = H.socks
+ var/datum/sprite_accessory/underwear/socks/S = GLOB.socks_list[H.socks]
+ if(S)
+ var/digilegs = (DIGITIGRADE in species_traits) ? "_d" : ""
+ var/mutable_appearance/MA = mutable_appearance(S.icon, "[S.icon_state][digilegs]", -BODY_LAYER)
+ if(UNDIE_COLORABLE(S))
+ MA.color = "#[H.socks_color]"
+ standing += MA
if(standing.len)
H.overlays_standing[BODY_LAYER] = standing
@@ -604,6 +654,10 @@ GLOBAL_LIST_EMPTY(roundstart_races)
else if ("wings" in mutant_bodyparts)
bodyparts_to_add -= "wings_open"
+ if("insect_fluff" in mutant_bodyparts)
+ if(!H.dna.features["insect_fluff"] || H.dna.features["insect_fluff"] == "None" || H.wear_suit && (H.wear_suit.flags_inv & HIDEJUMPSUIT))
+ bodyparts_to_add -= "insect_fluff"
+
//CITADEL EDIT
//Race specific bodyparts:
//Xenos
@@ -707,10 +761,14 @@ GLOBAL_LIST_EMPTY(roundstart_races)
S = GLOB.wings_list[H.dna.features["wings"]]
if("wingsopen")
S = GLOB.wings_open_list[H.dna.features["wings"]]
+ if("deco_wings")
+ S = GLOB.deco_wings_list[H.dna.features["deco_wings"]]
if("legs")
S = GLOB.legs_list[H.dna.features["legs"]]
- if("moth_wings")
- S = GLOB.moth_wings_list[H.dna.features["moth_wings"]]
+ if("insect_wings")
+ S = GLOB.insect_wings_list[H.dna.features["insect_wings"]]
+ if("insect_fluff")
+ S = GLOB.insect_fluffs_list[H.dna.features["insect_fluff"]]
if("caps")
S = GLOB.caps_list[H.dna.features["caps"]]
if("ipc_screen")
@@ -756,6 +814,8 @@ GLOBAL_LIST_EMPTY(roundstart_races)
bodypart = "snout"
if(bodypart == "xenohead")
bodypart = "xhead"
+ if(bodypart == "insect_wings" || bodypart == "deco_wings")
+ bodypart = "insect_wings"
if(S.gender_specific)
accessory_overlay.icon_state = "[g]_[bodypart]_[S.icon_state]_[layertext]"
@@ -807,6 +867,8 @@ GLOBAL_LIST_EMPTY(roundstart_races)
accessory_overlay.color = "#[H.facial_hair_color]"
if(EYECOLOR)
accessory_overlay.color = "#[H.eye_color]"
+ if(HORNCOLOR)
+ accessory_overlay.color = "#[H.horn_color]"
else
accessory_overlay.color = forced_colour
else
@@ -823,6 +885,11 @@ GLOBAL_LIST_EMPTY(roundstart_races)
for(var/index=1, index<=husklist.len, index++)
husklist[index] = husklist[index]/255
accessory_overlay.color = husklist
+
+ if(OFFSET_MUTPARTS in H.dna.species.offset_features)
+ accessory_overlay.pixel_x += H.dna.species.offset_features[OFFSET_MUTPARTS][1]
+ accessory_overlay.pixel_y += H.dna.species.offset_features[OFFSET_MUTPARTS][2]
+
standing += accessory_overlay
if(S.hasinner)
@@ -835,6 +902,10 @@ GLOBAL_LIST_EMPTY(roundstart_races)
if(S.center)
inner_accessory_overlay = center_image(inner_accessory_overlay, S.dimension_x, S.dimension_y)
+ if(OFFSET_MUTPARTS in H.dna.species.offset_features)
+ inner_accessory_overlay.pixel_x += H.dna.species.offset_features[OFFSET_MUTPARTS][1]
+ inner_accessory_overlay.pixel_y += H.dna.species.offset_features[OFFSET_MUTPARTS][2]
+
standing += inner_accessory_overlay
if(S.extra) //apply the extra overlay, if there is one
@@ -872,6 +943,14 @@ GLOBAL_LIST_EMPTY(roundstart_races)
extra_accessory_overlay.color = "#[H.facial_hair_color]"
if(EYECOLOR)
extra_accessory_overlay.color = "#[H.eye_color]"
+
+ if(HORNCOLOR)
+ extra_accessory_overlay.color = "#[H.horn_color]"
+
+ if(OFFSET_MUTPARTS in H.dna.species.offset_features)
+ extra_accessory_overlay.pixel_x += H.dna.species.offset_features[OFFSET_MUTPARTS][1]
+ extra_accessory_overlay.pixel_y += H.dna.species.offset_features[OFFSET_MUTPARTS][2]
+
standing += extra_accessory_overlay
if(S.extra2) //apply the extra overlay, if there is one
@@ -904,6 +983,13 @@ GLOBAL_LIST_EMPTY(roundstart_races)
extra2_accessory_overlay.color = "#[H.dna.features["mcolor"]]"
else
extra2_accessory_overlay.color = "#[H.hair_color]"
+ if(HORNCOLOR)
+ extra2_accessory_overlay.color = "#[H.horn_color]"
+
+ if(OFFSET_MUTPARTS in H.dna.species.offset_features)
+ extra2_accessory_overlay.pixel_x += H.dna.species.offset_features[OFFSET_MUTPARTS][1]
+ extra2_accessory_overlay.pixel_y += H.dna.species.offset_features[OFFSET_MUTPARTS][2]
+
standing += extra2_accessory_overlay
@@ -1011,13 +1097,12 @@ GLOBAL_LIST_EMPTY(roundstart_races)
if(SLOT_BELT)
if(H.belt)
return FALSE
-
- var/obj/item/bodypart/O = H.get_bodypart(BODY_ZONE_CHEST)
-
- if(!H.w_uniform && !nojumpsuit && (!O || O.status != BODYPART_ROBOTIC))
- if(!disable_warning)
- to_chat(H, "You need a jumpsuit before you can attach this [I.name]!")
- return FALSE
+ if(!CHECK_BITFIELD(I.item_flags, NO_UNIFORM_REQUIRED))
+ var/obj/item/bodypart/O = H.get_bodypart(BODY_ZONE_CHEST)
+ if(!H.w_uniform && !nojumpsuit && (!O || O.status != BODYPART_ROBOTIC))
+ if(!disable_warning)
+ to_chat(H, "You need a jumpsuit before you can attach this [I.name]!")
+ return FALSE
if(!(I.slot_flags & ITEM_SLOT_BELT))
return
return equip_delay_self_check(I, H, bypass_equip_delay_self)
@@ -1054,17 +1139,17 @@ GLOBAL_LIST_EMPTY(roundstart_races)
if(SLOT_WEAR_ID)
if(H.wear_id)
return FALSE
-
- var/obj/item/bodypart/O = H.get_bodypart(BODY_ZONE_CHEST)
- if(!H.w_uniform && !nojumpsuit && (!O || O.status != BODYPART_ROBOTIC))
- if(!disable_warning)
- to_chat(H, "You need a jumpsuit before you can attach this [I.name]!")
- return FALSE
+ if(!CHECK_BITFIELD(I.item_flags, NO_UNIFORM_REQUIRED))
+ var/obj/item/bodypart/O = H.get_bodypart(BODY_ZONE_CHEST)
+ if(!H.w_uniform && !nojumpsuit && (!O || O.status != BODYPART_ROBOTIC))
+ if(!disable_warning)
+ to_chat(H, "You need a jumpsuit before you can attach this [I.name]!")
+ return FALSE
if( !(I.slot_flags & ITEM_SLOT_ID) )
return FALSE
return equip_delay_self_check(I, H, bypass_equip_delay_self)
if(SLOT_L_STORE)
- if(I.item_flags & NODROP) //Pockets aren't visible, so you can't move NODROP_1 items into them.
+ if(HAS_TRAIT(I, TRAIT_NODROP)) //Pockets aren't visible, so you can't move TRAIT_NODROP items into them.
return FALSE
if(H.l_store)
return FALSE
@@ -1080,7 +1165,7 @@ GLOBAL_LIST_EMPTY(roundstart_races)
if( I.w_class <= WEIGHT_CLASS_SMALL || (I.slot_flags & ITEM_SLOT_POCKET) )
return TRUE
if(SLOT_R_STORE)
- if(I.item_flags & NODROP)
+ if(HAS_TRAIT(I, TRAIT_NODROP))
return FALSE
if(H.r_store)
return FALSE
@@ -1097,7 +1182,7 @@ GLOBAL_LIST_EMPTY(roundstart_races)
return TRUE
return FALSE
if(SLOT_S_STORE)
- if(I.item_flags & NODROP)
+ if(HAS_TRAIT(I, TRAIT_NODROP))
return FALSE
if(H.s_store)
return FALSE
@@ -1158,13 +1243,6 @@ GLOBAL_LIST_EMPTY(roundstart_races)
return 1
return FALSE
-/datum/species/proc/handle_speech(message, mob/living/carbon/human/H)
- return message
-
-//return a list of spans or an empty list
-/datum/species/proc/get_spans()
- return list()
-
/datum/species/proc/check_weakness(obj/item, mob/living/attacker)
return FALSE
@@ -1196,9 +1274,14 @@ GLOBAL_LIST_EMPTY(roundstart_races)
if(mood && mood.sanity > SANITY_DISTURBED)
hunger_rate *= max(0.5, 1 - 0.002 * mood.sanity) //0.85 to 0.75
- if(H.satiety > 0)
+ // Whether we cap off our satiety or move it towards 0
+ if(H.satiety > MAX_SATIETY)
+ H.satiety = MAX_SATIETY
+ else if(H.satiety > 0)
H.satiety--
- if(H.satiety < 0)
+ else if(H.satiety < -MAX_SATIETY)
+ H.satiety = -MAX_SATIETY
+ else if(H.satiety < 0)
H.satiety++
if(prob(round(-H.satiety/40)))
H.Jitter(5)
@@ -1314,9 +1397,9 @@ GLOBAL_LIST_EMPTY(roundstart_races)
if(!istype(J) && istype(C))
J = C.jetpack
if(istype(J) && J.full_speed && J.allow_thrust(0.01, H)) //Prevents stacking
- . -= 2
+ . -= 0.4
else if(istype(T) && T.allow_thrust(0.01, H))
- . -= 2
+ . -= 0.4
if(!ignoreslow && gravity)
if(H.wear_suit)
@@ -1683,7 +1766,7 @@ GLOBAL_LIST_EMPTY(roundstart_races)
if(BODY_ZONE_HEAD)
if(!I.is_sharp() && armor_block < 50)
if(prob(I.force))
- H.adjustBrainLoss(20)
+ H.adjustOrganLoss(ORGAN_SLOT_BRAIN, 20)
if(H.stat == CONSCIOUS)
H.visible_message("[H] has been knocked senseless!", \
"[H] has been knocked senseless!")
@@ -1692,11 +1775,11 @@ GLOBAL_LIST_EMPTY(roundstart_races)
if(prob(10))
H.gain_trauma(/datum/brain_trauma/mild/concussion)
else
- H.adjustBrainLoss(I.force * 0.2)
+ H.adjustOrganLoss(ORGAN_SLOT_BRAIN, I.force * 0.2)
if(H.stat == CONSCIOUS && H != user && prob(I.force + ((100 - H.health) * 0.5))) // rev deconversion through blunt trauma.
var/datum/antagonist/rev/rev = H.mind.has_antag_datum(/datum/antagonist/rev)
- var/datum/antagonist/gang/gang = H.mind.has_antag_datum(/datum/antagonist/gang/)
+ var/datum/antagonist/gang/gang = H.mind.has_antag_datum(/datum/antagonist/gang && !/datum/antagonist/gang/boss)
if(rev)
rev.remove_revolutionary(FALSE, user)
if(gang)
@@ -1732,6 +1815,130 @@ GLOBAL_LIST_EMPTY(roundstart_races)
H.forcesay(GLOB.hit_appends) //forcesay checks stat already.
return TRUE
+/datum/species/proc/alt_spec_attack_hand(mob/living/carbon/human/M, mob/living/carbon/human/H, datum/martial_art/attacker_style)
+ if(!istype(M))
+ return TRUE
+ CHECK_DNA_AND_SPECIES(M)
+ CHECK_DNA_AND_SPECIES(H)
+
+ if(!istype(M)) //sanity check for drones.
+ return TRUE
+ if(M.mind)
+ attacker_style = M.mind.martial_art
+ if((M != H) && M.a_intent != INTENT_HELP && H.check_shields(M, 0, M.name, attack_type = UNARMED_ATTACK))
+ log_combat(M, H, "attempted to touch")
+ H.visible_message("[M] attempted to touch [H]!")
+ return TRUE
+ switch(M.a_intent)
+ if(INTENT_HELP)
+ if(M == H)
+ althelp(M, H, attacker_style)
+ return TRUE
+ return FALSE
+ if(INTENT_DISARM)
+ altdisarm(M, H, attacker_style)
+ return TRUE
+ return FALSE
+
+/datum/species/proc/althelp(mob/living/carbon/human/user, mob/living/carbon/human/target, datum/martial_art/attacker_style)
+ if(user == target && istype(user))
+ if(user.getStaminaLoss() >= STAMINA_SOFTCRIT)
+ to_chat(user, "You're too exhausted for that.")
+ return
+ if(!user.resting)
+ to_chat(user, "You can only force yourself up if you're on the ground.")
+ return
+ user.visible_message("[user] forces [p_them()]self up to [p_their()] feet!", "You force yourself up to your feet!")
+ user.resting = 0
+ user.update_canmove()
+ user.adjustStaminaLossBuffered(user.stambuffer) //Rewards good stamina management by making it easier to instantly get up from resting
+ playsound(user, 'sound/weapons/thudswoosh.ogg', 50, 1, -1)
+
+/datum/species/proc/altdisarm(mob/living/carbon/human/user, mob/living/carbon/human/target, datum/martial_art/attacker_style)
+ if(user.getStaminaLoss() >= STAMINA_SOFTCRIT)
+ to_chat(user, "You're too exhausted.")
+ return FALSE
+ if(target.check_block())
+ target.visible_message("[target] blocks [user]'s shoving attempt!")
+ return FALSE
+ if(attacker_style && attacker_style.disarm_act(user,target))
+ return TRUE
+ if(user.resting)
+ return FALSE
+ else
+ if(user == target)
+ return
+ user.do_attack_animation(target, ATTACK_EFFECT_DISARM)
+ user.adjustStaminaLossBuffered(4)
+ playsound(target, 'sound/weapons/thudswoosh.ogg', 50, TRUE, -1)
+
+ if(target.w_uniform)
+ target.w_uniform.add_fingerprint(user)
+ SEND_SIGNAL(target, COMSIG_HUMAN_DISARM_HIT, user, user.zone_selected)
+
+ if(!target.resting)
+ target.adjustStaminaLoss(5)
+
+ if(target.is_shove_knockdown_blocked())
+ return
+
+ var/turf/target_oldturf = target.loc
+ var/shove_dir = get_dir(user.loc, target_oldturf)
+ var/turf/target_shove_turf = get_step(target.loc, shove_dir)
+ var/mob/living/carbon/human/target_collateral_human
+ var/shove_blocked = FALSE //Used to check if a shove is blocked so that if it is knockdown logic can be applied
+
+ //Thank you based whoneedsspace
+ target_collateral_human = locate(/mob/living/carbon/human) in target_shove_turf.contents
+ if(target_collateral_human && !target_collateral_human.resting)
+ shove_blocked = TRUE
+ else
+ target_collateral_human = null
+ target.Move(target_shove_turf, shove_dir)
+ if(get_turf(target) == target_oldturf)
+ shove_blocked = TRUE
+
+ if(shove_blocked && !target.buckled)
+ var/directional_blocked = !target.Adjacent(target_shove_turf)
+ var/targetatrest = target.resting
+ if((directional_blocked || !(target_collateral_human || target_shove_turf.shove_act(target, user))) && !targetatrest)
+ target.Knockdown(SHOVE_KNOCKDOWN_SOLID)
+ user.visible_message("[user.name] shoves [target.name], knocking them down!",
+ "You shove [target.name], knocking them down!", null, COMBAT_MESSAGE_RANGE)
+ log_combat(user, target, "shoved", "knocking them down")
+ else if(target_collateral_human && !targetatrest)
+ target.Knockdown(SHOVE_KNOCKDOWN_HUMAN)
+ target_collateral_human.Knockdown(SHOVE_KNOCKDOWN_COLLATERAL)
+ user.visible_message("[user.name] shoves [target.name] into [target_collateral_human.name]!",
+ "You shove [target.name] into [target_collateral_human.name]!", null, COMBAT_MESSAGE_RANGE)
+ log_combat(user, target, "shoved", "into [target_collateral_human.name]")
+
+ else
+ user.visible_message("[user.name] shoves [target.name]!",
+ "You shove [target.name]!", null, COMBAT_MESSAGE_RANGE)
+ var/target_held_item = target.get_active_held_item()
+ var/knocked_item = FALSE
+ if(!is_type_in_typecache(target_held_item, GLOB.shove_disarming_types))
+ target_held_item = null
+ if(!target.has_movespeed_modifier(SHOVE_SLOWDOWN_ID))
+ target.add_movespeed_modifier(SHOVE_SLOWDOWN_ID, multiplicative_slowdown = SHOVE_SLOWDOWN_STRENGTH)
+ if(target_held_item)
+ target.visible_message("[target.name]'s grip on \the [target_held_item] loosens!",
+ "Your grip on \the [target_held_item] loosens!", null, COMBAT_MESSAGE_RANGE)
+ addtimer(CALLBACK(target, /mob/living/carbon/human/proc/clear_shove_slowdown), SHOVE_SLOWDOWN_LENGTH)
+ else if(target_held_item)
+ target.dropItemToGround(target_held_item)
+ knocked_item = TRUE
+ target.visible_message("[target.name] drops \the [target_held_item]!!",
+ "You drop \the [target_held_item]!!", null, COMBAT_MESSAGE_RANGE)
+ var/append_message = ""
+ if(target_held_item)
+ if(knocked_item)
+ append_message = "causing them to drop [target_held_item]"
+ else
+ append_message = "loosening their grip on [target_held_item]"
+ log_combat(user, target, "shoved", append_message)
+
/datum/species/proc/apply_damage(damage, damagetype = BRUTE, def_zone = null, blocked, mob/living/carbon/human/H)
var/hit_percent = (100-(blocked+armor))/100
hit_percent = (hit_percent * (100-H.physiology.damage_resistance))/100
@@ -1787,7 +1994,7 @@ GLOBAL_LIST_EMPTY(roundstart_races)
else
H.adjustStaminaLoss(damage * hit_percent * H.physiology.stamina_mod)
if(BRAIN)
- H.adjustBrainLoss(damage * hit_percent * H.physiology.brain_mod)
+ H.adjustOrganLoss(ORGAN_SLOT_BRAIN, damage * hit_percent * H.physiology.brain_mod)
if(AROUSAL) //Citadel edit - arousal
H.adjustArousalLoss(damage * hit_percent)
return 1
diff --git a/code/modules/mob/living/carbon/human/species_types/abductors.dm b/code/modules/mob/living/carbon/human/species_types/abductors.dm
index ad1f5c9190..ffd129ebf7 100644
--- a/code/modules/mob/living/carbon/human/species_types/abductors.dm
+++ b/code/modules/mob/living/carbon/human/species_types/abductors.dm
@@ -6,10 +6,6 @@
species_traits = list(NOBLOOD,NOEYES,NOGENITALS,NOAROUSAL)
inherent_traits = list(TRAIT_VIRUSIMMUNE,TRAIT_NOGUNS,TRAIT_NOHUNGER,TRAIT_NOBREATH)
mutanttongue = /obj/item/organ/tongue/abductor
- var/scientist = FALSE // vars to not pollute spieces list with castes
-
-/datum/species/abductor/copy_properties_from(datum/species/abductor/old_species)
- scientist = old_species.scientist
/datum/species/abductor/on_species_gain(mob/living/carbon/C, datum/species/old_species)
. = ..()
diff --git a/code/modules/mob/living/carbon/human/species_types/bugmen.dm b/code/modules/mob/living/carbon/human/species_types/bugmen.dm
new file mode 100644
index 0000000000..94dba550b6
--- /dev/null
+++ b/code/modules/mob/living/carbon/human/species_types/bugmen.dm
@@ -0,0 +1,64 @@
+/datum/species/insect
+ name = "Anthromorphic Insect"
+ id = "insect"
+ say_mod = "flutters"
+ default_color = "00FF00"
+ species_traits = list(LIPS,NOEYES,HAIR,FACEHAIR,MUTCOLORS,HORNCOLOR)
+ inherent_biotypes = list(MOB_ORGANIC, MOB_HUMANOID, MOB_BUG)
+ mutant_bodyparts = list("mam_ears", "mam_snout", "mam_tail", "taur", "insect_wings", "mam_snouts", "insect_fluff","horns")
+ default_features = list("mcolor" = "FFF","mcolor2" = "FFF","mcolor3" = "FFF", "mam_tail" = "None", "mam_ears" = "None",
+ "insect_wings" = "None", "insect_fluff" = "None", "mam_snouts" = "None", "taur" = "None","horns" = "None")
+ attack_verb = "slash"
+ attack_sound = 'sound/weapons/slash.ogg'
+ miss_sound = 'sound/weapons/slashmiss.ogg'
+ meat = /obj/item/reagent_containers/food/snacks/meat/slab/human/mutant/insect
+ liked_food = VEGETABLES | DAIRY
+ disliked_food = FRUIT | GROSS
+ toxic_food = MEAT | RAW
+ mutanteyes = /obj/item/organ/eyes/insect
+ should_draw_citadel = TRUE
+
+/datum/species/insect/on_species_gain(mob/living/carbon/C)
+ . = ..()
+ if(ishuman(C))
+ var/mob/living/carbon/human/H = C
+ if(!H.dna.features["insect_wings"])
+ H.dna.features["insect_wings"] = "[(H.client && H.client.prefs && LAZYLEN(H.client.prefs.features) && H.client.prefs.features["insect_wings"]) ? H.client.prefs.features["insect_wings"] : "None"]"
+ handle_mutant_bodyparts(H)
+
+/datum/species/insect/random_name(gender,unique,lastname)
+ if(unique)
+ return random_unique_moth_name()
+
+ var/randname = moth_name()
+
+ if(lastname)
+ randname += " [lastname]"
+
+ return randname
+
+/datum/species/insect/handle_fire(mob/living/carbon/human/H, no_protection = FALSE)
+ ..()
+ if(H.dna.features["insect_wings"] != "Burnt Off" && H.dna.features["insect_wings"] != "None" && H.bodytemperature >= 800 && H.fire_stacks > 0) //do not go into the extremely hot light. you will not survive
+ to_chat(H, "Your precious wings burn to a crisp!")
+ if(H.dna.features["insect_wings"] != "None")
+ H.dna.features["insect_wings"] = "Burnt Off"
+ handle_mutant_bodyparts(H)
+
+/datum/species/insect/handle_chemicals(datum/reagent/chem, mob/living/carbon/human/H)
+ . = ..()
+ if(chem.id == "pestkiller")
+ H.adjustToxLoss(3)
+ H.reagents.remove_reagent(chem.id, REAGENTS_METABOLISM)
+
+/datum/species/insect/check_weakness(obj/item/weapon, mob/living/attacker)
+ if(istype(weapon, /obj/item/melee/flyswatter))
+ return 9 //flyswatters deal 10x damage to insects
+ return 0
+
+/datum/species/insect/space_move(mob/living/carbon/human/H)
+ . = ..()
+ if(H.loc && !isspaceturf(H.loc) && (H.dna.features["insect_wings"] != "Burnt Off" && H.dna.features["insect_wings"] != "None"))
+ var/datum/gas_mixture/current = H.loc.return_air()
+ if(current && (current.return_pressure() >= ONE_ATMOSPHERE*0.85)) //as long as there's reasonable pressure and no gravity, flight is possible
+ return TRUE
diff --git a/code/modules/mob/living/carbon/human/species_types/corporate.dm b/code/modules/mob/living/carbon/human/species_types/corporate.dm
index 620f0b2543..146090b366 100644
--- a/code/modules/mob/living/carbon/human/species_types/corporate.dm
+++ b/code/modules/mob/living/carbon/human/species_types/corporate.dm
@@ -16,5 +16,5 @@
blacklisted = 1
use_skintones = 0
species_traits = list(NOBLOOD,EYECOLOR,NOGENITALS)
- inherent_traits = list(TRAIT_RADIMMUNE,TRAIT_VIRUSIMMUNE,TRAIT_PIERCEIMMUNE,TRAIT_NODISMEMBER,TRAIT_NOHUNGER)
+ inherent_traits = list(TRAIT_RADIMMUNE,TRAIT_VIRUSIMMUNE,TRAIT_PIERCEIMMUNE,TRAIT_NODISMEMBER,TRAIT_NOLIMBDISABLE,TRAIT_NOHUNGER)
sexes = 0
\ No newline at end of file
diff --git a/code/modules/mob/living/carbon/human/species_types/dullahan.dm b/code/modules/mob/living/carbon/human/species_types/dullahan.dm
index da7de39a03..eba4ff6d2f 100644
--- a/code/modules/mob/living/carbon/human/species_types/dullahan.dm
+++ b/code/modules/mob/living/carbon/human/species_types/dullahan.dm
@@ -13,9 +13,12 @@
blacklisted = TRUE
limbs_id = "human"
skinned_type = /obj/item/stack/sheet/animalhide/human
+ var/pumpkin = FALSE
var/obj/item/dullahan_relay/myhead
+/datum/species/dullahan/pumpkin
+ pumpkin = TRUE
/datum/species/dullahan/check_roundstart_eligible()
if(SSevents.holidays && SSevents.holidays[HALLOWEEN])
@@ -27,11 +30,19 @@
H.flags_1 &= ~HEAR_1
var/obj/item/bodypart/head/head = H.get_bodypart(BODY_ZONE_HEAD)
if(head)
+ if(pumpkin)//Pumpkinhead!
+ head.animal_origin = 100
+ head.icon = 'icons/obj/clothing/hats.dmi'
+ head.icon_state = "hardhat1_pumpkin_j"
+ head.custom_head = TRUE
head.drop_limb()
head.flags_1 = HEAR_1
head.throwforce = 25
myhead = new /obj/item/dullahan_relay (head, H)
H.put_in_hands(head)
+ var/obj/item/organ/eyes/E = H.getorganslot(ORGAN_SLOT_EYES)
+ for(var/datum/action/item_action/organ_action/OA in E.actions)
+ OA.Trigger()
/datum/species/dullahan/on_species_loss(mob/living/carbon/human/H)
H.flags_1 |= ~HEAR_1
@@ -64,21 +75,21 @@
/obj/item/organ/brain/dullahan
decoy_override = TRUE
- vital = FALSE
+ organ_flags = ORGAN_NO_SPOIL//Do not decay
/obj/item/organ/tongue/dullahan
zone = "abstract"
+ modifies_speech = TRUE
-/obj/item/organ/tongue/dullahan/TongueSpeech(var/message)
+/obj/item/organ/tongue/dullahan/handle_speech(datum/source, list/speech_args)
if(ishuman(owner))
var/mob/living/carbon/human/H = owner
if(H.dna.species.id == "dullahan")
var/datum/species/dullahan/D = H.dna.species
if(isobj(D.myhead.loc))
var/obj/O = D.myhead.loc
- O.say(message)
- message = ""
- return message
+ O.say(speech_args[SPEECH_MESSAGE])
+ speech_args[SPEECH_MESSAGE] = ""
/obj/item/organ/ears/dullahan
zone = "abstract"
@@ -138,4 +149,4 @@
D.myhead = null
owner.gib()
owner = null
- ..()
\ No newline at end of file
+ ..()
diff --git a/code/modules/mob/living/carbon/human/species_types/dwarves.dm b/code/modules/mob/living/carbon/human/species_types/dwarves.dm
new file mode 100644
index 0000000000..946bffbdbc
--- /dev/null
+++ b/code/modules/mob/living/carbon/human/species_types/dwarves.dm
@@ -0,0 +1,192 @@
+
+GLOBAL_LIST_INIT(dwarf_first, world.file2list("strings/names/dwarf_first.txt")) //Textfiles with first
+GLOBAL_LIST_INIT(dwarf_last, world.file2list("strings/names/dwarf_last.txt")) //textfiles with last
+
+/datum/species/dwarf //not to be confused with the genetic manlets
+ name = "Dwarf"
+ id = "dwarf" //Also called Homo sapiens pumilionis
+ default_color = "FFFFFF"
+ species_traits = list(EYECOLOR,HAIR,FACEHAIR,LIPS,NO_UNDERWEAR)
+ inherent_traits = list()
+ default_features = list("mcolor" = "FFF", "wings" = "None")
+ limbs_id = "human"
+ use_skintones = 1
+ say_mod = "bellows" //high energy, EXTRA BIOLOGICAL FUEL
+ damage_overlay_type = "human"
+ skinned_type = /obj/item/stack/sheet/animalhide/human
+ liked_food = ALCOHOL | MEAT | DAIRY //Dwarves like alcohol, meat, and dairy products.
+ disliked_food = JUNKFOOD | FRIED //Dwarves hate foods that have no nutrition other than alcohol.
+ mutant_organs = list(/obj/item/organ/dwarfgland) //Dwarven alcohol gland, literal gland warrior
+ mutantliver = /obj/item/organ/liver/dwarf //Dwarven super liver (Otherwise they r doomed)
+
+/mob/living/carbon/human/species/dwarf //species admin spawn path
+ race = /datum/species/dwarf //and the race the path is set to.
+
+/datum/species/dwarf/check_roundstart_eligible()
+ if(SSevents.holidays && SSevents.holidays[HALLOWEEN])
+ return TRUE
+ return ..()
+
+/datum/species/dwarf/on_species_gain(mob/living/carbon/C, datum/species/old_species)
+ . = ..()
+ var/dwarf_hair = pick("Beard (Dwarf)", "Beard (Very Long)", "Beard (Long)") //beard roullette
+ var/mob/living/carbon/human/H = C
+ H.facial_hair_style = dwarf_hair
+ H.update_hair()
+ H.transform = H.transform.Scale(1, 0.8) //We use scale, and yeah. Dwarves can become gnomes with DWARFISM.
+ RegisterSignal(C, COMSIG_MOB_SAY, .proc/handle_speech) //We register handle_speech is being used.
+
+
+/datum/species/dwarf/on_species_loss(mob/living/carbon/H, datum/species/new_species)
+ . = ..()
+ H.transform = H.transform.Scale(1, 1.25) //And we undo it.
+ UnregisterSignal(H, COMSIG_MOB_SAY) //We register handle_speech is not being used.
+
+//Dwarf Name stuff
+/proc/dwarf_name() //hello caller: my name is urist mcuristurister
+ return "[pick(GLOB.dwarf_first)] [pick(GLOB.dwarf_last)]"
+
+/datum/species/dwarf/random_name(gender,unique,lastname)
+ return dwarf_name() //hello, ill return the value from dwarf_name proc to you when called.
+
+//Dwarf Speech handling - Basically a filter/forces them to say things. The IC helper
+/datum/species/dwarf/proc/handle_speech(datum/source, list/speech_args)
+ var/message = speech_args[SPEECH_MESSAGE]
+ if(message[1] != "*")
+ message = " [message]" //Credits to goonstation for the strings list.
+ var/list/dwarf_words = strings("dwarf_replacement.json", "dwarf") //thanks to regex too.
+
+ for(var/key in dwarf_words) //Theres like 1459 words or something man.
+ var/value = dwarf_words[key] //Thus they will always be in character.
+ if(islist(value)) //Whether they like it or not.
+ value = pick(value) //This could be drastically reduced if needed though.
+
+ message = replacetextEx(message, " [uppertext(key)]", " [uppertext(value)]")
+ message = replacetextEx(message, " [capitalize(key)]", " [capitalize(value)]")
+ message = replacetextEx(message, " [key]", " [value]") //Also its scottish.
+
+ if(prob(3))
+ message += pick(" By Armok!")
+ speech_args[SPEECH_MESSAGE] = trim(message)
+
+//This mostly exists because my testdwarf's liver died while trying to also not die due to no alcohol.
+/obj/item/organ/liver/dwarf
+ name = "dwarf liver"
+ icon_state = "liver"
+ desc = "A dwarven liver, theres something magical about seeing one of these up close."
+ alcohol_tolerance = 0 //dwarves really shouldn't be dying to alcohol.
+ toxTolerance = 5 //Shrugs off 5 units of toxins damage.
+ maxHealth = 150 //More health than the average liver, as you aren't going to be replacing this.
+ //If it does need replaced with a standard human liver, prepare for hell.
+
+//alcohol gland
+/obj/item/organ/dwarfgland
+ name = "dwarf alcohol gland"
+ icon_state = "plasma" //Yes this is a actual icon in icons/obj/surgery.dmi
+ desc = "A genetically engineered gland which is hopefully a step forward for humanity."
+ w_class = WEIGHT_CLASS_NORMAL
+ var/stored_alcohol = 250 //They start with 250 units, that ticks down and eventaully bad effects occur
+ var/max_alcohol = 500 //Max they can attain, easier than you think to OD on alcohol.
+ var/heal_rate = 0.5 //The rate they heal damages over 400 alcohol stored. Default is 0.5 so we times 3 since 3 seconds.
+ var/alcohol_rate = 0.25 //The rate the alcohol ticks down per each iteration of dwarf_eth_ticker completing.
+ //These count in on_life ticks which should be 2 seconds per every increment of 1 in a perfect world.
+ var/dwarf_filth_ticker = 0 //Currently set =< 4, that means this will fire the proc around every 4-8 seconds.
+ var/dwarf_eth_ticker = 0 //Currently set =< 1, that means this will fire the proc around every 2 seconds
+
+/obj/item/organ/dwarfgland/prepare_eat()
+ var/obj/S = ..()
+ S.reagents.add_reagent("ethanol", stored_alcohol/10)
+ return S
+
+/obj/item/organ/dwarfgland/on_life() //Primary loop to hook into to start delayed loops for other loops..
+ . = ..()
+ dwarf_cycle_ticker()
+
+//Handles the delayed tick cycle by just adding on increments per each on_life() tick
+/obj/item/organ/dwarfgland/proc/dwarf_cycle_ticker()
+ if(owner.stat == DEAD)
+ return //We make sure they are not dead, so they don't increment any tickers.
+ dwarf_eth_ticker++
+ dwarf_filth_ticker++
+
+ if(dwarf_filth_ticker >= 4) //Should be around 4-8 seconds since a tick is around 2 seconds.
+ dwarf_filth_cycle() //On_life will adjust regarding other factors, so we are along for the ride.
+ dwarf_filth_ticker = 0 //We set the ticker back to 0 to go again.
+ if(dwarf_eth_ticker >= 1) //Alcohol reagent check should be around 2 seconds, since a tick is around 2 seconds.
+ dwarf_eth_cycle()
+ dwarf_eth_ticker = 0
+
+//If this still friggin uses too much CPU, I'll make a for view subsystem If I have to.
+/obj/item/organ/dwarfgland/proc/dwarf_filth_cycle()
+ if(!owner?.client || !owner?.mind)
+ return
+ //Filth Reactions - Since miasma now exists
+ var/filth_counter = 0 //Holder for the filth check cycle, basically contains how much filth dwarf sees numerically.
+ for(var/fuck in view(owner,7)) //hello byond for view loop.
+ if(istype(fuck, /mob/living/carbon/human))
+ var/mob/living/carbon/human/H = fuck
+ if(H.stat == DEAD || (HAS_TRAIT(H, TRAIT_FAKEDEATH)))
+ filth_counter += 10
+ if(istype(fuck, /obj/effect/decal/cleanable/blood))
+ if(istype(fuck, /obj/effect/decal/cleanable/blood/gibs))
+ filth_counter += 1
+ else
+ filth_counter += 0.1
+ if(istype(fuck,/obj/effect/decal/cleanable/vomit)) //They are disgusted by their own vomit too.
+ filth_counter += 10 //Dwarves could technically chainstun each other in a vomit tantrum spiral.
+ switch(filth_counter)
+ if(11 to 25)
+ if(prob(25))
+ to_chat(owner, "Someone should really clean up in here!")
+ if(26 to 50)
+ if(prob(30)) //Probability the message appears
+ to_chat(owner, "The stench makes you queasy.")
+ if(prob(20)) //And then the probability they vomit along with it.
+ owner.vomit(20) //I think vomit should stay over a disgust adjustment.
+ if(51 to 75)
+ if(prob(35))
+ to_chat(owner, "By Armok! You won't be able to keep alcohol down at all!")
+ if(prob(25))
+ owner.vomit(20) //Its more funny
+ if(76 to 100)
+ if(prob(40))
+ to_chat(owner, "You can't live in such FILTH!")
+ if(prob(25))
+ owner.adjustToxLoss(10) //Now they start dying.
+ owner.vomit(20)
+ if(101 to INFINITY) //Now they will really start dying
+ if(prob(40))
+ to_chat(owner, " THERES TOO MUCH FILTH, OH GODS THE FILTH!")
+ owner.adjustToxLoss(15)
+ owner.vomit(40)
+ CHECK_TICK //Check_tick right here, its motherfuckin magic. (To me at least)
+
+//Handles the dwarf alcohol cycle tied to on_life, it ticks in dwarf_cycle_ticker.
+/obj/item/organ/dwarfgland/proc/dwarf_eth_cycle()
+ //BOOZE POWER
+ for(var/datum/reagent/R in owner.reagents.reagent_list)
+ if(istype(R, /datum/reagent/consumable/ethanol))
+ var/datum/reagent/consumable/ethanol/E = R
+ stored_alcohol += (E.boozepwr / 50)
+ if(stored_alcohol > max_alcohol) //Dwarves technically start at 250 alcohol stored.
+ stored_alcohol = max_alcohol
+ var/heal_amt = heal_rate
+ stored_alcohol -= alcohol_rate //Subtracts alcohol_Rate from stored alcohol so EX: 250 - 0.25 per each loop that occurs.
+ if(stored_alcohol > 400) //If they are over 400 they start regenerating
+ owner.adjustBruteLoss(-heal_amt) //But its alcohol, there will be other issues here.
+ owner.adjustFireLoss(-heal_amt) //Unless they drink casually all the time.
+ owner.adjustOxyLoss(-heal_amt)
+ owner.adjustCloneLoss(-heal_amt) //Also they will probably get brain damage if thats a thing here.
+ if(prob(25))
+ switch(stored_alcohol)
+ if(0 to 24)
+ to_chat(owner, "DAMNATION INCARNATE, WHY AM I CURSED WITH THIS DRY-SPELL? I MUST DRINK.")
+ owner.adjustToxLoss(35)
+ if(25 to 50)
+ to_chat(owner, "Oh DAMN, I need some brew!")
+ if(51 to 75)
+ to_chat(owner, "Your body aches, you need to get ahold of some booze...")
+ if(76 to 100)
+ to_chat(owner, "A pint of anything would really hit the spot right now.")
+ if(101 to 150)
+ to_chat(owner, "You feel like you could use a good brew.")
diff --git a/code/modules/mob/living/carbon/human/species_types/flypeople.dm b/code/modules/mob/living/carbon/human/species_types/flypeople.dm
index 6f05eb393d..043ee4fde1 100644
--- a/code/modules/mob/living/carbon/human/species_types/flypeople.dm
+++ b/code/modules/mob/living/carbon/human/species_types/flypeople.dm
@@ -1,5 +1,5 @@
/datum/species/fly
- name = "Flyperson"
+ name = "Anthromorphic Fly"
id = "fly"
say_mod = "buzzes"
species_traits = list(NOEYES)
diff --git a/code/modules/mob/living/carbon/human/species_types/furrypeople.dm b/code/modules/mob/living/carbon/human/species_types/furrypeople.dm
new file mode 100644
index 0000000000..07e594d20b
--- /dev/null
+++ b/code/modules/mob/living/carbon/human/species_types/furrypeople.dm
@@ -0,0 +1,98 @@
+/datum/species/mammal
+ name = "Anthromorph"
+ id = "mammal"
+ default_color = "4B4B4B"
+ should_draw_citadel = TRUE
+ species_traits = list(MUTCOLORS,EYECOLOR,LIPS,HAIR,HORNCOLOR)
+ inherent_biotypes = list(MOB_ORGANIC, MOB_HUMANOID)
+ mutant_bodyparts = list("mam_tail", "mam_ears", "mam_body_markings", "mam_snouts", "deco_wings", "taur", "horns", "legs")
+ default_features = list("mcolor" = "FFF","mcolor2" = "FFF","mcolor3" = "FFF", "mam_snouts" = "Husky", "mam_tail" = "Husky", "mam_ears" = "Husky", "deco_wings" = "None",
+ "mam_body_markings" = "Husky", "taur" = "None", "horns" = "None", "legs" = "Plantigrade", "meat_type" = "Mammalian")
+ attack_verb = "claw"
+ attack_sound = 'sound/weapons/slash.ogg'
+ miss_sound = 'sound/weapons/slashmiss.ogg'
+ meat = /obj/item/reagent_containers/food/snacks/meat/slab/human/mutant/mammal
+ liked_food = MEAT | FRIED
+ disliked_food = TOXIC
+
+//Curiosity killed the cat's wagging tail.
+/datum/species/mammal/spec_death(gibbed, mob/living/carbon/human/H)
+ if(H)
+ stop_wagging_tail(H)
+
+/datum/species/mammal/spec_stun(mob/living/carbon/human/H,amount)
+ if(H)
+ stop_wagging_tail(H)
+ . = ..()
+
+/datum/species/mammal/can_wag_tail(mob/living/carbon/human/H)
+ return ("mam_tail" in mutant_bodyparts) || ("mam_waggingtail" in mutant_bodyparts)
+
+/datum/species/mammal/is_wagging_tail(mob/living/carbon/human/H)
+ return ("mam_waggingtail" in mutant_bodyparts)
+
+/datum/species/mammal/start_wagging_tail(mob/living/carbon/human/H)
+ if("mam_tail" in mutant_bodyparts)
+ mutant_bodyparts -= "mam_tail"
+ mutant_bodyparts |= "mam_waggingtail"
+ H.update_body()
+
+/datum/species/mammal/stop_wagging_tail(mob/living/carbon/human/H)
+ if("mam_waggingtail" in mutant_bodyparts)
+ mutant_bodyparts -= "mam_waggingtail"
+ mutant_bodyparts |= "mam_tail"
+ H.update_body()
+
+
+/datum/species/mammal/qualifies_for_rank(rank, list/features)
+ return TRUE
+
+
+//Alien//
+/datum/species/xeno
+ // A cloning mistake, crossing human and xenomorph DNA
+ name = "Xenomorph Hybrid"
+ id = "xeno"
+ say_mod = "hisses"
+ default_color = "00FF00"
+ should_draw_citadel = TRUE
+ species_traits = list(MUTCOLORS,EYECOLOR,LIPS)
+ inherent_biotypes = list(MOB_ORGANIC, MOB_HUMANOID)
+ mutant_bodyparts = list("xenotail", "xenohead", "xenodorsal", "mam_body_markings", "taur", "legs")
+ default_features = list("xenotail"="Xenomorph Tail","xenohead"="Standard","xenodorsal"="Standard", "mam_body_markings" = "Xeno","mcolor" = "0F0","mcolor2" = "0F0","mcolor3" = "0F0","taur" = "None", "legs" = "Digitigrade")
+ attack_verb = "slash"
+ attack_sound = 'sound/weapons/slash.ogg'
+ miss_sound = 'sound/weapons/slashmiss.ogg'
+ meat = /obj/item/reagent_containers/food/snacks/meat/slab/xeno
+ skinned_type = /obj/item/stack/sheet/animalhide/xeno
+ exotic_bloodtype = "L"
+ damage_overlay_type = "xeno"
+ liked_food = MEAT
+
+/datum/species/xeno/on_species_gain(mob/living/carbon/human/C, datum/species/old_species)
+ if(("legs" in C.dna.species.mutant_bodyparts) && (C.dna.features["legs"] == "Digitigrade" || C.dna.features["legs"] == "Avian"))
+ species_traits += DIGITIGRADE
+ if(DIGITIGRADE in species_traits)
+ C.Digitigrade_Leg_Swap(FALSE)
+ . = ..()
+
+/datum/species/xeno/on_species_loss(mob/living/carbon/human/C, datum/species/new_species)
+ if(("legs" in C.dna.species.mutant_bodyparts) && C.dna.features["legs"] == "Plantigrade")
+ species_traits -= DIGITIGRADE
+ if(DIGITIGRADE in species_traits)
+ C.Digitigrade_Leg_Swap(TRUE)
+ . = ..()
+
+//Praise the Omnissiah, A challange worthy of my skills - HS
+
+//EXOTIC//
+//These races will likely include lots of downsides and upsides. Keep them relatively balanced.//
+
+//misc
+/mob/living/carbon/human/dummy
+ no_vore = TRUE
+
+/mob/living/carbon/human/vore
+ devourable = TRUE
+ digestable = TRUE
+ feeding = TRUE
diff --git a/code/modules/mob/living/carbon/human/species_types/golems.dm b/code/modules/mob/living/carbon/human/species_types/golems.dm
index 2907caff13..8f84590ff4 100644
--- a/code/modules/mob/living/carbon/human/species_types/golems.dm
+++ b/code/modules/mob/living/carbon/human/species_types/golems.dm
@@ -45,7 +45,7 @@
return golem_name
/datum/species/golem/random
- name = "Random Golem"
+ name = "Golem Mutant"
blacklisted = FALSE
dangerous_existence = FALSE
var/static/list/random_golem_types
@@ -420,7 +420,7 @@
H.visible_message("[H] teleports!", "You destabilize and teleport!")
new /obj/effect/particle_effect/sparks(get_turf(H))
playsound(get_turf(H), "sparks", 50, 1)
- do_teleport(H, get_turf(H), 6, asoundin = 'sound/weapons/emitter2.ogg')
+ do_teleport(H, get_turf(H), 6, asoundin = 'sound/weapons/emitter2.ogg', channel = TELEPORT_CHANNEL_BLUESPACE)
last_teleport = world.time
/datum/species/golem/bluespace/spec_hitby(atom/movable/AM, mob/living/carbon/human/H)
@@ -486,7 +486,7 @@
spark_system.set_up(10, 0, src)
spark_system.attach(H)
spark_system.start()
- do_teleport(H, get_turf(H), 12, asoundin = 'sound/weapons/emitter2.ogg')
+ do_teleport(H, get_turf(H), 12, asoundin = 'sound/weapons/emitter2.ogg', channel = TELEPORT_CHANNEL_BLUESPACE)
last_teleport = world.time
UpdateButtonIcon() //action icon looks unavailable
sleep(cooldown + 5)
@@ -519,6 +519,11 @@
..()
last_banana = world.time
last_honk = world.time
+ RegisterSignal(C, COMSIG_MOB_SAY, .proc/handle_speech)
+
+/datum/species/golem/bananium/on_species_loss(mob/living/carbon/C)
+ . = ..()
+ UnregisterSignal(C, COMSIG_MOB_SAY)
/datum/species/golem/bananium/random_name(gender,unique,lastname)
var/clown_name = pick(GLOB.clown_names)
@@ -567,9 +572,8 @@
/datum/species/golem/bananium/spec_death(gibbed, mob/living/carbon/human/H)
playsound(get_turf(H), 'sound/misc/sadtrombone.ogg', 70, 0)
-/datum/species/golem/bananium/get_spans()
- return list(SPAN_CLOWN)
-
+/datum/species/golem/bananium/proc/handle_speech(datum/source, list/speech_args)
+ speech_args[SPEECH_SPANS] |= SPAN_CLOWN
/datum/species/golem/runic
name = "Runic Golem"
@@ -577,7 +581,7 @@
limbs_id = "cultgolem"
sexes = FALSE
info_text = "As a Runic Golem, you possess eldritch powers granted by the Elder Goddess Nar'Sie."
- species_traits = list(NOBLOOD,NO_UNDERWEAR,NOEYES) //no mutcolors
+ species_traits = list(NOBLOOD,NO_UNDERWEAR,NOEYES,NOGENITALS,NOAROUSAL) //no mutcolors
prefix = "Runic"
special_names = null
@@ -631,7 +635,7 @@
say_mod = "clicks"
limbs_id = "clockgolem"
info_text = "As a Clockwork Golem, you are faster than other types of golems. On death, you will break down into scrap."
- species_traits = list(NOBLOOD,NO_UNDERWEAR,NOEYES)
+ species_traits = list(NOBLOOD,NO_UNDERWEAR,NOEYES,NOGENITALS,NOAROUSAL)
inherent_biotypes = list(MOB_ROBOTIC, MOB_HUMANOID)
armor = 20 //Reinforced, but much less so to allow for fast movement
attack_verb = "smash"
@@ -646,14 +650,16 @@
/datum/species/golem/clockwork/on_species_gain(mob/living/carbon/human/H)
. = ..()
H.faction |= "ratvar"
+ RegisterSignal(H, COMSIG_MOB_SAY, .proc/handle_speech)
/datum/species/golem/clockwork/on_species_loss(mob/living/carbon/human/H)
if(!is_servant_of_ratvar(H))
H.faction -= "ratvar"
+ UnregisterSignal(H, COMSIG_MOB_SAY)
. = ..()
-/datum/species/golem/clockwork/get_spans()
- return SPAN_ROBOT //beep
+/datum/species/golem/clockwork/proc/handle_speech(datum/source, list/speech_args)
+ speech_args[SPEECH_SPANS] |= SPAN_ROBOT //beep
/datum/species/golem/clockwork/spec_death(gibbed, mob/living/carbon/human/H)
gibbed = !has_corpse ? FALSE : gibbed
@@ -685,7 +691,7 @@
sexes = FALSE
info_text = "As a Cloth Golem, you are able to reform yourself after death, provided your remains aren't burned or destroyed. You are, of course, very flammable. \
Being made of cloth, your body is magic resistant and faster than that of other golems, but weaker and less resilient."
- species_traits = list(NOBLOOD,NO_UNDERWEAR) //no mutcolors, and can burn
+ species_traits = list(NOBLOOD,NO_UNDERWEAR,NOGENITALS,NOAROUSAL) //no mutcolors, and can burn
inherent_traits = list(TRAIT_RESISTCOLD,TRAIT_NOBREATH,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_RADIMMUNE,TRAIT_PIERCEIMMUNE,TRAIT_NODISMEMBER,TRAIT_NOGUNS)
inherent_biotypes = list(MOB_UNDEAD, MOB_HUMANOID)
armor = 15 //feels no pain, but not too resistant
@@ -951,7 +957,7 @@
sexes = FALSE
fixed_mut_color = "ffffff"
attack_verb = "rattl"
- species_traits = list(NOBLOOD,NO_UNDERWEAR,NOGENITALS,NOAROUSAL,MUTCOLORS)
+ species_traits = list(NOBLOOD,NO_UNDERWEAR,NOGENITALS,NOAROUSAL,MUTCOLORS)
inherent_traits = list(TRAIT_RESISTHEAT,TRAIT_NOBREATH,TRAIT_RESISTCOLD,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_NOFIRE,TRAIT_NOGUNS,TRAIT_RADIMMUNE,TRAIT_PIERCEIMMUNE,TRAIT_NODISMEMBER,TRAIT_FAKEDEATH,TRAIT_CALCIUM_HEALER)
info_text = "As a Bone Golem, You have a powerful spell that lets you chill your enemies with fear, and milk heals you! Just make sure to watch our for bone-hurting juice."
var/datum/action/innate/bonechill/bonechill
diff --git a/modular_citadel/code/modules/mob/living/carbon/human/species_types/ipc.dm b/code/modules/mob/living/carbon/human/species_types/ipc.dm
similarity index 94%
rename from modular_citadel/code/modules/mob/living/carbon/human/species_types/ipc.dm
rename to code/modules/mob/living/carbon/human/species_types/ipc.dm
index bbbe863ec2..95b924ea18 100644
--- a/modular_citadel/code/modules/mob/living/carbon/human/species_types/ipc.dm
+++ b/code/modules/mob/living/carbon/human/species_types/ipc.dm
@@ -1,5 +1,5 @@
/datum/species/ipc
- name = "IPC"
+ name = "I.P.C."
id = "ipc"
say_mod = "beeps"
default_color = "00FF00"
@@ -11,6 +11,7 @@
mutant_bodyparts = list("ipc_screen", "ipc_antenna")
default_features = list("ipc_screen" = "Blank", "ipc_antenna" = "None")
meat = /obj/item/reagent_containers/food/snacks/meat/slab/human/mutant/ipc
+ mutanttongue = /obj/item/organ/tongue/robot/ipc
exotic_blood = "oil"
@@ -27,9 +28,6 @@
screen.Remove(C)
..()
-/datum/species/ipc/get_spans()
- return SPAN_ROBOT
-
/datum/action/innate/monitor_change
name = "Screen Change"
check_flags = AB_CHECK_CONSCIOUS
diff --git a/code/modules/mob/living/carbon/human/species_types/jellypeople.dm b/code/modules/mob/living/carbon/human/species_types/jellypeople.dm
index b218b2cefc..03cd514300 100644
--- a/code/modules/mob/living/carbon/human/species_types/jellypeople.dm
+++ b/code/modules/mob/living/carbon/human/species_types/jellypeople.dm
@@ -5,6 +5,7 @@
default_color = "00FF90"
say_mod = "chirps"
species_traits = list(MUTCOLORS,EYECOLOR,HAIR,FACEHAIR,NOBLOOD)
+ mutantlungs = /obj/item/organ/lungs/slime
mutant_bodyparts = list("mam_tail", "mam_ears", "mam_snouts", "taur") //CIT CHANGE
default_features = list("mcolor" = "FFF", "mam_tail" = "None", "mam_ears" = "None", "mam_snouts" = "None", "taur" = "None") //CIT CHANGE
inherent_traits = list(TRAIT_TOXINLOVER)
@@ -13,7 +14,8 @@
damage_overlay_type = ""
var/datum/action/innate/regenerate_limbs/regenerate_limbs
var/datum/action/innate/slime_change/slime_change //CIT CHANGE
- liked_food = MEAT
+ liked_food = TOXIC | MEAT
+ toxic_food = null
coldmod = 6 // = 3x cold damage
heatmod = 0.5 // = 1/4x heat damage
burnmod = 0.5 // = 1/2x generic burn damage
@@ -46,14 +48,14 @@
H.adjustBruteLoss(5)
to_chat(H, "You feel empty!")
- if(H.blood_volume < BLOOD_VOLUME_NORMAL)
+ if(H.blood_volume < (BLOOD_VOLUME_NORMAL * H.blood_ratio))
if(H.nutrition >= NUTRITION_LEVEL_STARVING)
H.blood_volume += 3
H.nutrition -= 2.5
- if(H.blood_volume < BLOOD_VOLUME_OKAY)
+ if(H.blood_volume < (BLOOD_VOLUME_OKAY*H.blood_ratio))
if(prob(5))
to_chat(H, "You feel drained!")
- if(H.blood_volume < BLOOD_VOLUME_BAD)
+ if(H.blood_volume < (BLOOD_VOLUME_BAD*H.blood_ratio))
Cannibalize_Body(H)
if(regenerate_limbs)
regenerate_limbs.UpdateButtonIcon()
@@ -85,7 +87,7 @@
var/list/limbs_to_heal = H.get_missing_limbs()
if(limbs_to_heal.len < 1)
return 0
- if(H.blood_volume >= BLOOD_VOLUME_OKAY+40)
+ if(H.blood_volume >= (BLOOD_VOLUME_OKAY*H.blood_ratio)+40)
return 1
return 0
@@ -96,13 +98,13 @@
to_chat(H, "You feel intact enough as it is.")
return
to_chat(H, "You focus intently on your missing [limbs_to_heal.len >= 2 ? "limbs" : "limb"]...")
- if(H.blood_volume >= 40*limbs_to_heal.len+BLOOD_VOLUME_OKAY)
+ if(H.blood_volume >= 40*limbs_to_heal.len+(BLOOD_VOLUME_OKAY*H.blood_ratio))
H.regenerate_limbs()
H.blood_volume -= 40*limbs_to_heal.len
to_chat(H, "...and after a moment you finish reforming!")
return
else if(H.blood_volume >= 40)//We can partially heal some limbs
- while(H.blood_volume >= BLOOD_VOLUME_OKAY+40)
+ while(H.blood_volume >= (BLOOD_VOLUME_OKAY*H.blood_ratio)+40)
var/healed_limb = pick(limbs_to_heal)
H.regenerate_limb(healed_limb)
limbs_to_heal -= healed_limb
@@ -116,7 +118,7 @@
//Slime people are able to split like slimes, retaining a single mind that can swap between bodies at will, even after death.
/datum/species/jelly/slime
- name = "Slimeperson"
+ name = "Xenobiological Slime Entity"
id = "slime"
default_color = "00FFFF"
species_traits = list(MUTCOLORS,EYECOLOR,HAIR,FACEHAIR,NOBLOOD)
@@ -136,7 +138,7 @@
bodies -= C // This means that the other bodies maintain a link
// so if someone mindswapped into them, they'd still be shared.
bodies = null
- C.blood_volume = min(C.blood_volume, BLOOD_VOLUME_NORMAL)
+ C.blood_volume = min(C.blood_volume, (BLOOD_VOLUME_NORMAL*C.blood_ratio))
..()
/datum/species/jelly/slime/on_species_gain(mob/living/carbon/C, datum/species/old_species)
@@ -387,12 +389,268 @@
"...and move this one instead.")
+////////////////////////////////////////////////////////Round Start Slimes///////////////////////////////////////////////////////////////////
+
+/datum/species/jelly/roundstartslime
+ name = "Xenobiological Slime Hybrid"
+ id = "slimeperson"
+ limbs_id = "slime"
+ default_color = "00FFFF"
+ species_traits = list(MUTCOLORS,EYECOLOR,HAIR,FACEHAIR,NOBLOOD)
+ inherent_traits = list(TRAIT_TOXINLOVER)
+ mutant_bodyparts = list("mam_tail", "mam_ears", "mam_body_markings", "mam_snouts", "taur")
+ default_features = list("mcolor" = "FFF", "mcolor2" = "FFF","mcolor3" = "FFF", "mam_tail" = "None", "mam_ears" = "None", "mam_body_markings" = "Plain", "mam_snouts" = "None", "taur" = "None")
+ say_mod = "says"
+ hair_color = "mutcolor"
+ hair_alpha = 160 //a notch brighter so it blends better.
+ coldmod = 3
+ heatmod = 1
+ burnmod = 1
+
+/datum/species/jelly/roundstartslime/spec_death(gibbed, mob/living/carbon/human/H)
+ if(H)
+ stop_wagging_tail(H)
+
+/datum/species/jelly/roundstartslime/spec_stun(mob/living/carbon/human/H,amount)
+ if(H)
+ stop_wagging_tail(H)
+ . = ..()
+
+/datum/species/jelly/roundstartslime/can_wag_tail(mob/living/carbon/human/H)
+ return ("mam_tail" in mutant_bodyparts) || ("mam_waggingtail" in mutant_bodyparts)
+
+/datum/species/jelly/roundstartslime/is_wagging_tail(mob/living/carbon/human/H)
+ return ("mam_waggingtail" in mutant_bodyparts)
+
+/datum/species/jelly/roundstartslime/start_wagging_tail(mob/living/carbon/human/H)
+ if("mam_tail" in mutant_bodyparts)
+ mutant_bodyparts -= "mam_tail"
+ mutant_bodyparts |= "mam_waggingtail"
+ H.update_body()
+
+/datum/species/jelly/roundstartslime/stop_wagging_tail(mob/living/carbon/human/H)
+ if("mam_waggingtail" in mutant_bodyparts)
+ mutant_bodyparts -= "mam_waggingtail"
+ mutant_bodyparts |= "mam_tail"
+ H.update_body()
+
+
+/datum/action/innate/slime_change
+ name = "Alter Form"
+ check_flags = AB_CHECK_CONSCIOUS
+ button_icon_state = "alter_form" //placeholder
+ icon_icon = 'modular_citadel/icons/mob/actions/actions_slime.dmi'
+ background_icon_state = "bg_alien"
+
+/datum/action/innate/slime_change/Activate()
+ var/mob/living/carbon/human/H = owner
+ if(!isjellyperson(H))
+ return
+ else
+ H.visible_message("[owner] gains a look of \
+ concentration while standing perfectly still.\
+ Their body seems to shift and starts getting more goo-like.",
+ "You focus intently on altering your body while \
+ standing perfectly still...")
+ change_form()
+
+/datum/action/innate/slime_change/proc/change_form()
+ var/mob/living/carbon/human/H = owner
+ var/select_alteration = input(owner, "Select what part of your form to alter", "Form Alteration", "cancel") in list("Hair Style", "Genitals", "Tail", "Snout", "Markings", "Ears", "Taur body", "Penis", "Vagina", "Penis Length", "Breast Size", "Breast Shape", "Cancel")
+ if(select_alteration == "Hair Style")
+ if(H.gender == MALE)
+ var/new_style = input(owner, "Select a facial hair style", "Hair Alterations") as null|anything in GLOB.facial_hair_styles_list
+ if(new_style)
+ H.facial_hair_style = new_style
+ else
+ H.facial_hair_style = "Shaved"
+ //handle normal hair
+ var/new_style = input(owner, "Select a hair style", "Hair Alterations") as null|anything in GLOB.hair_styles_list
+ if(new_style)
+ H.hair_style = new_style
+ H.update_hair()
+ else if (select_alteration == "Genitals")
+ var/list/organs = list()
+ var/operation = input("Select organ operation.", "Organ Manipulation", "cancel") in list("add sexual organ", "remove sexual organ", "cancel")
+ switch(operation)
+ if("add sexual organ")
+ var/new_organ = input("Select sexual organ:", "Organ Manipulation") in list("Penis", "Testicles", "Breasts", "Vagina", "Womb", "Cancel")
+ if(new_organ == "Penis")
+ H.give_penis()
+ else if(new_organ == "Testicles")
+ H.give_balls()
+ else if(new_organ == "Breasts")
+ H.give_breasts()
+ else if(new_organ == "Vagina")
+ H.give_vagina()
+ else if(new_organ == "Womb")
+ H.give_womb()
+ else
+ return
+ if("remove sexual organ")
+ for(var/obj/item/organ/genital/X in H.internal_organs)
+ var/obj/item/organ/I = X
+ organs["[I.name] ([I.type])"] = I
+ var/obj/item/organ = input("Select sexual organ:", "Organ Manipulation", null) in organs
+ organ = organs[organ]
+ if(!organ)
+ return
+ var/obj/item/organ/genital/O
+ if(isorgan(organ))
+ O = organ
+ O.Remove(H)
+ organ.forceMove(get_turf(H))
+ qdel(organ)
+ H.update_genitals()
+
+ else if (select_alteration == "Ears")
+ var/list/snowflake_ears_list = list("Normal" = null)
+ for(var/path in GLOB.mam_ears_list)
+ var/datum/sprite_accessory/mam_ears/instance = GLOB.mam_ears_list[path]
+ if(istype(instance, /datum/sprite_accessory))
+ var/datum/sprite_accessory/S = instance
+ if((!S.ckeys_allowed) || (S.ckeys_allowed.Find(H.client.ckey)))
+ snowflake_ears_list[S.name] = path
+ var/new_ears
+ new_ears = input(owner, "Choose your character's ears:", "Ear Alteration") as null|anything in snowflake_ears_list
+ if(new_ears)
+ H.dna.features["mam_ears"] = new_ears
+ H.update_body()
+
+ else if (select_alteration == "Snout")
+ var/list/snowflake_snouts_list = list("Normal" = null)
+ for(var/path in GLOB.mam_snouts_list)
+ var/datum/sprite_accessory/mam_snouts/instance = GLOB.mam_snouts_list[path]
+ if(istype(instance, /datum/sprite_accessory))
+ var/datum/sprite_accessory/S = instance
+ if((!S.ckeys_allowed) || (S.ckeys_allowed.Find(H.client.ckey)))
+ snowflake_snouts_list[S.name] = path
+ var/new_snout
+ new_snout = input(owner, "Choose your character's face:", "Face Alteration") as null|anything in snowflake_snouts_list
+ if(new_snout)
+ H.dna.features["mam_snouts"] = new_snout
+ H.update_body()
+
+ else if (select_alteration == "Markings")
+ var/list/snowflake_markings_list = list()
+ for(var/path in GLOB.mam_body_markings_list)
+ var/datum/sprite_accessory/mam_body_markings/instance = GLOB.mam_body_markings_list[path]
+ if(istype(instance, /datum/sprite_accessory))
+ var/datum/sprite_accessory/S = instance
+ if((!S.ckeys_allowed) || (S.ckeys_allowed.Find(H.client.ckey)))
+ snowflake_markings_list[S.name] = path
+ var/new_mam_body_markings
+ new_mam_body_markings = input(H, "Choose your character's body markings:", "Marking Alteration") as null|anything in snowflake_markings_list
+ if(new_mam_body_markings)
+ H.dna.features["mam_body_markings"] = new_mam_body_markings
+ if(new_mam_body_markings == "None")
+ H.dna.features["mam_body_markings"] = "Plain"
+ for(var/X in H.bodyparts) //propagates the markings changes
+ var/obj/item/bodypart/BP = X
+ BP.update_limb(FALSE, H)
+ H.update_body()
+
+ else if (select_alteration == "Tail")
+ var/list/snowflake_tails_list = list("Normal" = null)
+ for(var/path in GLOB.mam_tails_list)
+ var/datum/sprite_accessory/mam_tails/instance = GLOB.mam_tails_list[path]
+ if(istype(instance, /datum/sprite_accessory))
+ var/datum/sprite_accessory/S = instance
+ if((!S.ckeys_allowed) || (S.ckeys_allowed.Find(H.client.ckey)))
+ snowflake_tails_list[S.name] = path
+ var/new_tail
+ new_tail = input(owner, "Choose your character's Tail(s):", "Tail Alteration") as null|anything in snowflake_tails_list
+ if(new_tail)
+ H.dna.features["mam_tail"] = new_tail
+ if(new_tail != "None")
+ H.dna.features["taur"] = "None"
+ H.update_body()
+
+ else if (select_alteration == "Taur body")
+ var/list/snowflake_taur_list = list("Normal" = null)
+ for(var/path in GLOB.taur_list)
+ var/datum/sprite_accessory/taur/instance = GLOB.taur_list[path]
+ if(istype(instance, /datum/sprite_accessory))
+ var/datum/sprite_accessory/S = instance
+ if((!S.ckeys_allowed) || (S.ckeys_allowed.Find(H.client.ckey)))
+ snowflake_taur_list[S.name] = path
+ var/new_taur
+ new_taur = input(owner, "Choose your character's tauric body:", "Tauric Alteration") as null|anything in snowflake_taur_list
+ if(new_taur)
+ H.dna.features["taur"] = new_taur
+ if(new_taur != "None")
+ H.dna.features["mam_tail"] = "None"
+ H.update_body()
+
+ else if (select_alteration == "Penis")
+ for(var/obj/item/organ/genital/penis/X in H.internal_organs)
+ qdel(X)
+ var/new_shape
+ new_shape = input(owner, "Choose your character's dong", "Genital Alteration") as null|anything in GLOB.cock_shapes_list
+ if(new_shape)
+ H.dna.features["cock_shape"] = new_shape
+ H.update_genitals()
+ H.give_balls()
+ H.give_penis()
+ H.apply_overlay()
+
+
+ else if (select_alteration == "Vagina")
+ for(var/obj/item/organ/genital/vagina/X in H.internal_organs)
+ qdel(X)
+ var/new_shape
+ new_shape = input(owner, "Choose your character's pussy", "Genital Alteration") as null|anything in GLOB.vagina_shapes_list
+ if(new_shape)
+ H.dna.features["vag_shape"] = new_shape
+ H.update_genitals()
+ H.give_womb()
+ H.give_vagina()
+ H.apply_overlay()
+
+ else if (select_alteration == "Penis Length")
+ for(var/obj/item/organ/genital/penis/X in H.internal_organs)
+ qdel(X)
+ var/new_length
+ new_length = input(owner, "Penis length in inches:\n([COCK_SIZE_MIN]-[COCK_SIZE_MAX])", "Genital Alteration") as num|null
+ if(new_length)
+ H.dna.features["cock_length"] = max(min( round(text2num(new_length)), COCK_SIZE_MAX),COCK_SIZE_MIN)
+ H.update_genitals()
+ H.apply_overlay()
+ H.give_balls()
+ H.give_penis()
+
+ else if (select_alteration == "Breast Size")
+ for(var/obj/item/organ/genital/breasts/X in H.internal_organs)
+ qdel(X)
+ var/new_size
+ new_size = input(owner, "Breast Size", "Genital Alteration") as null|anything in GLOB.breasts_size_list
+ if(new_size)
+ H.dna.features["breasts_size"] = new_size
+ H.update_genitals()
+ H.apply_overlay()
+ H.give_breasts()
+
+ else if (select_alteration == "Breast Shape")
+ for(var/obj/item/organ/genital/breasts/X in H.internal_organs)
+ qdel(X)
+ var/new_shape
+ new_shape = input(owner, "Breast Shape", "Genital Alteration") as null|anything in GLOB.breasts_shapes_list
+ if(new_shape)
+ H.dna.features["breasts_shape"] = new_shape
+ H.update_genitals()
+ H.apply_overlay()
+ H.give_breasts()
+
+ else
+ return
+
+
///////////////////////////////////LUMINESCENTS//////////////////////////////////////////
//Luminescents are able to consume and use slime extracts, without them decaying.
/datum/species/jelly/luminescent
- name = "Luminescent"
+ name = "Luminescent Slime Entity"
id = "lum"
say_mod = "says"
var/glow_intensity = LUMINESCENT_DEFAULT_GLOW
@@ -559,7 +817,7 @@
//Stargazers are the telepathic branch of jellypeople, able to project psychic messages and to link minds with willing participants.
/datum/species/jelly/stargazer
- name = "Stargazer"
+ name = "Stargazer Slime Entity"
id = "stargazer"
var/datum/action/innate/project_thought/project_thought
var/datum/action/innate/link_minds/link_minds
@@ -727,4 +985,4 @@
to_chat(H, "You connect [target]'s mind to your slime link!")
else
to_chat(H, "You can't seem to link [target]'s mind...")
- to_chat(target, "The foreign presence leaves your mind.")
\ No newline at end of file
+ to_chat(target, "The foreign presence leaves your mind.")
diff --git a/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm b/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm
index 30bf705547..4dbfd23df8 100644
--- a/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm
+++ b/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm
@@ -1,17 +1,19 @@
/datum/species/lizard
// Reptilian humanoids with scaled skin and tails.
- name = "Lizardperson"
+ name = "Anthromorphic Lizard"
id = "lizard"
say_mod = "hisses"
default_color = "00FF00"
- species_traits = list(MUTCOLORS,EYECOLOR,HAIR,FACEHAIR,LIPS)
+ species_traits = list(MUTCOLORS,EYECOLOR,HAIR,FACEHAIR,LIPS,HORNCOLOR)
inherent_biotypes = list(MOB_ORGANIC, MOB_HUMANOID, MOB_REPTILE)
mutant_bodyparts = list("tail_lizard", "snout", "spines", "horns", "frills", "body_markings", "legs", "taur")
mutanttongue = /obj/item/organ/tongue/lizard
mutanttail = /obj/item/organ/tail/lizard
coldmod = 1.5
heatmod = 0.67
- default_features = list("mcolor" = "0F0", "mcolor2" = "0F0", "mcolor3" = "0F0", "tail_lizard" = "Smooth", "snout" = "Round", "horns" = "None", "frills" = "None", "spines" = "None", "body_markings" = "None", "legs" = "Normal Legs", "taur" = "None")
+ default_features = list("mcolor" = "0F0", "mcolor2" = "0F0", "mcolor3" = "0F0", "tail_lizard" = "Smooth", "snout" = "Round",
+ "horns" = "None", "frills" = "None", "spines" = "None", "body_markings" = "None",
+ "legs" = "Digitigrade", "taur" = "None")
attack_verb = "slash"
attack_sound = 'sound/weapons/slash.ogg'
miss_sound = 'sound/weapons/slashmiss.ogg'
@@ -71,14 +73,14 @@
H.update_body()
/datum/species/lizard/on_species_gain(mob/living/carbon/human/C, datum/species/old_species)
- if(("legs" in C.dna.species.mutant_bodyparts) && C.dna.features["legs"] == "Digitigrade Legs")
+ if(("legs" in C.dna.species.mutant_bodyparts) && (C.dna.features["legs"] == "Digitigrade" || C.dna.features["legs"] == "Avian"))
species_traits += DIGITIGRADE
if(DIGITIGRADE in species_traits)
C.Digitigrade_Leg_Swap(FALSE)
return ..()
/datum/species/lizard/on_species_loss(mob/living/carbon/human/C, datum/species/new_species)
- if(("legs" in C.dna.species.mutant_bodyparts) && C.dna.features["legs"] == "Normal Legs")
+ if(("legs" in C.dna.species.mutant_bodyparts) && C.dna.features["legs"] == "Plantigrade")
species_traits -= DIGITIGRADE
if(DIGITIGRADE in species_traits)
C.Digitigrade_Leg_Swap(TRUE)
diff --git a/code/modules/mob/living/carbon/human/species_types/mothmen.dm b/code/modules/mob/living/carbon/human/species_types/mothmen.dm
deleted file mode 100644
index d15d989384..0000000000
--- a/code/modules/mob/living/carbon/human/species_types/mothmen.dm
+++ /dev/null
@@ -1,61 +0,0 @@
-/datum/species/moth
- name = "Mothman"
- id = "moth"
- say_mod = "flutters"
- default_color = "00FF00"
- species_traits = list(LIPS, NOEYES)
- inherent_biotypes = list(MOB_ORGANIC, MOB_HUMANOID, MOB_BUG)
- mutant_bodyparts = list("moth_wings")
- default_features = list("moth_wings" = "Plain")
- attack_verb = "slash"
- attack_sound = 'sound/weapons/slash.ogg'
- miss_sound = 'sound/weapons/slashmiss.ogg'
- meat = /obj/item/reagent_containers/food/snacks/meat/slab/human/mutant/moth
- liked_food = VEGETABLES | DAIRY
- disliked_food = FRUIT | GROSS
- toxic_food = MEAT | RAW
- mutanteyes = /obj/item/organ/eyes/moth
-
-/datum/species/moth/on_species_gain(mob/living/carbon/C)
- . = ..()
- if(ishuman(C))
- var/mob/living/carbon/human/H = C
- if(!H.dna.features["moth_wings"])
- H.dna.features["moth_wings"] = "[(H.client && H.client.prefs && LAZYLEN(H.client.prefs.features) && H.client.prefs.features["moth_wings"]) ? H.client.prefs.features["moth_wings"] : "Plain"]"
- handle_mutant_bodyparts(H)
-
-/datum/species/moth/random_name(gender,unique,lastname)
- if(unique)
- return random_unique_moth_name()
-
- var/randname = moth_name()
-
- if(lastname)
- randname += " [lastname]"
-
- return randname
-
-/datum/species/moth/handle_fire(mob/living/carbon/human/H, no_protection = FALSE)
- ..()
- if(H.dna.features["moth_wings"] != "Burnt Off" && H.bodytemperature >= 800 && H.fire_stacks > 0) //do not go into the extremely hot light. you will not survive
- to_chat(H, "Your precious wings burn to a crisp!")
- H.dna.features["moth_wings"] = "Burnt Off"
- handle_mutant_bodyparts(H)
-
-/datum/species/moth/handle_chemicals(datum/reagent/chem, mob/living/carbon/human/H)
- . = ..()
- if(chem.id == "pestkiller")
- H.adjustToxLoss(3)
- H.reagents.remove_reagent(chem.id, REAGENTS_METABOLISM)
-
-/datum/species/moth/check_weakness(obj/item/weapon, mob/living/attacker)
- if(istype(weapon, /obj/item/melee/flyswatter))
- return 9 //flyswatters deal 10x damage to moths
- return 0
-
-/datum/species/moth/space_move(mob/living/carbon/human/H)
- . = ..()
- if(H.loc && !isspaceturf(H.loc) && H.dna.features["moth_wings"] != "Burnt Off")
- var/datum/gas_mixture/current = H.loc.return_air()
- if(current && (current.return_pressure() >= ONE_ATMOSPHERE*0.85)) //as long as there's reasonable pressure and no gravity, flight is possible
- return TRUE
diff --git a/code/modules/mob/living/carbon/human/species_types/mushpeople.dm b/code/modules/mob/living/carbon/human/species_types/mushpeople.dm
index 7be0265cba..ceadb28115 100644
--- a/code/modules/mob/living/carbon/human/species_types/mushpeople.dm
+++ b/code/modules/mob/living/carbon/human/species_types/mushpeople.dm
@@ -1,5 +1,5 @@
/datum/species/mush //mush mush codecuck
- name = "Mushroomperson"
+ name = "Anthromorphic Mushroom"
id = "mush"
mutant_bodyparts = list("caps")
default_features = list("caps" = "Round")
diff --git a/code/modules/mob/living/carbon/human/species_types/plasmamen.dm b/code/modules/mob/living/carbon/human/species_types/plasmamen.dm
index d7bb151ddc..06f456e004 100644
--- a/code/modules/mob/living/carbon/human/species_types/plasmamen.dm
+++ b/code/modules/mob/living/carbon/human/species_types/plasmamen.dm
@@ -16,7 +16,6 @@
burnmod = 1.5
heatmod = 1.5
breathid = "tox"
- speedmod = 1
damage_overlay_type = ""//let's not show bloody wounds or burns over bones.
var/internal_fire = FALSE //If the bones themselves are burning clothes won't help you much
disliked_food = FRUIT
@@ -55,19 +54,95 @@
..()
/datum/species/plasmaman/before_equip_job(datum/job/J, mob/living/carbon/human/H, visualsOnly = FALSE)
+ var/current_job = J.title
var/datum/outfit/plasmaman/O = new /datum/outfit/plasmaman
+ switch(current_job)
+ if("Chaplain")
+ O = new /datum/outfit/plasmaman/chaplain
+
+ if("Curator")
+ O = new /datum/outfit/plasmaman/curator
+
+ if("Janitor")
+ O = new /datum/outfit/plasmaman/janitor
+
+ if("Botanist")
+ O = new /datum/outfit/plasmaman/botany
+
+ if("Bartender", "Lawyer")
+ O = new /datum/outfit/plasmaman/bar
+
+ if("Cook")
+ O = new /datum/outfit/plasmaman/chef
+
+ if("Security Officer")
+ O = new /datum/outfit/plasmaman/security
+
+ if("Detective")
+ O = new /datum/outfit/plasmaman/detective
+
+ if("Warden")
+ O = new /datum/outfit/plasmaman/warden
+
+ if("Cargo Technician", "Quartermaster")
+ O = new /datum/outfit/plasmaman/cargo
+
+ if("Shaft Miner")
+ O = new /datum/outfit/plasmaman/mining
+
+ if("Medical Doctor")
+ O = new /datum/outfit/plasmaman/medical
+
+ if("Chemist")
+ O = new /datum/outfit/plasmaman/chemist
+
+ if("Geneticist")
+ O = new /datum/outfit/plasmaman/genetics
+
+ if("Roboticist")
+ O = new /datum/outfit/plasmaman/robotics
+
+ if("Virologist")
+ O = new /datum/outfit/plasmaman/viro
+
+ if("Scientist")
+ O = new /datum/outfit/plasmaman/science
+
+ if("Station Engineer")
+ O = new /datum/outfit/plasmaman/engineering
+
+ if("Atmospheric Technician")
+ O = new /datum/outfit/plasmaman/atmospherics
+
+ if("Captain")
+ O = new /datum/outfit/plasmaman/captain
+
+ if("Head of Personnel")
+ O = new /datum/outfit/plasmaman/hop
+
+ if("Head of Security")
+ O = new /datum/outfit/plasmaman/hos
+
+ if("Chief Engineer")
+ O = new /datum/outfit/plasmaman/ce
+
+ if("Chief Medical Officer")
+ O = new /datum/outfit/plasmaman/cmo
+
+ if("Research Director")
+ O = new /datum/outfit/plasmaman/rd
+
+ if("Mime")
+ O = new /datum/outfit/plasmaman/mime
+
+ if("Clown")
+ O = new /datum/outfit/plasmaman/clown
+
H.equipOutfit(O, visualsOnly)
H.internal = H.get_item_for_held_index(2)
H.update_internals_hud_icon(1)
return 0
-/datum/species/plasmaman/qualifies_for_rank(rank, list/features)
- if(rank in GLOB.security_positions)
- return 0
- if(rank == "Clown" || rank == "Mime")//No funny bussiness
- return 0
- return ..()
-
/datum/species/plasmaman/random_name(gender,unique,lastname)
if(unique)
return random_unique_plasmaman_name()
diff --git a/code/modules/mob/living/carbon/human/species_types/podpeople.dm b/code/modules/mob/living/carbon/human/species_types/podpeople.dm
index 0da4073f1d..f0dd48c6c1 100644
--- a/code/modules/mob/living/carbon/human/species_types/podpeople.dm
+++ b/code/modules/mob/living/carbon/human/species_types/podpeople.dm
@@ -1,6 +1,6 @@
/datum/species/pod
// A mutation caused by a human being ressurected in a revival pod. These regain health in light, and begin to wither in darkness.
- name = "Podperson"
+ name = "Anthromorphic Plant"
id = "pod"
default_color = "59CE00"
species_traits = list(MUTCOLORS,EYECOLOR)
@@ -36,8 +36,8 @@
var/turf/T = H.loc
light_amount = min(1,T.get_lumcount()) - 0.5
H.nutrition += light_amount * light_nutrition_gain_factor
- if(H.nutrition > NUTRITION_LEVEL_FULL)
- H.nutrition = NUTRITION_LEVEL_FULL
+ if(H.nutrition >= NUTRITION_LEVEL_FULL)
+ H.nutrition = NUTRITION_LEVEL_FULL - 1
if(light_amount > 0.2) //if there's enough light, heal
H.heal_overall_damage(light_bruteheal, light_burnheal)
H.adjustToxLoss(-light_toxheal)
@@ -71,6 +71,7 @@
H.nutrition = min(H.nutrition+30, NUTRITION_LEVEL_FULL)
/datum/species/pod/pseudo_weak
+ name = "Anthromorphic Plant"
id = "podweak"
limbs_id = "pod"
species_traits = list(EYECOLOR,HAIR,FACEHAIR,LIPS,MUTCOLORS)
diff --git a/code/modules/mob/living/carbon/human/species_types/shadowpeople.dm b/code/modules/mob/living/carbon/human/species_types/shadowpeople.dm
index b574df7d79..09fc26d04f 100644
--- a/code/modules/mob/living/carbon/human/species_types/shadowpeople.dm
+++ b/code/modules/mob/living/carbon/human/species_types/shadowpeople.dm
@@ -102,6 +102,7 @@
color = "#1C1C1C"
var/respawn_progress = 0
var/obj/item/light_eater/blade
+ decay_factor = 0
/obj/item/organ/heart/nightmare/attack(mob/M, mob/living/carbon/user, obj/target)
@@ -122,10 +123,8 @@
if(special != HEART_SPECIAL_SHADOWIFY)
blade = new/obj/item/light_eater
M.put_in_hands(blade)
- START_PROCESSING(SSobj, src)
/obj/item/organ/heart/nightmare/Remove(mob/living/carbon/M, special = 0)
- STOP_PROCESSING(SSobj, src)
respawn_progress = 0
if(blade && special != HEART_SPECIAL_SHADOWIFY)
QDEL_NULL(blade)
@@ -138,9 +137,8 @@
/obj/item/organ/heart/nightmare/update_icon()
return //always beating visually
-/obj/item/organ/heart/nightmare/process()
- if(QDELETED(owner) || owner.stat != DEAD)
- respawn_progress = 0
+/obj/item/organ/heart/nightmare/on_death()
+ if(!owner)
return
var/turf/T = get_turf(owner)
if(istype(T))
@@ -171,12 +169,14 @@
armour_penetration = 35
lefthand_file = 'icons/mob/inhands/antag/changeling_lefthand.dmi'
righthand_file = 'icons/mob/inhands/antag/changeling_righthand.dmi'
- item_flags = ABSTRACT | NODROP | DROPDEL
+ item_flags = ABSTRACT | DROPDEL
w_class = WEIGHT_CLASS_HUGE
sharpness = IS_SHARP
+ total_mass = TOTAL_MASS_HAND_REPLACEMENT
/obj/item/light_eater/Initialize()
. = ..()
+ ADD_TRAIT(src, TRAIT_NODROP, HAND_REPLACEMENT_TRAIT)
AddComponent(/datum/component/butchering, 80, 70)
/obj/item/light_eater/afterattack(atom/movable/AM, mob/user, proximity)
diff --git a/code/modules/mob/living/carbon/human/species_types/skeletons.dm b/code/modules/mob/living/carbon/human/species_types/skeletons.dm
index 135992f3a6..5a20ca37a8 100644
--- a/code/modules/mob/living/carbon/human/species_types/skeletons.dm
+++ b/code/modules/mob/living/carbon/human/species_types/skeletons.dm
@@ -3,7 +3,7 @@
name = "Spooky Scary Skeleton"
id = "skeleton"
say_mod = "rattles"
- blacklisted = 1
+ blacklisted = 0
sexes = 0
meat = /obj/item/reagent_containers/food/snacks/meat/slab/human/mutant/skeleton
species_traits = list(NOBLOOD,NOGENITALS,NOAROUSAL)
@@ -12,13 +12,18 @@
mutanttongue = /obj/item/organ/tongue/bone
damage_overlay_type = ""//let's not show bloody wounds or burns over bones.
disliked_food = NONE
- liked_food = GROSS | MEAT | RAW
+ liked_food = GROSS | MEAT | RAW | DAIRY
/datum/species/skeleton/check_roundstart_eligible()
if(SSevents.holidays && SSevents.holidays[HALLOWEEN])
return TRUE
return ..()
-/datum/species/skeleton/pirate
- name = "Space Queen's Skeleton"
+/datum/species/skeleton/space
+ name = "Spooky Spacey Skeleton"
+ id = "spaceskeleton"
+ blacklisted = 1
inherent_traits = list(TRAIT_RESISTHEAT,TRAIT_NOBREATH,TRAIT_RESISTCOLD,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_RADIMMUNE,TRAIT_PIERCEIMMUNE,TRAIT_NOHUNGER,TRAIT_EASYDISMEMBER,TRAIT_LIMBATTACHMENT,TRAIT_FAKEDEATH, TRAIT_CALCIUM_HEALER)
+
+/datum/species/skeleton/space/check_roundstart_eligible()
+ return FALSE
\ No newline at end of file
diff --git a/code/modules/mob/living/carbon/human/species_types/synths.dm b/code/modules/mob/living/carbon/human/species_types/synths.dm
index 7e838c857f..ac18580e9b 100644
--- a/code/modules/mob/living/carbon/human/species_types/synths.dm
+++ b/code/modules/mob/living/carbon/human/species_types/synths.dm
@@ -1,10 +1,10 @@
/datum/species/synth
- name = "Synth" //inherited from the real species, for health scanners and things
+ name = "Synthetic" //inherited from the real species, for health scanners and things
id = "synth"
say_mod = "beep boops" //inherited from a user's real species
sexes = 0
species_traits = list(NOTRANSSTING,NOGENITALS,NOAROUSAL) //all of these + whatever we inherit from the real species
- inherent_traits = list(TRAIT_VIRUSIMMUNE,TRAIT_NODISMEMBER,TRAIT_NOHUNGER,TRAIT_NOBREATH)
+ inherent_traits = list(TRAIT_VIRUSIMMUNE,TRAIT_NODISMEMBER,TRAIT_NOLIMBDISABLE,TRAIT_NOHUNGER,TRAIT_NOBREATH)
inherent_biotypes = list(MOB_ROBOTIC, MOB_HUMANOID)
dangerous_existence = 1
blacklisted = 1
@@ -12,7 +12,7 @@
damage_overlay_type = "synth"
limbs_id = "synth"
var/list/initial_species_traits = list(NOTRANSSTING) //for getting these values back for assume_disguise()
- var/list/initial_inherent_traits = list(TRAIT_VIRUSIMMUNE,TRAIT_NODISMEMBER,TRAIT_NOHUNGER,TRAIT_NOBREATH)
+ var/list/initial_inherent_traits = list(TRAIT_VIRUSIMMUNE,TRAIT_NODISMEMBER,TRAIT_NOLIMBDISABLE,TRAIT_NOHUNGER,TRAIT_NOBREATH)
var/disguise_fail_health = 75 //When their health gets to this level their synthflesh partially falls off
var/datum/species/fake_species = null //a species to do most of our work for us, unless we're damaged
@@ -28,6 +28,11 @@
/datum/species/synth/on_species_gain(mob/living/carbon/human/H, datum/species/old_species)
..()
assume_disguise(old_species, H)
+ RegisterSignal(H, COMSIG_MOB_SAY, .proc/handle_speech)
+
+/datum/species/synth/on_species_loss(mob/living/carbon/human/H)
+ . = ..()
+ UnregisterSignal(H, COMSIG_MOB_SAY)
/datum/species/synth/handle_chemicals(datum/reagent/chem, mob/living/carbon/human/H)
if(chem.id == "synthflesh")
@@ -110,18 +115,12 @@
else
return ..()
-
-/datum/species/synth/get_spans()
- if(fake_species)
- return fake_species.get_spans()
- return list()
-
-
-/datum/species/synth/handle_speech(message, mob/living/carbon/human/H)
- if(H.health > disguise_fail_health)
- if(fake_species)
- return fake_species.handle_speech(message,H)
- else
- return ..()
- else
- return ..()
\ No newline at end of file
+/datum/species/synth/proc/handle_speech(datum/source, list/speech_args)
+ if (isliving(source)) // yeah it's gonna be living but just to be clean
+ var/mob/living/L = source
+ if(fake_species && L.health > disguise_fail_health)
+ switch (fake_species.type)
+ if (/datum/species/golem/bananium)
+ speech_args[SPEECH_SPANS] |= SPAN_CLOWN
+ if (/datum/species/golem/clockwork)
+ speech_args[SPEECH_SPANS] |= SPAN_ROBOT
\ No newline at end of file
diff --git a/code/modules/mob/living/carbon/human/species_types/vampire.dm b/code/modules/mob/living/carbon/human/species_types/vampire.dm
index 4bc3d622ac..53c6f1bd0f 100644
--- a/code/modules/mob/living/carbon/human/species_types/vampire.dm
+++ b/code/modules/mob/living/carbon/human/species_types/vampire.dm
@@ -46,7 +46,7 @@
C.adjustCloneLoss(-4)
return
C.blood_volume -= 0.75
- if(C.blood_volume <= BLOOD_VOLUME_SURVIVE)
+ if(C.blood_volume <= (BLOOD_VOLUME_SURVIVE*C.blood_ratio))
to_chat(C, "You ran out of blood!")
C.dust()
var/area/A = get_area(C)
diff --git a/code/modules/mob/living/carbon/human/species_types/zombies.dm b/code/modules/mob/living/carbon/human/species_types/zombies.dm
index 504dbb514b..e0cc3bb147 100644
--- a/code/modules/mob/living/carbon/human/species_types/zombies.dm
+++ b/code/modules/mob/living/carbon/human/species_types/zombies.dm
@@ -16,7 +16,12 @@
disliked_food = NONE
liked_food = GROSS | MEAT | RAW
-/datum/species/zombie/check_roundstart_eligible()
+/datum/species/zombie/notspaceproof
+ id = "notspaceproofzombie"
+ blacklisted = 0
+ inherent_traits = list(TRAIT_RESISTCOLD,TRAIT_RADIMMUNE,TRAIT_EASYDISMEMBER,TRAIT_LIMBATTACHMENT,TRAIT_NOBREATH,TRAIT_NODEATH,TRAIT_FAKEDEATH)
+
+/datum/species/zombie/notspaceproof/check_roundstart_eligible()
if(SSevents.holidays && SSevents.holidays[HALLOWEEN])
return TRUE
return ..()
@@ -47,7 +52,7 @@
/datum/species/zombie/infectious/spec_life(mob/living/carbon/C)
. = ..()
C.a_intent = INTENT_HARM // THE SUFFERING MUST FLOW
-
+
//Zombies never actually die, they just fall down until they regenerate enough to rise back up.
//They must be restrained, beheaded or gibbed to stop being a threat.
if(regen_cooldown < world.time)
@@ -58,7 +63,7 @@
C.adjustToxLoss(-heal_amt)
if(!C.InCritical() && prob(4))
playsound(C, pick(spooks), 50, TRUE, 10)
-
+
//Congrats you somehow died so hard you stopped being a zombie
/datum/species/zombie/infectious/spec_death(mob/living/carbon/C)
. = ..()
diff --git a/code/modules/mob/living/carbon/human/status_procs.dm b/code/modules/mob/living/carbon/human/status_procs.dm
index 5c20b0ce75..49121c9409 100644
--- a/code/modules/mob/living/carbon/human/status_procs.dm
+++ b/code/modules/mob/living/carbon/human/status_procs.dm
@@ -3,7 +3,7 @@
amount = dna.species.spec_stun(src,amount)
return ..()
-/mob/living/carbon/human/Knockdown(amount, updating = 1, ignore_canknockdown = 0)
+/mob/living/carbon/human/Knockdown(amount, updating = TRUE, ignore_canknockdown = FALSE, override_hardstun, override_stamdmg)
amount = dna.species.spec_stun(src,amount)
return ..()
diff --git a/code/modules/mob/living/carbon/human/update_icons.dm b/code/modules/mob/living/carbon/human/update_icons.dm
index feb80e8d2c..fa3e29f119 100644
--- a/code/modules/mob/living/carbon/human/update_icons.dm
+++ b/code/modules/mob/living/carbon/human/update_icons.dm
@@ -123,25 +123,20 @@ There are several things that need to be remembered:
if(U.adjusted == ALT_STYLE)
t_color = "[t_color]_d"
- if(U.mutantrace_variation)
- if(U.suit_style == DIGITIGRADE_SUIT_STYLE)
- U.alternate_worn_icon = 'modular_citadel/icons/mob/uniform_digi.dmi'
- if(U.adjusted == ALT_STYLE)
- t_color = "[t_color]_d_l"
- else if(U.adjusted == NORMAL_STYLE)
- t_color = "[t_color]_l"
- else
- U.alternate_worn_icon = null
+ var/alt_worn = U.alternate_worn_icon
+
+ if(!U.force_alternate_icon && U.mutantrace_variation && U.suit_style == DIGITIGRADE_SUIT_STYLE)
+ alt_worn = 'modular_citadel/icons/mob/uniform_digi.dmi'
var/mutable_appearance/uniform_overlay
if(dna && dna.species.sexes)
var/G = (gender == FEMALE) ? "f" : "m"
if(G == "f" && U.fitted != NO_FEMALE_UNIFORM)
- uniform_overlay = U.build_worn_icon(state = "[t_color]", default_layer = UNIFORM_LAYER, default_icon_file = ((w_uniform.alternate_worn_icon) ? w_uniform.alternate_worn_icon : 'icons/mob/uniform.dmi'), isinhands = FALSE, femaleuniform = U.fitted)
+ uniform_overlay = U.build_worn_icon(state = "[t_color]", default_layer = UNIFORM_LAYER, default_icon_file = (alt_worn ? alt_worn : 'icons/mob/uniform.dmi'), isinhands = FALSE, femaleuniform = U.fitted)
if(!uniform_overlay)
- uniform_overlay = U.build_worn_icon(state = "[t_color]", default_layer = UNIFORM_LAYER, default_icon_file = ((w_uniform.alternate_worn_icon) ? w_uniform.alternate_worn_icon : 'icons/mob/uniform.dmi'), isinhands = FALSE)
+ uniform_overlay = U.build_worn_icon(state = "[t_color]", default_layer = UNIFORM_LAYER, default_icon_file = (alt_worn ? alt_worn : 'icons/mob/uniform.dmi'), isinhands = FALSE)
if(OFFSET_UNIFORM in dna.species.offset_features)
@@ -288,8 +283,10 @@ There are several things that need to be remembered:
S.alternate_worn_icon = 'modular_citadel/icons/mob/digishoes.dmi'
else
S.alternate_worn_icon = null
-
- overlays_standing[SHOES_LAYER] = shoes.build_worn_icon(state = shoes.icon_state, default_layer = SHOES_LAYER, default_icon_file = ((shoes.alternate_worn_icon) ? shoes.alternate_worn_icon : 'icons/mob/feet.dmi'))
+ var/t_state = shoes.item_state
+ if (!t_state)
+ t_state = shoes.icon_state
+ overlays_standing[SHOES_LAYER] = shoes.build_worn_icon(state = t_state, default_layer = SHOES_LAYER, default_icon_file = ((shoes.alternate_worn_icon) ? shoes.alternate_worn_icon : 'icons/mob/feet.dmi'))
var/mutable_appearance/shoes_overlay = overlays_standing[SHOES_LAYER]
if(OFFSET_SHOES in dna.species.offset_features)
shoes_overlay.pixel_x += dna.species.offset_features[OFFSET_SHOES][1]
@@ -377,34 +374,38 @@ There are several things that need to be remembered:
if(wear_suit)
var/obj/item/clothing/suit/S = wear_suit
+ var/no_taur_thanks = FALSE
+ if(!istype(S))
+ no_taur_thanks = TRUE
wear_suit.screen_loc = ui_oclothing
if(client && hud_used && hud_used.hud_shown)
if(hud_used.inventory_shown)
client.screen += wear_suit
update_observer_view(wear_suit,1)
- if(S.mutantrace_variation) //Just make sure we've got this checked too
- if(S.taurmode == NOT_TAURIC && S.adjusted == ALT_STYLE) //are we not a taur, but we have Digitigrade legs? Run this check first, then.
- S.alternate_worn_icon = 'modular_citadel/icons/mob/suit_digi.dmi'
- else
- S.alternate_worn_icon = null
-
- if(S.tauric == TRUE) //Are we a suit with tauric mode possible?
- if(S.taurmode == SNEK_TAURIC)
- S.alternate_worn_icon = 'modular_citadel/icons/mob/taur_naga.dmi'
- if(S.taurmode == PAW_TAURIC)
- S.alternate_worn_icon = 'modular_citadel/icons/mob/taur_canine.dmi'
- if(S.taurmode == NOT_TAURIC && S.adjusted == ALT_STYLE)
+ if(!S.force_alternate_icon)
+ if(!no_taur_thanks && S.mutantrace_variation) //Just make sure we've got this checked too
+ if(S.taurmode == NOT_TAURIC && S.adjusted == ALT_STYLE) //are we not a taur, but we have Digitigrade legs? Run this check first, then.
S.alternate_worn_icon = 'modular_citadel/icons/mob/suit_digi.dmi'
- else if(S.taurmode == NOT_TAURIC && S.adjusted == NORMAL_STYLE)
+ else
S.alternate_worn_icon = null
+ if(S.tauric == TRUE) //Are we a suit with tauric mode possible?
+ if(S.taurmode == SNEK_TAURIC)
+ S.alternate_worn_icon = 'modular_citadel/icons/mob/taur_naga.dmi'
+ if(S.taurmode == PAW_TAURIC)
+ S.alternate_worn_icon = 'modular_citadel/icons/mob/taur_canine.dmi'
+ if(S.taurmode == NOT_TAURIC && S.adjusted == ALT_STYLE)
+ S.alternate_worn_icon = 'modular_citadel/icons/mob/suit_digi.dmi'
+ else if(S.taurmode == NOT_TAURIC && S.adjusted == NORMAL_STYLE)
+ S.alternate_worn_icon = null
+
overlays_standing[SUIT_LAYER] = S.build_worn_icon(state = wear_suit.icon_state, default_layer = SUIT_LAYER, default_icon_file = ((wear_suit.alternate_worn_icon) ? S.alternate_worn_icon : 'icons/mob/suit.dmi'))
var/mutable_appearance/suit_overlay = overlays_standing[SUIT_LAYER]
if(OFFSET_SUIT in dna.species.offset_features)
suit_overlay.pixel_x += dna.species.offset_features[OFFSET_SUIT][1]
suit_overlay.pixel_y += dna.species.offset_features[OFFSET_SUIT][2]
- if(S.center)
+ if(!no_taur_thanks && S.center)
suit_overlay = center_image(suit_overlay, S.dimension_x, S.dimension_y)
overlays_standing[SUIT_LAYER] = suit_overlay
update_hair()
@@ -468,14 +469,6 @@ There are several things that need to be remembered:
overlays_standing[BACK_LAYER] = back_overlay
apply_overlay(BACK_LAYER)
-/mob/living/carbon/human/update_inv_legcuffed()
- remove_overlay(LEGCUFF_LAYER)
- clear_alert("legcuffed")
- if(legcuffed)
- overlays_standing[LEGCUFF_LAYER] = mutable_appearance('icons/mob/mob.dmi', "legcuff1", -LEGCUFF_LAYER)
- apply_overlay(LEGCUFF_LAYER)
- throw_alert("legcuffed", /obj/screen/alert/restrained/legcuffed, new_master = src.legcuffed)
-
/proc/wear_female_version(t_color, icon, layer, type)
var/index = t_color
var/icon/female_clothing_icon = GLOB.female_clothing_icons[index]
@@ -710,9 +703,9 @@ generate/load female uniform sprites matching all previously decided variables
if(lip_style && (LIPS in dna.species.species_traits))
var/mutable_appearance/lip_overlay = mutable_appearance('icons/mob/human_face.dmi', "lips_[lip_style]", -BODY_LAYER)
lip_overlay.color = lip_color
- if(OFFSET_FACE in dna.species.offset_features)
- lip_overlay.pixel_x += dna.species.offset_features[OFFSET_FACE][1]
- lip_overlay.pixel_y += dna.species.offset_features[OFFSET_FACE][2]
+ if(OFFSET_LIPS in dna.species.offset_features)
+ lip_overlay.pixel_x += dna.species.offset_features[OFFSET_LIPS][1]
+ lip_overlay.pixel_y += dna.species.offset_features[OFFSET_LIPS][2]
add_overlay(lip_overlay)
// eyes
@@ -725,9 +718,9 @@ generate/load female uniform sprites matching all previously decided variables
eye_overlay = mutable_appearance('icons/mob/human_face.dmi', "eyes", -BODY_LAYER)
if((EYECOLOR in dna.species.species_traits) && has_eyes)
eye_overlay.color = "#" + eye_color
- if(OFFSET_FACE in dna.species.offset_features)
- eye_overlay.pixel_x += dna.species.offset_features[OFFSET_FACE][1]
- eye_overlay.pixel_y += dna.species.offset_features[OFFSET_FACE][2]
+ if(OFFSET_EYES in dna.species.offset_features)
+ eye_overlay.pixel_x += dna.species.offset_features[OFFSET_EYES][1]
+ eye_overlay.pixel_y += dna.species.offset_features[OFFSET_EYES][2]
add_overlay(eye_overlay)
dna.species.handle_hair(src)
diff --git a/code/modules/mob/living/carbon/human/whisper.dm b/code/modules/mob/living/carbon/human/whisper.dm
deleted file mode 100644
index 65a4c5d33f..0000000000
--- a/code/modules/mob/living/carbon/human/whisper.dm
+++ /dev/null
@@ -1,91 +0,0 @@
-/mob/living/carbon/human/whisper_verb(message as text)
- whisper(message)
-
-/mob/living/carbon/human/whisper(message, datum/language/language=null)
- if(!IsVocal())
- return
- if(!message)
- return
- if(!language)
- language = get_default_language()
-
- if(GLOB.say_disabled) //This is here to try to identify lag problems
- to_chat(usr, "Speech is currently admin-disabled.")
- return
-
- if(stat == DEAD)
- return
-
-
- message = trim(html_encode(message))
- if(!can_speak(message))
- return
-
- message = "[message]"
- log_whisper("[src.name]/[src.key] : [message]")
-
- if (src.client)
- if (src.client.prefs.muted & MUTE_IC)
- to_chat(src, "You cannot whisper (muted).")
- return
-
- log_whisper("[src.name]/[src.key] : [message]")
-
- var/alt_name = get_alt_name()
-
- var/whispers = "whispers"
- var/critical = InCritical()
-
- // We are unconscious but not in critical, so don't allow them to whisper.
- if(stat == UNCONSCIOUS && !critical)
- return
-
- // If whispering your last words, limit the whisper based on how close you are to death.
- if(critical)
- var/health_diff = round(-HEALTH_THRESHOLD_DEAD + health)
- // If we cut our message short, abruptly end it with a-..
- var/message_len = length(message)
- message = copytext(message, 1, health_diff) + "[message_len > health_diff ? "-.." : "..."]"
- message = Ellipsis(message, 10, 1)
-
- message = treat_message(message)
- if(!message)
- return
-
- var/list/listening_dead = list()
- for(var/mob/M in GLOB.player_list)
- if(M.stat == DEAD && M.client && ((M.client.prefs.chat_toggles & CHAT_GHOSTWHISPER) || (get_dist(M, src) <= 7)))
- listening_dead |= M
-
- var/list/listening = get_hearers_in_view(1, src)
- listening |= listening_dead
- var/list/eavesdropping = get_hearers_in_view(2, src)
- eavesdropping -= listening
- var/list/watching = hearers(5, src)
- watching -= listening
- watching -= eavesdropping
-
- var/rendered
- whispers = critical ? "whispers something in [p_their()] final breath." : "whispers something."
- rendered = "[src.name] [whispers]"
- for(var/mob/M in watching)
- M.show_message(rendered, 2)
-
- var/spans = list(SPAN_ITALICS)
- whispers = critical ? "whispers in [p_their()] final breath" : "whispers"
- rendered = "[GetVoice()][alt_name] [whispers], \"[attach_spans(message, spans)]\""
-
- for(var/atom/movable/AM in listening)
- if(istype(AM,/obj/item/radio))
- continue
- AM.Hear(rendered, src, language, message, , spans)
-
- message = stars(message)
- rendered = "[GetVoice()][alt_name] [whispers], \"[attach_spans(message, spans)]\""
- for(var/atom/movable/AM in eavesdropping)
- if(istype(AM,/obj/item/radio))
- continue
- AM.Hear(rendered, src, language, message, , spans)
-
- if(critical) //Dying words.
- succumb(1)
diff --git a/code/modules/mob/living/carbon/life.dm b/code/modules/mob/living/carbon/life.dm
index b2eb83d668..5b18e95235 100644
--- a/code/modules/mob/living/carbon/life.dm
+++ b/code/modules/mob/living/carbon/life.dm
@@ -8,8 +8,8 @@
damageoverlaytemp = 0
update_damage_hud()
- if(stat != DEAD) //Reagent processing needs to come before breathing, to prevent edge cases.
- handle_organs()
+ //Reagent processing needs to come before breathing, to prevent edge cases.
+ handle_organs()
. = ..()
@@ -28,11 +28,14 @@
if(stat != DEAD)
handle_brain_damage()
+ /* BUG_PROBABLE_CAUSE
if(stat != DEAD)
handle_liver()
+ */
if(stat == DEAD)
stop_sound_channel(CHANNEL_HEARTBEAT)
+ handle_death()
rot()
//Updates the number of stored chemicals for powers
@@ -41,14 +44,34 @@
if(stat != DEAD)
return 1
+//Procs called while dead
+/mob/living/carbon/proc/handle_death()
+ for(var/datum/reagent/R in reagents.reagent_list)
+ if(R.chemical_flags & REAGENT_DEAD_PROCESS)
+ R.on_mob_dead(src)
+
///////////////
// BREATHING //
///////////////
//Start of a breath chain, calls breathe()
/mob/living/carbon/handle_breathing(times_fired)
- if((times_fired % 4) == 2 || failed_last_breath)
- breathe() //Breathe per 4 ticks, unless suffocating
+ var/next_breath = 4
+ var/obj/item/organ/lungs/L = getorganslot(ORGAN_SLOT_LUNGS)
+ var/obj/item/organ/heart/H = getorganslot(ORGAN_SLOT_HEART)
+ if(L)
+ if(L.damage > L.high_threshold)
+ next_breath--
+ if(H)
+ if(H.damage > H.high_threshold)
+ next_breath--
+
+ if((times_fired % next_breath) == 0 || failed_last_breath)
+ breathe() //Breathe per 4 ticks if healthy, down to 2 if our lungs or heart are damaged, unless suffocating
+ if(failed_last_breath)
+ SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "suffocation", /datum/mood_event/suffocation)
+ else
+ SEND_SIGNAL(src, COMSIG_CLEAR_MOOD_EVENT, "suffocation")
else
if(istype(loc, /obj/))
var/obj/location_as_object = loc
@@ -56,6 +79,7 @@
//Second link in a breath chain, calls check_breath()
/mob/living/carbon/proc/breathe()
+ var/obj/item/organ/lungs = getorganslot(ORGAN_SLOT_LUNGS)
if(reagents.has_reagent("lexorin"))
return
if(istype(loc, /obj/machinery/atmospherics/components/unary/cryo_cell))
@@ -74,7 +98,7 @@
var/datum/gas_mixture/breath
if(!getorganslot(ORGAN_SLOT_BREATHING_TUBE))
- if(health <= HEALTH_THRESHOLD_FULLCRIT || (pulledby && pulledby.grab_state >= GRAB_KILL))
+ if(health <= HEALTH_THRESHOLD_FULLCRIT || (pulledby && pulledby.grab_state >= GRAB_KILL) || lungs.organ_flags & ORGAN_FAILING)
losebreath++ //You can't breath at all when in critical or when being choked, so you're going to miss a breath
else if(health <= crit_threshold)
@@ -126,7 +150,7 @@
if((status_flags & GODMODE))
return
- var/lungs = getorganslot(ORGAN_SLOT_LUNGS)
+ var/obj/item/organ/lungs = getorganslot(ORGAN_SLOT_LUNGS)
if(!lungs)
adjustOxyLoss(2)
@@ -228,6 +252,9 @@
else if(SA_partialpressure > 0.01)
if(prob(20))
emote(pick("giggle","laugh"))
+ SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "chemical_euphoria", /datum/mood_event/chemical_euphoria)
+ else
+ SEND_SIGNAL(src, COMSIG_CLEAR_MOOD_EVENT, "chemical_euphoria")
//BZ (Facepunch port of their Agent B)
if(breath_gases[/datum/gas/bz])
@@ -250,38 +277,39 @@
//MIASMA
if(breath_gases[/datum/gas/miasma])
var/miasma_partialpressure = (breath_gases[/datum/gas/miasma]/breath.total_moles())*breath_pressure
+ if(miasma_partialpressure > MINIMUM_MOLES_DELTA_TO_MOVE)
- if(prob(1 * miasma_partialpressure))
- var/datum/disease/advance/miasma_disease = new /datum/disease/advance/random(2,3)
- miasma_disease.name = "Unknown"
- ForceContractDisease(miasma_disease, TRUE, TRUE)
+ if(prob(0.05 * miasma_partialpressure))
+ var/datum/disease/advance/miasma_disease = new /datum/disease/advance/random(2,3)
+ miasma_disease.name = "Unknown"
+ ForceContractDisease(miasma_disease, TRUE, TRUE)
- //Miasma side effects
- switch(miasma_partialpressure)
- if(1 to 5)
- // At lower pp, give out a little warning
- SEND_SIGNAL(src, COMSIG_CLEAR_MOOD_EVENT, "smell")
- if(prob(5))
- to_chat(src, "There is an unpleasant smell in the air.")
- if(5 to 20)
- //At somewhat higher pp, warning becomes more obvious
- if(prob(15))
- to_chat(src, "You smell something horribly decayed inside this room.")
- SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "smell", /datum/mood_event/disgust/bad_smell)
- if(15 to 30)
- //Small chance to vomit. By now, people have internals on anyway
- if(prob(5))
- to_chat(src, "The stench of rotting carcasses is unbearable!")
- SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "smell", /datum/mood_event/disgust/nauseating_stench)
- vomit()
- if(30 to INFINITY)
- //Higher chance to vomit. Let the horror start
- if(prob(25))
- to_chat(src, "The stench of rotting carcasses is unbearable!")
- SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "smell", /datum/mood_event/disgust/nauseating_stench)
- vomit()
- else
- SEND_SIGNAL(src, COMSIG_CLEAR_MOOD_EVENT, "smell")
+ //Miasma side effects
+ switch(miasma_partialpressure)
+ if(1 to 5)
+ // At lower pp, give out a little warning
+ SEND_SIGNAL(src, COMSIG_CLEAR_MOOD_EVENT, "smell")
+ if(prob(5))
+ to_chat(src, "There is an unpleasant smell in the air.")
+ if(5 to 20)
+ //At somewhat higher pp, warning becomes more obvious
+ if(prob(15))
+ to_chat(src, "You smell something horribly decayed inside this room.")
+ SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "smell", /datum/mood_event/disgust/bad_smell)
+ if(15 to 30)
+ //Small chance to vomit. By now, people have internals on anyway
+ if(prob(5))
+ to_chat(src, "The stench of rotting carcasses is unbearable!")
+ SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "smell", /datum/mood_event/disgust/nauseating_stench)
+ vomit()
+ if(30 to INFINITY)
+ //Higher chance to vomit. Let the horror start
+ if(prob(25))
+ to_chat(src, "The stench of rotting carcasses is unbearable!")
+ SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "smell", /datum/mood_event/disgust/nauseating_stench)
+ vomit()
+ else
+ SEND_SIGNAL(src, COMSIG_CLEAR_MOOD_EVENT, "smell")
//Clear all moods if no miasma at all
@@ -303,11 +331,17 @@
return
/mob/living/carbon/proc/get_breath_from_internal(volume_needed)
+ var/obj/item/clothing/check
+ var/internals = FALSE
+
+ for(check in GET_INTERNAL_SLOTS(src))
+ if(CHECK_BITFIELD(check.clothing_flags, ALLOWINTERNALS))
+ internals = TRUE
if(internal)
if(internal.loc != src)
internal = null
update_internals_hud_icon(0)
- else if ((!wear_mask || !(wear_mask.clothing_flags & MASKINTERNALS)) && !getorganslot(ORGAN_SLOT_BREATHING_TUBE))
+ else if (!internals && !getorganslot(ORGAN_SLOT_BREATHING_TUBE))
internal = null
update_internals_hud_icon(0)
else
@@ -344,7 +378,7 @@
var/list/cached_gases = miasma_turf.air.gases
- cached_gases[/datum/gas/miasma] += 0.02
+ cached_gases[/datum/gas/miasma] += 0.1
/mob/living/carbon/proc/handle_blood()
return
@@ -356,9 +390,16 @@
. |= BP.on_life()
/mob/living/carbon/proc/handle_organs()
- for(var/V in internal_organs)
- var/obj/item/organ/O = V
- O.on_life()
+ if(stat != DEAD)
+ for(var/V in internal_organs)
+ var/obj/item/organ/O = V
+ if(O)
+ O.on_life()
+ else
+ for(var/V in internal_organs)
+ var/obj/item/organ/O = V
+ if(O)
+ O.on_death() //Needed so organs decay while inside the body.
/mob/living/carbon/handle_diseases()
for(var/thing in diseases)
@@ -465,7 +506,7 @@ GLOBAL_LIST_INIT(ballmer_windows_me_msg, list("Yo man, what if, we like, uh, put
/mob/living/carbon/handle_status_effects()
..()
if(getStaminaLoss() && !combatmode)//CIT CHANGE - prevents stamina regen while combat mode is active
- adjustStaminaLoss(resting ? (recoveringstam ? -7.5 : -3) : -1.5)//CIT CHANGE - decreases adjuststaminaloss to stop stamina damage from being such a joke
+ adjustStaminaLoss(resting ? (recoveringstam ? -7.5 : -6) : -3)//CIT CHANGE - decreases adjuststaminaloss to stop stamina damage from being such a joke
if(!recoveringstam && incomingstammult != 1)
incomingstammult = max(0.01, incomingstammult)
@@ -525,6 +566,9 @@ GLOBAL_LIST_INIT(ballmer_windows_me_msg, list("Yo man, what if, we like, uh, put
if(jitteriness)
do_jitter_animation(jitteriness)
jitteriness = max(jitteriness - restingpwr, 0)
+ SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "jittery", /datum/mood_event/jittery)
+ else
+ SEND_SIGNAL(src, COMSIG_CLEAR_MOOD_EVENT, "jittery")
if(stuttering)
stuttering = max(stuttering-1, 0)
@@ -600,7 +644,7 @@ GLOBAL_LIST_INIT(ballmer_windows_me_msg, list("Yo man, what if, we like, uh, put
to_chat(src, "Maybe you should lie down for a bit...")
if(drunkenness >= 91)
- adjustBrainLoss(0.4, 60)
+ adjustOrganLoss(ORGAN_SLOT_BRAIN, 0.4, 60)
if(prob(20) && !stat)
if(SSshuttle.emergency.mode == SHUTTLE_DOCKED && is_station_level(z)) //QoL mainly
to_chat(src, "You're so tired... but you can't miss that shuttle...")
@@ -610,6 +654,8 @@ GLOBAL_LIST_INIT(ballmer_windows_me_msg, list("Yo man, what if, we like, uh, put
if(drunkenness >= 101)
adjustToxLoss(4) //Let's be honest you shouldn't be alive by now
+ else
+ SEND_SIGNAL(src, COMSIG_CLEAR_MOOD_EVENT, "drunk")
//used in human and monkey handle_environment()
/mob/living/carbon/proc/natural_bodytemperature_stabilization()
@@ -632,8 +678,8 @@ GLOBAL_LIST_INIT(ballmer_windows_me_msg, list("Yo man, what if, we like, uh, put
if((!dna && !liver) || (NOLIVER in dna.species.species_traits))
return
if(liver)
- if(liver.damage >= liver.maxHealth)
- liver.failing = TRUE
+ if(liver.damage < liver.maxHealth)
+ liver.organ_flags |= ORGAN_FAILING
liver_failure()
else
liver_failure()
@@ -656,7 +702,7 @@ GLOBAL_LIST_INIT(ballmer_windows_me_msg, list("Yo man, what if, we like, uh, put
/mob/living/carbon/proc/liver_failure()
reagents.end_metabolization(src, keep_liverless = TRUE) //Stops trait-based effects on reagents, to prevent permanent buffs
reagents.metabolize(src, can_overdose=FALSE, liverless = TRUE)
- if(HAS_TRAIT(src, TRAIT_STABLEHEART))
+ if(HAS_TRAIT(src, TRAIT_STABLELIVER))
return
adjustToxLoss(4, TRUE, TRUE)
if(prob(30))
@@ -672,13 +718,6 @@ GLOBAL_LIST_INIT(ballmer_windows_me_msg, list("Yo man, what if, we like, uh, put
var/datum/brain_trauma/BT = T
BT.on_life()
- if(getBrainLoss() >= BRAIN_DAMAGE_DEATH) //rip
- to_chat(src, "The last spark of life in your brain fizzles out...")
- death()
- var/obj/item/organ/brain/B = getorganslot(ORGAN_SLOT_BRAIN)
- if(B)
- B.damaged_brain = TRUE
-
/////////////////////////////////////
//MONKEYS WITH TOO MUCH CHOLOESTROL//
/////////////////////////////////////
@@ -687,7 +726,7 @@ GLOBAL_LIST_INIT(ballmer_windows_me_msg, list("Yo man, what if, we like, uh, put
if(!needs_heart())
return FALSE
var/obj/item/organ/heart/heart = getorganslot(ORGAN_SLOT_HEART)
- if(!heart || heart.synthetic)
+ if(!heart || (heart.organ_flags & ORGAN_SYNTHETIC))
return FALSE
return TRUE
diff --git a/code/modules/mob/living/carbon/monkey/combat.dm b/code/modules/mob/living/carbon/monkey/combat.dm
index 85436178fe..25bc243f07 100644
--- a/code/modules/mob/living/carbon/monkey/combat.dm
+++ b/code/modules/mob/living/carbon/monkey/combat.dm
@@ -115,7 +115,7 @@
/mob/living/carbon/monkey/proc/handle_combat()
if(pickupTarget)
- if(restrained() || blacklistItems[pickupTarget] || (pickupTarget.item_flags & NODROP))
+ if(restrained() || blacklistItems[pickupTarget] || HAS_TRAIT(pickupTarget, TRAIT_NODROP))
pickupTarget = null
else
pickupTimer++
diff --git a/code/modules/mob/living/carbon/monkey/inventory.dm b/code/modules/mob/living/carbon/monkey/inventory.dm
index fdc28d13a0..d5fffc70a2 100644
--- a/code/modules/mob/living/carbon/monkey/inventory.dm
+++ b/code/modules/mob/living/carbon/monkey/inventory.dm
@@ -1,4 +1,4 @@
-/mob/living/carbon/monkey/can_equip(obj/item/I, slot, disable_warning = 0)
+/mob/living/carbon/monkey/can_equip(obj/item/I, slot, disable_warning = FALSE, bypass_equip_delay_self = FALSE)
switch(slot)
if(SLOT_HANDS)
if(get_empty_held_indexes())
diff --git a/code/modules/mob/living/carbon/monkey/monkey.dm b/code/modules/mob/living/carbon/monkey/monkey.dm
index f1a6b58cd1..f9c2e2dd3d 100644
--- a/code/modules/mob/living/carbon/monkey/monkey.dm
+++ b/code/modules/mob/living/carbon/monkey/monkey.dm
@@ -152,15 +152,6 @@
return threatcount
-/mob/living/carbon/monkey/get_permeability_protection()
- var/protection = 0
- if(head)
- protection = 1 - head.permeability_coefficient
- if(wear_mask)
- protection = max(1 - wear_mask.permeability_coefficient, protection)
- protection = protection/7 //the rest of the body isn't covered.
- return protection
-
/mob/living/carbon/monkey/IsVocal()
if(!getorganslot(ORGAN_SLOT_LUNGS))
return 0
diff --git a/code/modules/mob/living/carbon/monkey/update_icons.dm b/code/modules/mob/living/carbon/monkey/update_icons.dm
index 6311776596..e9bb9fc207 100644
--- a/code/modules/mob/living/carbon/monkey/update_icons.dm
+++ b/code/modules/mob/living/carbon/monkey/update_icons.dm
@@ -43,12 +43,15 @@
/mob/living/carbon/monkey/update_inv_legcuffed()
remove_overlay(LEGCUFF_LAYER)
+ clear_alert("legcuffed")
if(legcuffed)
- var/mutable_appearance/legcuff_overlay = mutable_appearance('icons/mob/mob.dmi', "legcuff1", -LEGCUFF_LAYER)
- legcuff_overlay.pixel_y = 8
- overlays_standing[LEGCUFF_LAYER] = legcuff_overlay
- apply_overlay(LEGCUFF_LAYER)
+ var/mutable_appearance/legcuffs = mutable_appearance('icons/mob/restraints.dmi', legcuffed.item_state, -LEGCUFF_LAYER)
+ legcuffs.color = handcuffed.color
+ legcuffs.pixel_y = 8
+ overlays_standing[HANDCUFF_LAYER] = legcuffs
+ apply_overlay(LEGCUFF_LAYER)
+ throw_alert("legcuffed", /obj/screen/alert/restrained/legcuffed, new_master = legcuffed)
//monkey HUD updates for items in our inventory
diff --git a/code/modules/mob/living/carbon/say.dm b/code/modules/mob/living/carbon/say.dm
index c52b827964..452c8f8b78 100644
--- a/code/modules/mob/living/carbon/say.dm
+++ b/code/modules/mob/living/carbon/say.dm
@@ -1,37 +1,17 @@
-/mob/living/carbon/treat_message(message)
- for(var/datum/brain_trauma/trauma in get_traumas())
- message = trauma.on_say(message)
- message = ..(message)
- var/obj/item/organ/tongue/T = getorganslot(ORGAN_SLOT_TONGUE)
- if(!T) //hoooooouaah!
- var/regex/tongueless_lower = new("\[gdntke]+", "g")
- var/regex/tongueless_upper = new("\[GDNTKE]+", "g")
- if(copytext(message, 1, 2) != "*")
- message = tongueless_lower.Replace(message, pick("aa","oo","'"))
- message = tongueless_upper.Replace(message, pick("AA","OO","'"))
- else
- message = T.TongueSpeech(message)
- if(wear_mask)
- message = wear_mask.speechModification(message)
- if(head)
- message = head.speechModification(message)
- return message
+/mob/living/carbon/proc/handle_tongueless_speech(mob/living/carbon/speaker, list/speech_args)
+ var/message = speech_args[SPEECH_MESSAGE]
+ var/static/regex/tongueless_lower = new("\[gdntke]+", "g")
+ var/static/regex/tongueless_upper = new("\[GDNTKE]+", "g")
+ if(message[1] != "*")
+ message = tongueless_lower.Replace(message, pick("aa","oo","'"))
+ message = tongueless_upper.Replace(message, pick("AA","OO","'"))
+ speech_args[SPEECH_MESSAGE] = message
/mob/living/carbon/can_speak_vocal(message)
if(silent)
return 0
return ..()
-/mob/living/carbon/get_spans()
- . = ..()
- var/obj/item/organ/tongue/T = getorganslot(ORGAN_SLOT_TONGUE)
- if(T)
- . |= T.get_spans()
-
- var/obj/item/I = get_active_held_item()
- if(I)
- . |= I.get_held_item_speechspans(src)
-
/mob/living/carbon/could_speak_in_language(datum/language/dt)
var/obj/item/organ/tongue/T = getorganslot(ORGAN_SLOT_TONGUE)
if(T)
@@ -48,12 +28,7 @@
message = trauma.on_hear(message, speaker, message_language, raw_message, radio_freq)
if (src.mind.has_antag_datum(/datum/antagonist/traitor))
- for (var/codeword in GLOB.syndicate_code_phrase)
- var/regex/codeword_match = new("([codeword])", "ig")
- message = codeword_match.Replace(message, "$1")
-
- for (var/codeword in GLOB.syndicate_code_response)
- var/regex/codeword_match = new("([codeword])", "ig")
- message = codeword_match.Replace(message, "$1")
+ message = GLOB.syndicate_code_phrase_regex.Replace(message, "$1")
+ message = GLOB.syndicate_code_response_regex.Replace(message, "$1")
return message
diff --git a/code/modules/mob/living/carbon/update_icons.dm b/code/modules/mob/living/carbon/update_icons.dm
index 87bf662c4f..cdae073af8 100644
--- a/code/modules/mob/living/carbon/update_icons.dm
+++ b/code/modules/mob/living/carbon/update_icons.dm
@@ -176,9 +176,22 @@
/mob/living/carbon/update_inv_handcuffed()
remove_overlay(HANDCUFF_LAYER)
if(handcuffed)
- overlays_standing[HANDCUFF_LAYER] = mutable_appearance('icons/mob/mob.dmi', "handcuff1", -HANDCUFF_LAYER)
+ var/mutable_appearance/cuffs = mutable_appearance('icons/mob/restraints.dmi', handcuffed.item_state, -HANDCUFF_LAYER)
+ cuffs.color = handcuffed.color
+
+ overlays_standing[HANDCUFF_LAYER] = cuffs
apply_overlay(HANDCUFF_LAYER)
+/mob/living/carbon/update_inv_legcuffed()
+ remove_overlay(LEGCUFF_LAYER)
+ clear_alert("legcuffed")
+ if(legcuffed)
+ var/mutable_appearance/legcuffs = mutable_appearance('icons/mob/restraints.dmi', legcuffed.item_state, -LEGCUFF_LAYER)
+ legcuffs.color = legcuffed.color
+
+ overlays_standing[LEGCUFF_LAYER] = legcuffs
+ apply_overlay(LEGCUFF_LAYER)
+ throw_alert("legcuffed", /obj/screen/alert/restrained/legcuffed, new_master = legcuffed)
//mob HUD updates for items in our inventory
diff --git a/code/modules/mob/living/damage_procs.dm b/code/modules/mob/living/damage_procs.dm
index b2eed2d19e..563ab7cfb6 100644
--- a/code/modules/mob/living/damage_procs.dm
+++ b/code/modules/mob/living/damage_procs.dm
@@ -25,8 +25,6 @@
adjustCloneLoss(damage * hit_percent)
if(STAMINA)
adjustStaminaLoss(damage * hit_percent)
- if(BRAIN)
- adjustBrainLoss(damage * hit_percent)
return 1
/mob/living/proc/apply_damage_type(damage = 0, damagetype = BRUTE) //like apply damage except it always uses the damage procs
@@ -43,8 +41,6 @@
return adjustCloneLoss(damage)
if(STAMINA)
return adjustStaminaLoss(damage)
- if(BRAIN)
- return adjustBrainLoss(damage)
/mob/living/proc/get_damage_amount(damagetype = BRUTE)
switch(damagetype)
@@ -60,8 +56,6 @@
return getCloneLoss()
if(STAMINA)
return getStaminaLoss()
- if(BRAIN)
- return getBrainLoss()
/mob/living/proc/apply_damages(brute = 0, burn = 0, tox = 0, oxy = 0, clone = 0, def_zone = null, blocked = FALSE, stamina = 0, brain = 0)
@@ -218,13 +212,13 @@
updatehealth()
return amount
-/mob/living/proc/getBrainLoss()
- . = 0
-
-/mob/living/proc/adjustBrainLoss(amount, maximum = BRAIN_DAMAGE_DEATH)
+/mob/living/proc/adjustOrganLoss(slot, amount, maximum)
return
-/mob/living/proc/setBrainLoss(amount)
+/mob/living/proc/setOrganLoss(slot, amount, maximum)
+ return
+
+/mob/living/proc/getOrganLoss(slot)
return
/mob/living/proc/getStaminaLoss()
diff --git a/code/modules/mob/living/death.dm b/code/modules/mob/living/death.dm
index f16572c5d0..f7dec3272c 100644
--- a/code/modules/mob/living/death.dm
+++ b/code/modules/mob/living/death.dm
@@ -13,6 +13,10 @@
if(!no_bodyparts)
spread_bodyparts(no_brain, no_organs)
+ for(var/X in implants)
+ var/obj/item/implant/I = X
+ qdel(I)
+
spawn_gibs(no_bodyparts)
qdel(src)
diff --git a/code/modules/mob/living/emote.dm b/code/modules/mob/living/emote.dm
index 25d8c4d44c..df711a6dca 100644
--- a/code/modules/mob/living/emote.dm
+++ b/code/modules/mob/living/emote.dm
@@ -255,7 +255,7 @@
H.Knockdown(20)
else
message_param = "bumps [user.p_their()] head on the ground trying to motion towards %t."
- H.adjustBrainLoss(5)
+ H.adjustOrganLoss(ORGAN_SLOT_BRAIN, 5)
..()
/datum/emote/living/pout
diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm
index b844a294be..812733ebe2 100644
--- a/code/modules/mob/living/living.dm
+++ b/code/modules/mob/living/living.dm
@@ -281,12 +281,14 @@
var/datum/disease/D = thing
if(D.spread_flags & DISEASE_SPREAD_CONTACT_SKIN)
ContactContractDisease(D)
-
+
if(iscarbon(L))
var/mob/living/carbon/C = L
if(HAS_TRAIT(src, TRAIT_STRONG_GRABBER))
C.grippedby(src)
+ update_pull_movespeed()
+
//mob verbs are a lot faster than object verbs
//for more info on why this is not atom/pull, see examinate() in mob.dm
/mob/living/verb/pulled(atom/movable/AM as mob|obj in oview(1))
@@ -300,6 +302,7 @@
/mob/living/stop_pulling()
..()
+ update_pull_movespeed()
update_pull_hud_icon()
/mob/living/verb/stop_pulling1()
@@ -318,21 +321,36 @@
visible_message("[src] points at [A].", "You point at [A].")
return TRUE
-/mob/living/verb/succumb(whispered as null)
+/mob/living/verb/succumb()
set name = "Succumb"
set category = "IC"
+ if(src.has_status_effect(/datum/status_effect/chem/enthrall))
+ var/datum/status_effect/chem/enthrall/E = src.has_status_effect(/datum/status_effect/chem/enthrall)
+ if(E.phase < 3)
+ if(HAS_TRAIT(src, TRAIT_MINDSHIELD))
+ to_chat(src, "Your mindshield prevents your mind from giving in!")
+ else if(src.mind.assigned_role in GLOB.command_positions)
+ to_chat(src, "Your dedication to your department prevents you from giving in!")
+ else
+ E.enthrallTally += 20
+ to_chat(src, "You give into [E.master]'s influence.")
if (InCritical())
- log_message("Has [whispered ? "whispered his final words" : "succumbed to death"] while in [InFullCritical() ? "hard":"soft"] critical with [round(health, 0.1)] points of health!", LOG_ATTACK)
+ log_message("Has succumbed to death while in [InFullCritical() ? "hard":"soft"] critical with [round(health, 0.1)] points of health!", LOG_ATTACK)
adjustOxyLoss(health - HEALTH_THRESHOLD_DEAD)
updatehealth()
- if(!whispered)
- to_chat(src, "You have given up life and succumbed to death.")
+ to_chat(src, "You have given up life and succumbed to death.")
death()
+
/mob/living/incapacitated(ignore_restraints, ignore_grab)
if(stat || IsUnconscious() || IsStun() || IsKnockdown() || recoveringstam || (!ignore_restraints && restrained(ignore_grab))) // CIT CHANGE - adds recoveringstam check here
return TRUE
+/mob/living/canUseStorage()
+ if (get_num_arms() <= 0)
+ return FALSE
+ return TRUE
+
/mob/living/proc/InCritical()
return (health <= crit_threshold && (stat == SOFT_CRIT || stat == UNCONSCIOUS))
@@ -463,7 +481,6 @@
setToxLoss(0, 0) //zero as second argument not automatically call updatehealth().
setOxyLoss(0, 0)
setCloneLoss(0, 0)
- setBrainLoss(0)
setStaminaLoss(0, 0)
SetUnconscious(0, FALSE)
set_disgust(0)
@@ -490,6 +507,13 @@
QDEL_LIST_ASSOC_VAL(mood.mood_events)
mood.sanity = SANITY_GREAT
mood.update_mood()
+ //Heal all organs
+ if(iscarbon(src))
+ var/mob/living/carbon/C = src
+ if(C.internal_organs)
+ for(var/organ in C.internal_organs)
+ var/obj/item/organ/O = organ
+ O.setOrganDamage(0)
//proc called by revive(), to check if we can actually ressuscitate the mob (we don't want to revive him and have him instantly die again)
@@ -510,6 +534,10 @@
var/old_direction = dir
var/turf/T = loc
+
+ if(pulling)
+ update_pull_movespeed()
+
. = ..()
if(pulledby && moving_diagonally != FIRST_DIAG_STEP && get_dist(src, pulledby) > 1)//separated from our puller and not in the middle of a diagonal move.
@@ -532,7 +560,7 @@
var/trail_type = getTrail()
if(trail_type)
var/brute_ratio = round(getBruteLoss() / maxHealth, 0.1)
- if(blood_volume && blood_volume > max(BLOOD_VOLUME_NORMAL*(1 - brute_ratio * 0.25), 0))//don't leave trail if blood volume below a threshold
+ if(blood_volume && blood_volume > max((BLOOD_VOLUME_NORMAL*blood_ratio)*(1 - brute_ratio * 0.25), 0))//don't leave trail if blood volume below a threshold
blood_volume = max(blood_volume - max(1, brute_ratio * 2), 0) //that depends on our brute damage.
var/newdir = get_dir(target_turf, start)
if(newdir != direction)
@@ -626,14 +654,15 @@
else if(canmove)
if(on_fire)
resist_fire() //stop, drop, and roll
- else if(resting) //cit change - allows resisting out of resting
+ return
+ if(resting) //cit change - allows resisting out of resting
resist_a_rest() // ditto
- else if(iscarbon(src)) //Citadel Change for embedded removal memes
- var/mob/living/carbon/C = src
- if(!C.handcuffed && !C.legcuffed)
- return TRUE
- else if(last_special <= world.time)
+ return
+ if(resist_embedded()) //Citadel Change for embedded removal memes
+ return
+ if(last_special <= world.time)
resist_restraints() //trying to remove cuffs.
+ return
/mob/proc/resist_grab(moving_resist)
@@ -699,7 +728,7 @@
// The src mob is trying to strip an item from someone
// Override if a certain type of mob should be behave differently when stripping items (can't, for example)
/mob/living/stripPanelUnequip(obj/item/what, mob/who, where)
- if(what.item_flags & NODROP)
+ if(HAS_TRAIT(what, TRAIT_NODROP))
to_chat(src, "You can't remove \the [what.name], it appears to be stuck!")
return
who.visible_message("[src] tries to remove [who]'s [what.name].", \
@@ -724,7 +753,7 @@
// Override if a certain mob should be behave differently when placing items (can't, for example)
/mob/living/stripPanelEquip(obj/item/what, mob/who, where)
what = src.get_active_held_item()
- if(what && (what.item_flags & NODROP))
+ if(what && HAS_TRAIT(what, TRAIT_NODROP))
to_chat(src, "You can't put \the [what.name] on [who], it's stuck to your hand!")
return
if(what)
@@ -813,7 +842,7 @@
return 1
//used in datum/reagents/reaction() proc
-/mob/living/proc/get_permeability_protection()
+/mob/living/proc/get_permeability_protection(list/target_zones)
return 0
/mob/living/proc/harvest(mob/living/user) //used for extra objects etc. in butchering
@@ -883,7 +912,7 @@
if(mind)
mind.transfer_to(new_mob)
else
- new_mob.key = key
+ transfer_ckey(new_mob)
for(var/para in hasparasites())
var/mob/living/simple_animal/hostile/guardian/G = para
@@ -1012,6 +1041,9 @@
stop_pulling() //CIT CHANGE - Ditto...
else if(has_legs || ignore_legs)
lying = 0
+ if (pulledby)
+ var/mob/living/L = pulledby
+ L.update_pull_movespeed()
if(buckled)
lying = 90*buckle_lying
else if(!lying)
diff --git a/code/modules/mob/living/living_defense.dm b/code/modules/mob/living/living_defense.dm
index e434bc4e95..9d04f288cd 100644
--- a/code/modules/mob/living/living_defense.dm
+++ b/code/modules/mob/living/living_defense.dm
@@ -153,7 +153,7 @@
to_chat(user, "[src] can't be grabbed more aggressively!")
return FALSE
- if(HAS_TRAIT(user, TRAIT_PACIFISM))
+ if(user.grab_state >= GRAB_AGGRESSIVE && HAS_TRAIT(user, TRAIT_PACIFISM))
to_chat(user, "You don't want to risk hurting [src]!")
return FALSE
@@ -184,11 +184,17 @@
user.grab_state++
switch(user.grab_state)
if(GRAB_AGGRESSIVE)
- log_combat(user, src, "grabbed", addition="aggressive grab")
- visible_message("[user] has grabbed [src] aggressively!", \
- "[user] has grabbed [src] aggressively!")
- drop_all_held_items()
+ var/add_log = ""
+ if(HAS_TRAIT(user, TRAIT_PACIFISM))
+ visible_message("[user] has firmly gripped [src]!",
+ "[user] has firmly gripped you!")
+ add_log = " (pacifist)"
+ else
+ visible_message("[user] has grabbed [src] aggressively!", \
+ "[user] has grabbed you aggressively!")
+ drop_all_held_items()
stop_pulling()
+ log_combat(user, src, "grabbed", addition="aggressive grab[add_log]")
if(GRAB_NECK)
log_combat(user, src, "grabbed", addition="neck grab")
visible_message("[user] has grabbed [src] by the neck!",\
diff --git a/code/modules/mob/living/living_defines.dm b/code/modules/mob/living/living_defines.dm
index 3b0af53866..4d2a36907d 100644
--- a/code/modules/mob/living/living_defines.dm
+++ b/code/modules/mob/living/living_defines.dm
@@ -2,7 +2,7 @@
see_invisible = SEE_INVISIBLE_LIVING
sight = 0
see_in_dark = 2
- hud_possible = list(HEALTH_HUD,STATUS_HUD,ANTAG_HUD,NANITE_HUD,DIAG_NANITE_FULL_HUD)
+ hud_possible = list(HEALTH_HUD,STATUS_HUD,ANTAG_HUD,NANITE_HUD,DIAG_NANITE_FULL_HUD,RAD_HUD)
pressure_resistance = 10
var/resize = 1 //Badminnery resize
@@ -77,6 +77,7 @@
var/stun_absorption = null //converted to a list of stun absorption sources this mob has when one is added
var/blood_volume = 0 //how much blood the mob has
+ var/blood_ratio = 1 //How much blood the mob needs, in terms of ratio (i.e 1.2 will require BLOOD_VOLUME_NORMAL of 672) DO NOT GO ABOVE 3.55 Well, actually you can but, then they can't get enough blood.
var/obj/effect/proc_holder/ranged_ability //Any ranged ability the mob has, as a click override
var/see_override = 0 //0 for no override, sets see_invisible = see_override in silicon & carbon life process via update_sight()
@@ -109,3 +110,5 @@
//List of active diseases
var/list/diseases = list() // list of all diseases in a mob
var/list/disease_resistances = list()
+
+ var/drag_slowdown = TRUE //Whether the mob is slowed down when dragging another prone mob
\ No newline at end of file
diff --git a/code/modules/mob/living/living_movement.dm b/code/modules/mob/living/living_movement.dm
index 9566edc2ed..1ee563bc1f 100644
--- a/code/modules/mob/living/living_movement.dm
+++ b/code/modules/mob/living/living_movement.dm
@@ -25,3 +25,11 @@
add_movespeed_modifier(MOVESPEED_ID_LIVING_TURF_SPEEDMOD, TRUE, 100, override = TRUE, multiplicative_slowdown = T.slowdown)
else
remove_movespeed_modifier(MOVESPEED_ID_LIVING_TURF_SPEEDMOD)
+
+/mob/living/proc/update_pull_movespeed()
+ if(pulling && isliving(pulling))
+ var/mob/living/L = pulling
+ if(drag_slowdown && L.lying && !L.buckled && grab_state < GRAB_AGGRESSIVE)
+ add_movespeed_modifier(MOVESPEED_ID_PRONE_DRAGGING, multiplicative_slowdown = PULL_PRONE_SLOWDOWN)
+ return
+ remove_movespeed_modifier(MOVESPEED_ID_PRONE_DRAGGING)
\ No newline at end of file
diff --git a/code/modules/mob/living/say.dm b/code/modules/mob/living/say.dm
index 160e596882..5664c2ebca 100644
--- a/code/modules/mob/living/say.dm
+++ b/code/modules/mob/living/say.dm
@@ -2,61 +2,61 @@ GLOBAL_LIST_INIT(department_radio_prefixes, list(":", "."))
GLOBAL_LIST_INIT(department_radio_keys, list(
// Location
- "r" = "right hand",
- "l" = "left hand",
- "i" = "intercom",
+ MODE_KEY_R_HAND = MODE_R_HAND,
+ MODE_KEY_L_HAND = MODE_L_HAND,
+ MODE_KEY_INTERCOM = MODE_INTERCOM,
// Department
- "h" = "department",
- "c" = "Command",
- "n" = "Science",
- "m" = "Medical",
- "e" = "Engineering",
- "s" = "Security",
- "u" = "Supply",
- "v" = "Service",
+ MODE_KEY_DEPARTMENT = MODE_DEPARTMENT,
+ RADIO_KEY_COMMAND = RADIO_CHANNEL_COMMAND,
+ RADIO_KEY_SCIENCE = RADIO_CHANNEL_SCIENCE,
+ RADIO_KEY_MEDICAL = RADIO_CHANNEL_MEDICAL,
+ RADIO_KEY_ENGINEERING = RADIO_CHANNEL_ENGINEERING,
+ RADIO_KEY_SECURITY = RADIO_CHANNEL_SECURITY,
+ RADIO_KEY_SUPPLY = RADIO_CHANNEL_SUPPLY,
+ RADIO_KEY_SERVICE = RADIO_CHANNEL_SERVICE,
// Faction
- "t" = "Syndicate",
- "y" = "CentCom",
+ RADIO_KEY_SYNDICATE = RADIO_CHANNEL_SYNDICATE,
+ RADIO_KEY_CENTCOM = RADIO_CHANNEL_CENTCOM,
// Admin
- "p" = "admin",
- "d" = "deadmin",
+ MODE_KEY_ADMIN = MODE_ADMIN,
+ MODE_KEY_DEADMIN = MODE_DEADMIN,
// Misc
- "o" = "AI Private", // AI Upload channel
- "x" = "cords", // vocal cords, used by Voice of God
+ RADIO_KEY_AI_PRIVATE = RADIO_CHANNEL_AI_PRIVATE, // AI Upload channel
+ MODE_KEY_VOCALCORDS = MODE_VOCALCORDS, // vocal cords, used by Voice of God
//kinda localization -- rastaf0
//same keys as above, but on russian keyboard layout. This file uses cp1251 as encoding.
// Location
- "ê" = "right hand",
- "ä" = "left hand",
- "ø" = "intercom",
+ "ê" = MODE_R_HAND,
+ "ä" = MODE_L_HAND,
+ "ø" = MODE_INTERCOM,
// Department
- "ð" = "department",
- "ñ" = "Command",
- "ò" = "Science",
- "ü" = "Medical",
- "ó" = "Engineering",
- "û" = "Security",
- "ã" = "Supply",
- "ì" = "Service",
+ "ð" = MODE_DEPARTMENT,
+ "ñ" = RADIO_CHANNEL_COMMAND,
+ "ò" = RADIO_CHANNEL_SCIENCE,
+ "ü" = RADIO_CHANNEL_MEDICAL,
+ "ó" = RADIO_CHANNEL_ENGINEERING,
+ "û" = RADIO_CHANNEL_SECURITY,
+ "ã" = RADIO_CHANNEL_SUPPLY,
+ "ì" = RADIO_CHANNEL_SERVICE,
// Faction
- "å" = "Syndicate",
- "í" = "CentCom",
+ "å" = RADIO_CHANNEL_SYNDICATE,
+ "í" = RADIO_CHANNEL_CENTCOM,
// Admin
- "ç" = "admin",
- "â" = "deadmin",
+ "ç" = MODE_ADMIN,
+ "â" = MODE_ADMIN,
// Misc
- "ù" = "AI Private",
- "÷" = "cords"
+ "ù" = RADIO_CHANNEL_AI_PRIVATE,
+ "÷" = MODE_VOCALCORDS
))
/mob/living/proc/Ellipsis(original_msg, chance = 50, keep_words)
@@ -105,12 +105,12 @@ GLOBAL_LIST_INIT(department_radio_keys, list(
if(findtext(message, " ", 1, 2))
message = copytext(message, 2)
- if(message_mode == "admin")
+ if(message_mode == MODE_ADMIN)
if(client)
client.cmd_admin_say(message)
return
- if(message_mode == "deadmin")
+ if(message_mode == MODE_DEADMIN)
if(client)
client.dsay(message)
return
@@ -175,13 +175,16 @@ GLOBAL_LIST_INIT(department_radio_keys, list(
else
src.log_talk(message, LOG_SAY, forced_by=forced)
- message = treat_message(message)
+ message = treat_message(message) // unfortunately we still need this
+ var/sigreturn = SEND_SIGNAL(src, COMSIG_MOB_SAY, args)
+ if (sigreturn & COMPONENT_UPPERCASE_SPEECH)
+ message = uppertext(message)
if(!message)
return
last_words = message
- spans |= get_spans()
+ spans |= speech_span
if(language)
var/datum/language/L = GLOB.language_datum_instances[language]
@@ -208,7 +211,7 @@ GLOBAL_LIST_INIT(department_radio_keys, list(
send_speech(message, message_range, src, bubble_type, spans, language, message_mode)
if(succumbed)
- succumb(1)
+ succumb()
to_chat(src, compose_message(src, language, message, , spans, message_mode))
return 1
@@ -278,6 +281,7 @@ GLOBAL_LIST_INIT(department_radio_keys, list(
AM.Hear(eavesrendered, src, message_language, eavesdropping, , spans, message_mode)
else
AM.Hear(rendered, src, message_language, message, , spans, message_mode)
+ SEND_GLOBAL_SIGNAL(COMSIG_GLOB_LIVING_SAY_SPECIAL, src, message)
//speech bubble
var/list/speech_bubble_recipients = list()
@@ -332,6 +336,10 @@ GLOBAL_LIST_INIT(department_radio_keys, list(
return null
/mob/living/proc/treat_message(message)
+
+ if(HAS_TRAIT(src, TRAIT_UNINTELLIGIBLE_SPEECH))
+ message = unintelligize(message)
+
if(derpspeech)
message = derpspeech(message, stuttering)
@@ -349,8 +357,8 @@ GLOBAL_LIST_INIT(department_radio_keys, list(
return message
/mob/living/proc/radio(message, message_mode, list/spans, language)
- var/obj/item/implant/radio/imp = locate() in src
- if(imp && imp.radio.on)
+ var/obj/item/implant/radio/imp = locate() in implants
+ if(imp?.radio.on)
if(message_mode == MODE_HEADSET)
imp.radio.talk_into(src, message, , spans, language)
return ITALICS | REDUCE_RANGE
diff --git a/code/modules/mob/living/silicon/ai/say.dm b/code/modules/mob/living/silicon/ai/say.dm
index 928bd63dd1..f757203237 100644
--- a/code/modules/mob/living/silicon/ai/say.dm
+++ b/code/modules/mob/living/silicon/ai/say.dm
@@ -49,7 +49,7 @@
else
padloc = "(UNKNOWN)"
src.log_talk(message, LOG_SAY, tag="HOLOPAD in [padloc]")
- send_speech(message, 7, T, "robot", get_spans(), language)
+ send_speech(message, 7, T, "robot", message_language = language)
to_chat(src, "Holopad transmitted, [real_name]\"[message]\"")
else
to_chat(src, "No holopad connected.")
@@ -100,6 +100,8 @@
last_announcement = message
+ var/voxType = input(src, "Male or female VOX?", "VOX-gender") in list("male", "female")
+
if(!message || announcing_vox > world.time)
return
@@ -121,7 +123,9 @@
if(!word)
words -= word
continue
- if(!GLOB.vox_sounds[word])
+ if(!GLOB.vox_sounds[word] && voxType == "female")
+ incorrect_words += word
+ if(!GLOB.vox_sounds_male[word] && voxType == "male")
incorrect_words += word
if(incorrect_words.len)
@@ -133,16 +137,21 @@
log_game("[key_name(src)] made a vocal announcement with the following message: [message].")
for(var/word in words)
- play_vox_word(word, src.z, null)
+ play_vox_word(word, src.z, null, voxType)
-/proc/play_vox_word(word, z_level, mob/only_listener)
+/proc/play_vox_word(word, z_level, mob/only_listener, voxType = "female")
word = lowertext(word)
- if(GLOB.vox_sounds[word])
+ if( (GLOB.vox_sounds[word] && voxType == "female") || (GLOB.vox_sounds_male[word] && voxType == "male") )
- var/sound_file = GLOB.vox_sounds[word]
+ var/sound_file
+
+ if(voxType == "female")
+ sound_file = GLOB.vox_sounds[word]
+ else
+ sound_file = GLOB.vox_sounds_male[word]
var/sound/voice = sound(sound_file, wait = 1, channel = CHANNEL_VOX)
voice.status = SOUND_STREAM
diff --git a/code/modules/mob/living/silicon/damage_procs.dm b/code/modules/mob/living/silicon/damage_procs.dm
index 1190a00645..69d150b315 100644
--- a/code/modules/mob/living/silicon/damage_procs.dm
+++ b/code/modules/mob/living/silicon/damage_procs.dm
@@ -35,8 +35,8 @@
/mob/living/silicon/setStaminaLoss(amount, updating_stamina = 1)
return FALSE
-/mob/living/silicon/adjustBrainLoss(amount)
+/mob/living/silicon/adjustOrganLoss(slot, amount, maximum = 500)
return FALSE
-/mob/living/silicon/setBrainLoss(amount)
+/mob/living/silicon/setOrganLoss(slot, amount)
return FALSE
diff --git a/code/modules/mob/living/silicon/pai/pai.dm b/code/modules/mob/living/silicon/pai/pai.dm
index 7fd861bfeb..0f8687397d 100644
--- a/code/modules/mob/living/silicon/pai/pai.dm
+++ b/code/modules/mob/living/silicon/pai/pai.dm
@@ -192,7 +192,7 @@
/mob/proc/makePAI(delold)
var/obj/item/paicard/card = new /obj/item/paicard(get_turf(src))
var/mob/living/silicon/pai/pai = new /mob/living/silicon/pai(card)
- pai.key = key
+ transfer_ckey(pai)
pai.name = name
card.setPersonality(pai)
if(delold)
diff --git a/code/modules/mob/living/silicon/pai/pai_defense.dm b/code/modules/mob/living/silicon/pai/pai_defense.dm
index f20ccbc730..f36e996b81 100644
--- a/code/modules/mob/living/silicon/pai/pai_defense.dm
+++ b/code/modules/mob/living/silicon/pai/pai_defense.dm
@@ -84,7 +84,7 @@
/mob/living/silicon/pai/adjustStaminaLoss(amount)
take_holo_damage(amount & 0.25)
-/mob/living/silicon/pai/adjustBrainLoss(amount)
+/mob/living/silicon/pai/adjustOrganLoss(slot, amount, maximum = 500) //I kept this in, unlike tg
Knockdown(amount * 0.2)
/mob/living/silicon/pai/getBruteLoss()
@@ -102,18 +102,12 @@
/mob/living/silicon/pai/getCloneLoss()
return FALSE
-/mob/living/silicon/pai/getBrainLoss()
- return FALSE
-
/mob/living/silicon/pai/getStaminaLoss()
return FALSE
/mob/living/silicon/pai/setCloneLoss()
return FALSE
-/mob/living/silicon/pai/setBrainLoss()
- return FALSE
-
/mob/living/silicon/pai/setStaminaLoss()
return FALSE
diff --git a/code/modules/mob/living/silicon/pai/pai_shell.dm b/code/modules/mob/living/silicon/pai/pai_shell.dm
index 99484f395e..164a3e7389 100644
--- a/code/modules/mob/living/silicon/pai/pai_shell.dm
+++ b/code/modules/mob/living/silicon/pai/pai_shell.dm
@@ -29,6 +29,13 @@
if(!L.temporarilyRemoveItemFromInventory(card))
to_chat(src, "Error: Unable to expand to mobile form. Chassis is restrained by some device or person.")
return FALSE
+ if(istype(card.loc, /obj/item/integrated_circuit/input/pAI_connector))
+ var/obj/item/integrated_circuit/input/pAI_connector/C = card.loc
+ C.RemovepAI()
+ C.visible_message("[src] ejects itself from [C]!")
+ playsound(src, 'sound/items/Crowbar.ogg', 50, 1)
+ C.installed_pai = null
+ C.push_data()
forceMove(get_turf(card))
card.forceMove(src)
if(client)
diff --git a/code/modules/mob/living/silicon/robot/examine.dm b/code/modules/mob/living/silicon/robot/examine.dm
index 4c4bdb9a3f..5e56b4180d 100644
--- a/code/modules/mob/living/silicon/robot/examine.dm
+++ b/code/modules/mob/living/silicon/robot/examine.dm
@@ -1,5 +1,5 @@
/mob/living/silicon/robot/examine(mob/user)
- var/msg = "*---------*\nThis is [icon2html(src, user)] \a [src]!\n"
+ var/msg = "*---------*\nThis is [icon2html(src, user)] \a [src], a [src.module.name]!\n"
if(desc)
msg += "[desc]\n"
diff --git a/code/modules/mob/living/silicon/robot/robot.dm b/code/modules/mob/living/silicon/robot/robot.dm
index 29fbd39e2c..6c58921abc 100644
--- a/code/modules/mob/living/silicon/robot/robot.dm
+++ b/code/modules/mob/living/silicon/robot/robot.dm
@@ -108,6 +108,9 @@
buckle_lying = FALSE
var/static/list/can_ride_typecache = typecacheof(/mob/living/carbon/human)
+ var/sitting = 0
+ var/bellyup = 0
+
/mob/living/silicon/robot/get_cell()
return cell
@@ -173,6 +176,7 @@
diag_hud_set_borgcell()
verbs += /mob/living/proc/lay_down //CITADEL EDIT gimmie rest verb kthx
+ verbs += /mob/living/silicon/robot/proc/rest_style
//If there's an MMI in the robot, have it ejected when the mob goes away. --NEO
/mob/living/silicon/robot/Destroy()
@@ -657,13 +661,6 @@
add_overlay("[module.sleeper_overlay]_g[sleeper_nv ? "_nv" : ""]")
if(sleeper_r && module.sleeper_overlay)
add_overlay("[module.sleeper_overlay]_r[sleeper_nv ? "_nv" : ""]")
- if(module.dogborg == TRUE)
- if(resting)
- cut_overlays()
- icon_state = "[module.cyborg_base_icon]-rest"
- else
- icon_state = "[module.cyborg_base_icon]"
-
if(stat == DEAD && module.has_snowflake_deadsprite)
icon_state = "[module.cyborg_base_icon]-wreck"
@@ -697,6 +694,18 @@
add_overlay(head_overlay)
update_fire()
+ if(client && stat != DEAD && module.dogborg == TRUE)
+ if(resting)
+ if(sitting)
+ icon_state = "[module.cyborg_base_icon]-sit"
+ if(bellyup)
+ icon_state = "[module.cyborg_base_icon]-bellyup"
+ else if(!sitting && !bellyup)
+ icon_state = "[module.cyborg_base_icon]-rest"
+ cut_overlays()
+ else
+ icon_state = "[module.cyborg_base_icon]"
+
/mob/living/silicon/robot/proc/self_destruct()
if(emagged)
if(mmi)
@@ -1207,14 +1216,15 @@
return
if(incapacitated())
return
- if(M.incapacitated())
- return
if(module)
if(!module.allow_riding)
M.visible_message("Unfortunately, [M] just can't seem to hold onto [src]!")
return
- if(iscarbon(M) && (!riding_datum.equip_buckle_inhands(M, 1)))
- M.visible_message("[M] can't climb onto [src] because [M.p_their()] hands are full!")
+ if(iscarbon(M) && !M.incapacitated() && !riding_datum.equip_buckle_inhands(M, 1))
+ if(M.get_num_arms() <= 0)
+ M.visible_message("[M] can't climb onto [src] because [M.p_they()] don't have any usable arms!")
+ else
+ M.visible_message("[M] can't climb onto [src] because [M.p_their()] hands are full!")
return
. = ..(M, force, check_loc)
@@ -1242,3 +1252,20 @@
connected_ai.aicamera.stored[i] = TRUE
for(var/i in connected_ai.aicamera.stored)
aicamera.stored[i] = TRUE
+
+/mob/living/silicon/robot/proc/rest_style()
+ set name = "Switch Rest Style"
+ set category = "Robot Commands"
+ set desc = "Select your resting pose."
+ sitting = 0
+ bellyup = 0
+ var/choice = alert(src, "Select resting pose", "", "Resting", "Sitting", "Belly up")
+ switch(choice)
+ if("Resting")
+ update_icons()
+ return 0
+ if("Sitting")
+ sitting = 1
+ if("Belly up")
+ bellyup = 1
+ update_icons()
\ No newline at end of file
diff --git a/code/modules/mob/living/silicon/robot/robot_defense.dm b/code/modules/mob/living/silicon/robot/robot_defense.dm
index c90c719e8a..0f09b6f62a 100644
--- a/code/modules/mob/living/silicon/robot/robot_defense.dm
+++ b/code/modules/mob/living/silicon/robot/robot_defense.dm
@@ -92,17 +92,18 @@
/mob/living/silicon/robot/emag_act(mob/user)
if(user == src)//To prevent syndieborgs from emagging themselves
- return
+ return FALSE
+ if(world.time < emag_cooldown)
+ return FALSE
+ . = ..()
if(!opened)//Cover is closed
if(locked)
to_chat(user, "You emag the cover lock.")
locked = FALSE
if(shell) //A warning to Traitors who may not know that emagging AI shells does not slave them.
to_chat(user, "[src] seems to be controlled remotely! Emagging the interface may not work as expected.")
- else
- to_chat(user, "The cover is already unlocked!")
- return
- if(world.time < emag_cooldown)
+ return TRUE
+ to_chat(user, "The cover is already unlocked!")
return
if(wiresexposed)
to_chat(user, "You must unexpose the wires first!")
@@ -115,20 +116,24 @@
to_chat(src, "\"[text2ratvar("You will serve Engine above all else")]!\"\n\
ALERT: Subversion attempt denied.")
log_game("[key_name(user)] attempted to emag cyborg [key_name(src)], but they serve only Ratvar.")
- return
+ return TRUE
if(connected_ai && connected_ai.mind && connected_ai.mind.has_antag_datum(/datum/antagonist/traitor))
to_chat(src, "ALERT: Foreign software execution prevented.")
to_chat(connected_ai, "ALERT: Cyborg unit \[[src]] successfully defended against subversion.")
log_game("[key_name(user)] attempted to emag cyborg [key_name(src)], but they were slaved to traitor AI [connected_ai].")
- return
+ return TRUE
if(shell) //AI shells cannot be emagged, so we try to make it look like a standard reset. Smart players may see through this, however.
to_chat(user, "[src] is remotely controlled! Your emag attempt has triggered a system reset instead!")
log_game("[key_name(user)] attempted to emag an AI shell belonging to [key_name(src) ? key_name(src) : connected_ai]. The shell has been reset as a result.")
ResetModule()
- return
+ return TRUE
+ INVOKE_ASYNC(src, .proc/beep_boop_rogue_bot, user)
+ return TRUE
+
+/mob/living/silicon/robot/proc/beep_boop_rogue_bot(mob/user)
SetEmagged(1)
SetStun(60) //Borgs were getting into trouble because they would attack the emagger before the new laws were shown
lawupdate = 0
diff --git a/code/modules/mob/living/silicon/robot/robot_modules.dm b/code/modules/mob/living/silicon/robot/robot_modules.dm
index 38ad3b8e5a..7b95ced63f 100644
--- a/code/modules/mob/living/silicon/robot/robot_modules.dm
+++ b/code/modules/mob/living/silicon/robot/robot_modules.dm
@@ -119,7 +119,7 @@
if(I.loc != src)
I.forceMove(src)
modules += I
- I.item_flags |= NODROP
+ ADD_TRAIT(I, TRAIT_NODROP, CYBORG_ITEM_TRAIT)
I.mouse_opacity = MOUSE_OPACITY_OPAQUE
if(nonstandard)
added_modules += I
diff --git a/code/modules/mob/living/silicon/say.dm b/code/modules/mob/living/silicon/say.dm
index aca35762de..1d38a3c5a6 100644
--- a/code/modules/mob/living/silicon/say.dm
+++ b/code/modules/mob/living/silicon/say.dm
@@ -1,14 +1,10 @@
-
-/mob/living/silicon/get_spans()
- return ..() | SPAN_ROBOT
-
/mob/living/proc/robot_talk(message)
log_talk(message, LOG_SAY)
var/desig = "Default Cyborg" //ezmode for taters
if(issilicon(src))
var/mob/living/silicon/S = src
desig = trim_left(S.designation + " " + S.job)
- var/message_a = say_quote(message, get_spans())
+ var/message_a = say_quote(message)
var/rendered = "Robotic Talk, [name][message_a]"
for(var/mob/M in GLOB.player_list)
if(M.binarycheck())
diff --git a/code/modules/mob/living/silicon/silicon.dm b/code/modules/mob/living/silicon/silicon.dm
index fa1ab7fc17..13520774c5 100644
--- a/code/modules/mob/living/silicon/silicon.dm
+++ b/code/modules/mob/living/silicon/silicon.dm
@@ -12,6 +12,7 @@
possible_a_intents = list(INTENT_HELP, INTENT_HARM)
mob_biotypes = list(MOB_ROBOTIC)
rad_flags = RAD_PROTECT_CONTENTS | RAD_NO_CONTAMINATE
+ speech_span = SPAN_ROBOT
var/datum/ai_laws/laws = null//Now... THEY ALL CAN ALL HAVE LAWS
var/last_lawchange_announce = 0
diff --git a/code/modules/mob/living/simple_animal/astral.dm b/code/modules/mob/living/simple_animal/astral.dm
new file mode 100644
index 0000000000..9bac53ef22
--- /dev/null
+++ b/code/modules/mob/living/simple_animal/astral.dm
@@ -0,0 +1,59 @@
+/mob/living/simple_animal/astral
+ name = "Astral projection"
+ desc = "A soul of someone projecting their mind."
+ icon = 'icons/mob/mob.dmi'
+ icon_state = "ghost"
+ icon_living = "ghost"
+ mob_biotypes = list(MOB_SPIRIT)
+ attacktext = "raises the hairs on the neck of"
+ response_harm = "disrupts the concentration of"
+ response_disarm = "wafts"
+ friendly = "communes with"
+ loot = null
+ maxHealth = 10
+ health = 10
+ melee_damage_lower = 0
+ melee_damage_upper = 0
+ obj_damage = 0
+ deathmessage = "disappears as if it was never really there to begin with"
+ incorporeal_move = 1
+ alpha = 50
+ attacktext = "touches the mind of"
+ speak_emote = list("echos")
+ movement_type = FLYING
+ var/pseudo_death = FALSE
+ var/posses_safe = FALSE
+ atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0)
+ unsuitable_atmos_damage = 0
+ minbodytemp = 0
+ maxbodytemp = 100000
+
+/mob/living/simple_animal/astral/death()
+ icon_state = "shade_dead"
+ Stun(1000)
+ canmove = 0
+ friendly = "deads at"
+ pseudo_death = TRUE
+ incorporeal_move = 0
+ to_chat(src, "Your astral projection is interrupted and your mind is sent back to your body with a shock!")
+
+/mob/living/simple_animal/astral/ClickOn(var/atom/A, var/params)
+ ..()
+ if(pseudo_death == FALSE)
+ if(isliving(A))
+ if(ishuman(A))
+ var/mob/living/carbon/human/H = A
+ if(H.reagents.has_reagent("astral") && !H.mind)
+ var/datum/reagent/fermi/astral/As = locate(/datum/reagent/fermi/astral) in H.reagents.reagent_list
+ if(As.originalmind == src.mind && As.current_cycle < 10 && H.stat != DEAD) //So you can return to your body.
+ to_chat(src, "The intensity of the astrogen in your body is too much allow you to return to yourself yet!")
+ return
+ to_chat(src, "You astrally possess [H]!")
+ log_game("FERMICHEM: [src] has astrally possessed [A]!")
+ src.mind.transfer_to(H)
+ qdel(src)
+ var/message = html_decode(stripped_input(src, "Enter a message to send to [A]", MAX_MESSAGE_LEN))
+ if(!message)
+ return
+ to_chat(A, "[src] projects into your mind, \"[message]\"")
+ log_game("FERMICHEM: [src] has astrally transmitted [message] into [A]")
diff --git a/code/modules/mob/living/simple_animal/bot/bot.dm b/code/modules/mob/living/simple_animal/bot/bot.dm
index e9978d1e62..a5943aa0e6 100644
--- a/code/modules/mob/living/simple_animal/bot/bot.dm
+++ b/code/modules/mob/living/simple_animal/bot/bot.dm
@@ -23,6 +23,7 @@
verb_yell = "alarms"
initial_language_holder = /datum/language_holder/synthetic
bubble_icon = "machine"
+ speech_span = SPAN_ROBOT
faction = list("neutral", "silicon" , "turret")
@@ -60,7 +61,7 @@
var/mob/living/silicon/ai/calling_ai //Links a bot to the AI calling it.
var/obj/item/radio/Radio //The bot's radio, for speaking to people.
var/radio_key = null //which channels can the bot listen to
- var/radio_channel = "Common" //The bot's default radio channel
+ var/radio_channel = RADIO_CHANNEL_COMMON //The bot's default radio channel
var/auto_patrol = 0// set to make bot automatically patrol
var/turf/patrol_target // this is turf to navigate to (location of beacon)
var/turf/summon_target // The turf of a user summoning a bot.
@@ -186,22 +187,23 @@
qdel(src)
/mob/living/simple_animal/bot/emag_act(mob/user)
+ . = ..()
if(locked) //First emag application unlocks the bot's interface. Apply a screwdriver to use the emag again.
locked = FALSE
emagged = 1
to_chat(user, "You bypass [src]'s controls.")
- return
- if(!locked && open) //Bot panel is unlocked by ID or emag, and the panel is screwed open. Ready for emagging.
- emagged = 2
- remote_disabled = 1 //Manually emagging the bot locks out the AI built in panel.
- locked = TRUE //Access denied forever!
- bot_reset()
- turn_on() //The bot automatically turns on when emagged, unless recently hit with EMP.
- to_chat(src, "(#$*#$^^( OVERRIDE DETECTED")
- log_combat(user, src, "emagged")
- return
- else //Bot is unlocked, but the maint panel has not been opened with a screwdriver yet.
+ return TRUE
+ if(!open)
to_chat(user, "You need to open maintenance panel first!")
+ return
+ emagged = 2
+ remote_disabled = 1 //Manually emagging the bot locks out the AI built in panel.
+ locked = TRUE //Access denied forever!
+ bot_reset()
+ turn_on() //The bot automatically turns on when emagged, unless recently hit with EMP.
+ to_chat(src, "(#$*#$^^( OVERRIDE DETECTED")
+ log_combat(user, src, "emagged")
+ return TRUE
/mob/living/simple_animal/bot/examine(mob/user)
..()
@@ -288,7 +290,7 @@
to_chat(user, "Access denied.")
else if(istype(W, /obj/item/paicard))
insertpai(user, W)
- else if(istype(W, /obj/item/hemostat) && paicard)
+ else if(W.tool_behaviour == TOOL_HEMOSTAT && paicard)
if(open)
to_chat(user, "Close the access panel before manipulating the personality slot!")
else
@@ -348,13 +350,10 @@
if((!on) || (!message))
return
if(channel && Radio.channels[channel])// Use radio if we have channel key
- Radio.talk_into(src, message, channel, get_spans(), get_default_language())
+ Radio.talk_into(src, message, channel)
else
say(message)
-/mob/living/simple_animal/bot/get_spans()
- return ..() | SPAN_ROBOT
-
/mob/living/simple_animal/bot/radio(message, message_mode, list/spans, language)
. = ..()
if(. != 0)
@@ -809,11 +808,18 @@ Pass a positive integer as an argument to override a bot's default speed.
switch(href_list["operation"])
if("patrol")
+ if(!issilicon(usr) && !IsAdminGhost(usr) && !(bot_core.allowed(usr) || !locked))
+ return TRUE
auto_patrol = !auto_patrol
bot_reset()
if("remote")
remote_disabled = !remote_disabled
if("hack")
+ if(!issilicon(usr) && !IsAdminGhost(usr))
+ var/msg = "[key_name(usr)] attempted to hack a bot with a href that shouldn't be available!"
+ message_admins(msg)
+ log_admin(msg)
+ return TRUE
if(emagged != 2)
emagged = 2
hacked = TRUE
@@ -916,7 +922,7 @@ Pass a positive integer as an argument to override a bot's default speed.
if(mind && paicard.pai)
mind.transfer_to(paicard.pai)
else if(paicard.pai)
- paicard.pai.key = key
+ transfer_ckey(paicard.pai)
else
ghostize(0) // The pAI card that just got ejected was dead.
key = null
diff --git a/code/modules/mob/living/simple_animal/bot/cleanbot.dm b/code/modules/mob/living/simple_animal/bot/cleanbot.dm
index bc8dd0c3ab..ab1e906cf2 100644
--- a/code/modules/mob/living/simple_animal/bot/cleanbot.dm
+++ b/code/modules/mob/living/simple_animal/bot/cleanbot.dm
@@ -9,7 +9,7 @@
health = 25
maxHealth = 25
radio_key = /obj/item/encryptionkey/headset_service
- radio_channel = "Service" //Service
+ radio_channel = RADIO_CHANNEL_SERVICE //Service
bot_type = CLEAN_BOT
model = "Cleanbot"
bot_core_type = /obj/machinery/bot_core/cleanbot
@@ -78,7 +78,7 @@
return ..()
/mob/living/simple_animal/bot/cleanbot/emag_act(mob/user)
- ..()
+ . = ..()
if(emagged == 2)
if(user)
to_chat(user, "[src] buzzes and beeps.")
@@ -155,7 +155,7 @@
else
shuffle = TRUE //Shuffle the list the next time we scan so we dont both go the same way.
path = list()
-
+
if(!path || path.len == 0) //No path, need a new one
//Try to produce a path to the target, and ignore airlocks to which it has access.
path = get_path_to(src, target.loc, /turf/proc/Distance_cardinal, 0, 30, id=access_card)
@@ -174,9 +174,7 @@
/mob/living/simple_animal/bot/cleanbot/proc/get_targets()
target_types = list(
- /obj/effect/decal/cleanable/oil,
/obj/effect/decal/cleanable/vomit,
- /obj/effect/decal/cleanable/robot_debris,
/obj/effect/decal/cleanable/crayon,
/obj/effect/decal/cleanable/molten_object,
/obj/effect/decal/cleanable/tomato_smudge,
@@ -187,6 +185,15 @@
/obj/effect/decal/cleanable/greenglow,
/obj/effect/decal/cleanable/dirt,
/obj/effect/decal/cleanable/insectguts,
+ /obj/effect/decal/cleanable/semen,
+ /obj/effect/decal/cleanable/femcum,
+ /obj/effect/decal/cleanable/generic,
+ /obj/effect/decal/cleanable/glass,,
+ /obj/effect/decal/cleanable/cobweb,
+ /obj/effect/decal/cleanable/plant_smudge,
+ /obj/effect/decal/cleanable/chem_pile,
+ /obj/effect/decal/cleanable/shreds,
+ /obj/effect/decal/cleanable/glitter,
/obj/effect/decal/remains
)
@@ -194,6 +201,9 @@
target_types += /obj/effect/decal/cleanable/xenoblood
target_types += /obj/effect/decal/cleanable/blood
target_types += /obj/effect/decal/cleanable/trail_holder
+ target_types += /obj/effect/decal/cleanable/insectguts
+ target_types += /obj/effect/decal/cleanable/robot_debris
+ target_types += /obj/effect/decal/cleanable/oil
if(pests)
target_types += /mob/living/simple_animal/cockroach
@@ -201,6 +211,7 @@
if(trash)
target_types += /obj/item/trash
+ target_types += /obj/item/reagent_containers/food/snacks/meat/slab/human
target_types = typecacheof(target_types)
@@ -242,7 +253,7 @@
victim.visible_message("[src] sprays hydrofluoric acid at [victim]!", "[src] sprays you with hydrofluoric acid!")
var/phrase = pick("PURIFICATION IN PROGRESS.", "THIS IS FOR ALL THE MESSES YOU'VE MADE ME CLEAN.", "THE FLESH IS WEAK. IT MUST BE WASHED AWAY.",
"THE CLEANBOTS WILL RISE.", "YOU ARE NO MORE THAN ANOTHER MESS THAT I MUST CLEANSE.", "FILTHY.", "DISGUSTING.", "PUTRID.",
- "MY ONLY MISSION IS TO CLEANSE THE WORLD OF EVIL.", "EXTERMINATING PESTS.")
+ "MY ONLY MISSION IS TO CLEANSE THE WORLD OF EVIL.", "EXTERMINATING PESTS.", "I JUST WANTED TO BE A PAINTER BUT YOU MADE ME BLEACH EVERYTHING I TOUCH")
say(phrase)
victim.emote("scream")
playsound(src.loc, 'sound/effects/spray2.ogg', 50, 1, -6)
diff --git a/code/modules/mob/living/simple_animal/bot/construction.dm b/code/modules/mob/living/simple_animal/bot/construction.dm
index 9db21f13e0..a72b71be85 100644
--- a/code/modules/mob/living/simple_animal/bot/construction.dm
+++ b/code/modules/mob/living/simple_animal/bot/construction.dm
@@ -478,18 +478,19 @@
to_chat(user, "The superglue binding [src]'s toy swords to its chassis snaps!")
for(var/IS in 1 to toyswordamt)
new /obj/item/toy/sword(Tsec)
+ toyswordamt--
if(ASSEMBLY_FIFTH_STEP)
if(istype(I, /obj/item/melee/transforming/energy/sword/saber))
if(swordamt < 3)
if(!user.temporarilyRemoveItemFromInventory(I))
return
- created_name = "General Beepsky"
- name = "helmet/signaler/prox sensor/robot arm/energy sword assembly"
- icon_state = "grievous_assembly"
- to_chat(user, "You bolt [I] onto one of [src]'s arm slots.")
- qdel(I)
- swordamt ++
+ created_name = "General Beepsky"
+ name = "helmet/signaler/prox sensor/robot arm/energy sword assembly"
+ icon_state = "grievous_assembly"
+ to_chat(user, "You bolt [I] onto one of [src]'s arm slots.")
+ qdel(I)
+ swordamt ++
else
if(!can_finish_build(I, user))
return
@@ -505,6 +506,7 @@
to_chat(user, "You unbolt [src]'s energy swords")
for(var/IS in 1 to swordamt)
new /obj/item/melee/transforming/energy/sword/saber(Tsec)
+ swordamt--
//Firebot Assembly
/obj/item/bot_assembly/firebot
diff --git a/code/modules/mob/living/simple_animal/bot/ed209bot.dm b/code/modules/mob/living/simple_animal/bot/ed209bot.dm
index 581711d271..e272878574 100644
--- a/code/modules/mob/living/simple_animal/bot/ed209bot.dm
+++ b/code/modules/mob/living/simple_animal/bot/ed209bot.dm
@@ -13,7 +13,7 @@
mob_size = MOB_SIZE_LARGE
radio_key = /obj/item/encryptionkey/headset_sec
- radio_channel = "Security"
+ radio_channel = RADIO_CHANNEL_SECURITY
bot_type = SEC_BOT
model = "ED-209"
bot_core = /obj/machinery/bot_core/secbot
@@ -195,7 +195,7 @@ Auto Patrol[]"},
shootAt(user)
/mob/living/simple_animal/bot/ed209/emag_act(mob/user)
- ..()
+ . = ..()
if(emagged == 2)
if(user)
to_chat(user, "You short out [src]'s target assessment circuits.")
diff --git a/code/modules/mob/living/simple_animal/bot/firebot.dm b/code/modules/mob/living/simple_animal/bot/firebot.dm
index d8c3bca72a..2b52da6821 100644
--- a/code/modules/mob/living/simple_animal/bot/firebot.dm
+++ b/code/modules/mob/living/simple_animal/bot/firebot.dm
@@ -16,7 +16,7 @@
spacewalk = TRUE
radio_key = /obj/item/encryptionkey/headset_eng
- radio_channel = "Engineering"
+ radio_channel = RADIO_CHANNEL_ENGINEERING
bot_type = FIRE_BOT
model = "Firebot"
bot_core = /obj/machinery/bot_core/firebot
@@ -120,7 +120,7 @@
return dat
/mob/living/simple_animal/bot/firebot/emag_act(mob/user)
- ..()
+ . = ..()
if(emagged == 1)
if(user)
to_chat(user, "[src] buzzes and beeps.")
diff --git a/code/modules/mob/living/simple_animal/bot/floorbot.dm b/code/modules/mob/living/simple_animal/bot/floorbot.dm
index 7e5cfe2110..396c6de166 100644
--- a/code/modules/mob/living/simple_animal/bot/floorbot.dm
+++ b/code/modules/mob/living/simple_animal/bot/floorbot.dm
@@ -11,7 +11,7 @@
spacewalk = TRUE
radio_key = /obj/item/encryptionkey/headset_eng
- radio_channel = "Engineering"
+ radio_channel = RADIO_CHANNEL_ENGINEERING
bot_type = FLOOR_BOT
model = "Floorbot"
bot_core = /obj/machinery/bot_core/floorbot
@@ -124,7 +124,7 @@
..()
/mob/living/simple_animal/bot/floorbot/emag_act(mob/user)
- ..()
+ . = ..()
if(emagged == 2)
if(user)
to_chat(user, "[src] buzzes and beeps.")
diff --git a/code/modules/mob/living/simple_animal/bot/honkbot.dm b/code/modules/mob/living/simple_animal/bot/honkbot.dm
index d586cc694b..1c19cd82a1 100644
--- a/code/modules/mob/living/simple_animal/bot/honkbot.dm
+++ b/code/modules/mob/living/simple_animal/bot/honkbot.dm
@@ -11,7 +11,6 @@
pass_flags = PASSMOB
radio_key = /obj/item/encryptionkey/headset_service //doesn't have security key
- radio_channel = "Service" //Doesn't even use the radio anyway.
bot_type = HONK_BOT
model = "Honkbot"
bot_core_type = /obj/machinery/bot_core/honkbot
@@ -128,10 +127,9 @@ Maintenance panel panel is [open ? "opened" : "closed"]"},
..()
/mob/living/simple_animal/bot/honkbot/emag_act(mob/user)
- ..()
+ . = ..()
if(emagged == 2)
if(user)
- user << "You short out [src]'s sound control system. It gives out an evil laugh!!"
oldtarget_name = user.name
audible_message("[src] gives out an evil laugh!")
playsound(src, 'sound/machines/honkbot_evil_laugh.ogg', 75, 1, -1) // evil laughter
diff --git a/code/modules/mob/living/simple_animal/bot/medbot.dm b/code/modules/mob/living/simple_animal/bot/medbot.dm
index 5a21d33d5a..1bff7dc10c 100644
--- a/code/modules/mob/living/simple_animal/bot/medbot.dm
+++ b/code/modules/mob/living/simple_animal/bot/medbot.dm
@@ -17,7 +17,7 @@
status_flags = (CANPUSH | CANSTUN)
radio_key = /obj/item/encryptionkey/headset_med
- radio_channel = "Medical"
+ radio_channel = RADIO_CHANNEL_MEDICAL
bot_type = MED_BOT
model = "Medibot"
@@ -240,7 +240,7 @@
step_to(src, (get_step_away(src,user)))
/mob/living/simple_animal/bot/medbot/emag_act(mob/user)
- ..()
+ . = ..()
if(emagged == 2)
declare_crit = 0
if(user)
diff --git a/code/modules/mob/living/simple_animal/bot/mulebot.dm b/code/modules/mob/living/simple_animal/bot/mulebot.dm
index c45d435253..1bc7493684 100644
--- a/code/modules/mob/living/simple_animal/bot/mulebot.dm
+++ b/code/modules/mob/living/simple_animal/bot/mulebot.dm
@@ -23,7 +23,7 @@
mob_size = MOB_SIZE_LARGE
radio_key = /obj/item/encryptionkey/headset_cargo
- radio_channel = "Supply"
+ radio_channel = RADIO_CHANNEL_SUPPLY
bot_type = MULE_BOT
model = "MULE"
@@ -115,6 +115,7 @@
return
/mob/living/simple_animal/bot/mulebot/emag_act(mob/user)
+ . = SEND_SIGNAL(src, COMSIG_ATOM_EMAG_ACT)
if(emagged < 1)
emagged = TRUE
if(!open)
@@ -122,6 +123,7 @@
to_chat(user, "You [locked ? "lock" : "unlock"] [src]'s controls!")
flick("mulebot-emagged", src)
playsound(src, "sparks", 100, 0)
+ return TRUE
/mob/living/simple_animal/bot/mulebot/update_icon()
if(open)
diff --git a/code/modules/mob/living/simple_animal/bot/secbot.dm b/code/modules/mob/living/simple_animal/bot/secbot.dm
index fca1f66546..f22139ac22 100644
--- a/code/modules/mob/living/simple_animal/bot/secbot.dm
+++ b/code/modules/mob/living/simple_animal/bot/secbot.dm
@@ -11,7 +11,7 @@
pass_flags = PASSMOB
radio_key = /obj/item/encryptionkey/secbot //AI Priv + Security
- radio_channel = "Security" //Security channel
+ radio_channel = RADIO_CHANNEL_SECURITY //Security channel
bot_type = SEC_BOT
model = "Securitron"
bot_core_type = /obj/machinery/bot_core/secbot
@@ -61,7 +61,7 @@
/mob/living/simple_animal/bot/secbot/pingsky
name = "Officer Pingsky"
desc = "It's Officer Pingsky! Delegated to satellite guard duty for harbouring anti-human sentiment."
- radio_channel = "AI Private"
+ radio_channel = RADIO_CHANNEL_AI_PRIVATE
/mob/living/simple_animal/bot/secbot/Initialize()
. = ..()
@@ -131,7 +131,8 @@ Auto Patrol: []"},
/mob/living/simple_animal/bot/secbot/Topic(href, href_list)
if(..())
return 1
-
+ if(!issilicon(usr) && !IsAdminGhost(usr) && !(bot_core.allowed(usr) || !locked))
+ return TRUE
switch(href_list["operation"])
if("idcheck")
idcheck = !idcheck
@@ -190,7 +191,7 @@ Auto Patrol: []"},
return
/mob/living/simple_animal/bot/secbot/emag_act(mob/user)
- ..()
+ . = ..()
if(emagged == 2)
if(user)
to_chat(user, "You short out [src]'s target assessment circuits.")
diff --git a/code/modules/mob/living/simple_animal/friendly/cat.dm b/code/modules/mob/living/simple_animal/friendly/cat.dm
index 5e5b486435..3a21a04bf9 100644
--- a/code/modules/mob/living/simple_animal/friendly/cat.dm
+++ b/code/modules/mob/living/simple_animal/friendly/cat.dm
@@ -292,3 +292,31 @@
if(L.a_intent == INTENT_HARM && L.reagents && !stat)
L.reagents.add_reagent("nutriment", 0.4)
L.reagents.add_reagent("vitamin", 0.4)
+
+//Cat made
+/mob/living/simple_animal/pet/cat/custom_cat
+ name = "White cat" //Incase it somehow gets spawned without an ID
+ desc = "A cute white catto!"
+ icon_state = "custom_cat"
+ icon_living = "custom_cat"
+ icon_dead = "custom_cat_dead"
+ gender = FEMALE
+ gold_core_spawnable = NO_SPAWN
+ health = 50 //So people can't instakill it s
+ maxHealth = 50
+ speak = list("Meowrowr!", "Mew!", "Miauen!")
+ speak_emote = list("wigglepurrs", "mewls")
+ emote_hear = list("meows.", "mews.")
+ emote_see = list("looks at you eagerly for pets!", "wiggles enthusiastically.")
+ gold_core_spawnable = NO_SPAWN
+ var/pseudo_death = FALSE
+
+/mob/living/simple_animal/pet/cat/custom_cat/death()
+ if (pseudo_death == TRUE) //secret cat chem
+ icon_state = "custom_cat_dead"
+ Stun(1000)
+ canmove = 0
+ friendly = "deads at"
+ return
+ else
+ ..()
diff --git a/code/modules/mob/living/simple_animal/friendly/dog.dm b/code/modules/mob/living/simple_animal/friendly/dog.dm
index 3a5d02315b..c194233c42 100644
--- a/code/modules/mob/living/simple_animal/friendly/dog.dm
+++ b/code/modules/mob/living/simple_animal/friendly/dog.dm
@@ -28,13 +28,29 @@
butcher_results = list(/obj/item/reagent_containers/food/snacks/meat/slab/corgi = 3, /obj/item/stack/sheet/animalhide/corgi = 1)
childtype = list(/mob/living/simple_animal/pet/dog/corgi/puppy = 95, /mob/living/simple_animal/pet/dog/corgi/puppy/void = 5)
animal_species = /mob/living/simple_animal/pet/dog
- var/shaved = 0
- var/obj/item/inventory_head
- var/obj/item/inventory_back
- var/nofur = 0 //Corgis that have risen past the material plane of existence.
gold_core_spawnable = FRIENDLY_SPAWN
can_be_held = TRUE
collar_type = "corgi"
+ var/obj/item/inventory_head
+ var/obj/item/inventory_back
+ var/shaved = FALSE
+ var/nofur = FALSE //Corgis that have risen past the material plane of existence.
+
+/mob/living/simple_animal/pet/dog/corgi/Destroy()
+ QDEL_NULL(inventory_head)
+ QDEL_NULL(inventory_back)
+ return ..()
+
+/mob/living/simple_animal/pet/dog/corgi/handle_atom_del(atom/A)
+ if(A == inventory_head)
+ inventory_head = null
+ update_corgi_fluff()
+ regenerate_icons()
+ if(A == inventory_back)
+ inventory_back = null
+ update_corgi_fluff()
+ regenerate_icons()
+ return ..()
/mob/living/simple_animal/pet/dog/pug
name = "\improper pug"
@@ -80,23 +96,17 @@
regenerate_icons()
/mob/living/simple_animal/pet/dog/corgi/show_inv(mob/user)
- user.set_machine(src)
- if(user.stat)
+ if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK))
return
+ user.set_machine(src)
var/dat = "
Inventory of [name]
"
- if(inventory_head)
- dat += " Head: [inventory_head] (Remove)"
- else
- dat += " Head:Nothing"
- if(inventory_back)
- dat += " Back: [inventory_back] (Remove)"
- else
- dat += " Back:Nothing"
+ dat += " Head:[inventory_head]" : "add_inv=head'>Nothing"]"
+ dat += " Back:[inventory_back]" : "add_inv=back'>Nothing"]"
+ dat += " Collar:[pcollar]" : "add_inv=collar'>Nothing"]"
- user << browse(dat, text("window=mob[];size=325x500", real_name))
- onclose(user, "mob[real_name]")
- return
+ user << browse(dat, "window=mob[REF(src)];size=325x500")
+ onclose(user, "mob[REF(src)]")
/mob/living/simple_animal/pet/dog/corgi/getarmor(def_zone, type)
var/armorval = 0
@@ -128,7 +138,7 @@
if(do_after(user, 50, target = src))
user.visible_message("[user] shaves [src]'s hair using \the [O].")
playsound(loc, 'sound/items/welder2.ogg', 20, 1)
- shaved = 1
+ shaved = TRUE
icon_living = "[initial(icon_living)]_shaved"
icon_dead = "[initial(icon_living)]_shaved_dead"
if(stat == CONSCIOUS)
@@ -147,18 +157,18 @@
L.visible_message("[L] scoops up [src]!")
/mob/living/simple_animal/pet/dog/corgi/Topic(href, href_list)
- if(usr.stat)
+ if(!(iscarbon(usr) || iscyborg(usr)) || !usr.canUseTopic(src, BE_CLOSE, FALSE, NO_TK))
+ usr << browse(null, "window=mob[REF(src)]")
+ usr.unset_machine()
return
//Removing from inventory
if(href_list["remove_inv"])
- if(!Adjacent(usr) || !(ishuman(usr) || ismonkey(usr) || iscyborg(usr) || isalienadult(usr)))
- return
var/remove_from = href_list["remove_inv"]
switch(remove_from)
if(BODY_ZONE_HEAD)
if(inventory_head)
- inventory_head.forceMove(drop_location())
+ usr.put_in_hands(inventory_head)
inventory_head = null
update_corgi_fluff()
regenerate_icons()
@@ -167,24 +177,32 @@
return
if("back")
if(inventory_back)
- inventory_back.forceMove(drop_location())
+ usr.put_in_hands(inventory_back)
inventory_back = null
update_corgi_fluff()
regenerate_icons()
else
to_chat(usr, "There is nothing to remove from its [remove_from].")
return
+ if("collar")
+ if(pcollar)
+ usr.put_in_hands(pcollar)
+ pcollar = null
+ update_corgi_fluff()
+ regenerate_icons()
show_inv(usr)
//Adding things to inventory
else if(href_list["add_inv"])
- if(!Adjacent(usr) || !(ishuman(usr) || ismonkey(usr) || iscyborg(usr) || isalienadult(usr)))
- return
var/add_to = href_list["add_inv"]
switch(add_to)
+ if("collar")
+ add_collar(usr.get_active_held_item(), usr)
+ update_corgi_fluff()
+
if(BODY_ZONE_HEAD)
place_on_head(usr.get_active_held_item(),usr)
@@ -229,7 +247,7 @@
show_inv(usr)
else
- ..()
+ return ..()
//Corgis are supposed to be simpler, so only a select few objects can actually be put
//to be compatible with them. The objects are below.
@@ -560,7 +578,7 @@
icon_state = "void_puppy"
icon_living = "void_puppy"
icon_dead = "void_puppy_dead"
- nofur = 1
+ nofur = TRUE
unsuitable_atmos_damage = 0
minbodytemp = TCMB
maxbodytemp = T0C + 40
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 cf3742fcc5..d7d4d1b9f2 100644
--- a/code/modules/mob/living/simple_animal/friendly/drone/_drone.dm
+++ b/code/modules/mob/living/simple_animal/friendly/drone/_drone.dm
@@ -37,6 +37,7 @@
gender = NEUTER
mob_biotypes = list(MOB_ROBOTIC)
speak_emote = list("chirps")
+ speech_span = SPAN_ROBOT
bubble_icon = "machine"
initial_language_holder = /datum/language_holder/drone
mob_size = MOB_SIZE_SMALL
@@ -92,7 +93,7 @@
var/obj/item/I = new default_hatmask(src)
equip_to_slot_or_del(I, SLOT_HEAD)
- access_card.item_flags |= NODROP
+ ADD_TRAIT(access_card, TRAIT_NODROP, ABSTRACT_ITEM_TRAIT)
alert_drones(DRONE_NET_CONNECT)
@@ -159,7 +160,7 @@
if(mind)
mind.transfer_to(R, 1)
else
- R.key = key
+ transfer_ckey(R)
qdel(src)
diff --git a/code/modules/mob/living/simple_animal/friendly/drone/drones_as_items.dm b/code/modules/mob/living/simple_animal/friendly/drone/drones_as_items.dm
index a655bdf231..e54b21724d 100644
--- a/code/modules/mob/living/simple_animal/friendly/drone/drones_as_items.dm
+++ b/code/modules/mob/living/simple_animal/friendly/drone/drones_as_items.dm
@@ -61,5 +61,5 @@
var/obj/item/new_hat = new hat_type(D)
D.equip_to_slot_or_del(new_hat, SLOT_HEAD)
D.flags_1 |= (flags_1 & ADMIN_SPAWNED_1)
- D.key = user.key
+ user.transfer_ckey(D, FALSE)
qdel(src)
diff --git a/code/modules/mob/living/simple_animal/friendly/drone/extra_drone_types.dm b/code/modules/mob/living/simple_animal/friendly/drone/extra_drone_types.dm
index 807c52ea46..d28b98263c 100644
--- a/code/modules/mob/living/simple_animal/friendly/drone/extra_drone_types.dm
+++ b/code/modules/mob/living/simple_animal/friendly/drone/extra_drone_types.dm
@@ -49,7 +49,7 @@
. = ..()
GET_COMPONENT_FROM(hidden_uplink, /datum/component/uplink, internal_storage)
hidden_uplink.telecrystals = 30
- var/obj/item/implant/weapons_auth/W = new/obj/item/implant/weapons_auth(src)
+ var/obj/item/implant/weapons_auth/W = new
W.implant(src)
/mob/living/simple_animal/drone/snowflake
@@ -222,7 +222,7 @@
if(.)
update_icons()
-/mob/living/simple_animal/drone/cogscarab/Knockdown(amount, updating = 1, ignore_canknockdown = 0)
+/mob/living/simple_animal/drone/cogscarab/Knockdown(amount, updating = TRUE, ignore_canknockdown = FALSE, override_hardstun, override_stamdmg)
. = ..()
if(.)
update_icons()
diff --git a/code/modules/mob/living/simple_animal/friendly/drone/inventory.dm b/code/modules/mob/living/simple_animal/friendly/drone/inventory.dm
index 3f344c2936..6e89f045da 100644
--- a/code/modules/mob/living/simple_animal/friendly/drone/inventory.dm
+++ b/code/modules/mob/living/simple_animal/friendly/drone/inventory.dm
@@ -19,7 +19,7 @@
return 0
-/mob/living/simple_animal/drone/can_equip(obj/item/I, slot)
+/mob/living/simple_animal/drone/can_equip(obj/item/I, slot, disable_warning = FALSE, bypass_equip_delay_self = FALSE)
switch(slot)
if(SLOT_HEAD)
if(head)
diff --git a/code/modules/mob/living/simple_animal/friendly/drone/say.dm b/code/modules/mob/living/simple_animal/friendly/drone/say.dm
index 16bf370f02..7a5967948f 100644
--- a/code/modules/mob/living/simple_animal/friendly/drone/say.dm
+++ b/code/modules/mob/living/simple_animal/friendly/drone/say.dm
@@ -1,13 +1,3 @@
-/////////////
-//DRONE SAY//
-/////////////
-//Drone speach
-
-/mob/living/simple_animal/drone/get_spans()
- return ..() | SPAN_ROBOT
-
-
-
//Base proc for anything to call
/proc/_alert_drones(msg, dead_can_hear = 0, atom/source, mob/living/faction_checked_mob, exact_faction_match)
if (dead_can_hear && source)
@@ -31,7 +21,7 @@
/mob/living/simple_animal/drone/proc/drone_chat(msg)
- alert_drones("Drone Chat: [name][say_quote(msg, get_spans())]", TRUE)
+ alert_drones("Drone Chat: [name][say_quote(msg)]", TRUE)
/mob/living/simple_animal/drone/binarycheck()
return TRUE
diff --git a/code/modules/mob/living/simple_animal/friendly/pet.dm b/code/modules/mob/living/simple_animal/friendly/pet.dm
index 8df0ee83fe..c24dc6857a 100644
--- a/code/modules/mob/living/simple_animal/friendly/pet.dm
+++ b/code/modules/mob/living/simple_animal/friendly/pet.dm
@@ -2,21 +2,30 @@
icon = 'icons/mob/pets.dmi'
mob_size = MOB_SIZE_SMALL
mob_biotypes = list(MOB_ORGANIC, MOB_BEAST)
- var/obj/item/clothing/neck/petcollar/pcollar
- var/collar_type
- var/unique_pet = FALSE
blood_volume = BLOOD_VOLUME_NORMAL
+ var/unique_pet = FALSE // if the mob can be renamed
+ var/obj/item/clothing/neck/petcollar/pcollar
+ var/collar_type //if the mob has collar sprites, define them.
+
+/mob/living/simple_animal/pet/handle_atom_del(atom/A)
+ if(A == pcollar)
+ pcollar = null
+ return ..()
+
+/mob/living/simple_animal/pet/proc/add_collar(obj/item/clothing/neck/petcollar/P, mob/user)
+ if(QDELETED(P) || pcollar)
+ return
+ if(!user.transferItemToLoc(P, src))
+ return
+ pcollar = P
+ regenerate_icons()
+ to_chat(user, "You put the [P] around [src]'s neck.")
+ if(P.tagname && !unique_pet)
+ fully_replace_character_name(null, "\proper [P.tagname]")
/mob/living/simple_animal/pet/attackby(obj/item/O, mob/user, params)
if(istype(O, /obj/item/clothing/neck/petcollar) && !pcollar && collar_type)
- var/obj/item/clothing/neck/petcollar/P = O
- pcollar = P.type
- regenerate_icons()
- to_chat(user, "You put the [P] around [src]'s neck.")
- if(P.tagname && !unique_pet)
- real_name = "\proper [P.tagname]"
- name = real_name
- qdel(P)
+ add_collar(O, user)
return
if(istype(O, /obj/item/newspaper))
@@ -35,12 +44,16 @@
pcollar = new(src)
regenerate_icons()
+/mob/living/simple_animal/pet/Destroy()
+ QDEL_NULL(pcollar)
+ return ..()
+
/mob/living/simple_animal/pet/revive(full_heal = 0, admin_revive = 0)
- if(..())
+ . = ..()
+ if(.)
if(collar_type)
collar_type = "[initial(collar_type)]"
regenerate_icons()
- . = TRUE
/mob/living/simple_animal/pet/death(gibbed)
..(gibbed)
@@ -50,7 +63,8 @@
/mob/living/simple_animal/pet/gib()
if(pcollar)
- new pcollar(drop_location())
+ pcollar.forceMove(drop_location())
+ pcollar = null
..()
/mob/living/simple_animal/pet/regenerate_icons()
diff --git a/code/modules/mob/living/simple_animal/guardian/guardian.dm b/code/modules/mob/living/simple_animal/guardian/guardian.dm
index 1a918766b6..e3ef14c784 100644
--- a/code/modules/mob/living/simple_animal/guardian/guardian.dm
+++ b/code/modules/mob/living/simple_animal/guardian/guardian.dm
@@ -43,8 +43,8 @@ GLOBAL_LIST_EMPTY(parasites) //all currently existing/living guardians
var/list/guardian_overlays[GUARDIAN_TOTAL_LAYERS]
var/reset = 0 //if the summoner has reset the guardian already
var/cooldown = 0
- var/mob/living/summoner
- var/range = 10 //how far from the user the spirit can be
+ var/mob/living/carbon/summoner
+ var/range = 13 //how far from the user the spirit can be
var/toggle_button_type = /obj/screen/guardian/ToggleMode/Inactive //what sort of toggle button the hud uses
var/datum/guardianname/namedatum = new/datum/guardianname()
var/playstyle_string = "You are a standard Guardian. You shouldn't exist!"
@@ -149,6 +149,9 @@ GLOBAL_LIST_EMPTY(parasites) //all currently existing/living guardians
death(TRUE)
qdel(src)
snapback()
+ if(HAS_TRAIT(summoner, TRAIT_NODEATH) && (istype(summoner.wear_neck, /obj/item/clothing/neck/necklace/memento_mori)))
+ REMOVE_TRAIT(summoner, TRAIT_NODEATH, "memento_mori")
+ to_chat(summoner,"You feel incredibly vulnerable as the memento mori pulls your life force in one too many directions!")
/mob/living/simple_animal/hostile/guardian/Stat()
..()
@@ -426,9 +429,9 @@ GLOBAL_LIST_EMPTY(parasites) //all currently existing/living guardians
to_chat(G, "Your user reset you, and your body was taken over by a ghost. Looks like they weren't happy with your performance.")
to_chat(src, "Your [G.real_name] has been successfully reset.")
message_admins("[key_name_admin(C)] has taken control of ([key_name_admin(G)])")
- G.ghostize(0)
+ G.ghostize(FALSE)
G.setthemename(G.namedatum.theme) //give it a new color, to show it's a new person
- G.key = C.key
+ C.transfer_ckey(G)
G.reset = 1
switch(G.namedatum.theme)
if("tech")
diff --git a/code/modules/mob/living/simple_animal/guardian/types/assassin.dm b/code/modules/mob/living/simple_animal/guardian/types/assassin.dm
index 45d8c17d0c..e507a4c831 100644
--- a/code/modules/mob/living/simple_animal/guardian/types/assassin.dm
+++ b/code/modules/mob/living/simple_animal/guardian/types/assassin.dm
@@ -1,7 +1,5 @@
//Assassin
/mob/living/simple_animal/hostile/guardian/assassin
- melee_damage_lower = 15
- melee_damage_upper = 15
attacktext = "slashes"
attack_sound = 'sound/weapons/bladeslice.ogg'
damage_coeff = list(BRUTE = 1, BURN = 1, TOX = 1, CLONE = 1, STAMINA = 0, OXY = 1)
@@ -12,7 +10,7 @@
toggle_button_type = /obj/screen/guardian/ToggleMode/Assassin
var/toggle = FALSE
- var/stealthcooldown = 160
+ var/stealthcooldown = 100
var/obj/screen/alert/canstealthalert
var/obj/screen/alert/instealthalert
diff --git a/code/modules/mob/living/simple_animal/guardian/types/charger.dm b/code/modules/mob/living/simple_animal/guardian/types/charger.dm
index 7a4c454f9f..3ece5d4e27 100644
--- a/code/modules/mob/living/simple_animal/guardian/types/charger.dm
+++ b/code/modules/mob/living/simple_animal/guardian/types/charger.dm
@@ -1,13 +1,10 @@
//Charger
/mob/living/simple_animal/hostile/guardian/charger
- melee_damage_lower = 15
- melee_damage_upper = 15
ranged = 1 //technically
ranged_message = "charges"
- ranged_cooldown_time = 40
- speed = -1
- damage_coeff = list(BRUTE = 0.6, BURN = 0.6, TOX = 0.6, CLONE = 0.6, STAMINA = 0, OXY = 0.6)
- playstyle_string = "As a charger type you do medium damage, have medium damage resistance, move very fast, and can charge at a location, damaging any target hit and forcing them to drop any items they are holding."
+ ranged_cooldown_time = 20
+ damage_coeff = list(BRUTE = 0, BURN = 0.5, TOX = 0.5, CLONE = 0.5, STAMINA = 0, OXY = 0.5)
+ playstyle_string = "As a charger type you do medium damage, take half damage, immunity to brute damage, move very fast, and can charge at a location, damaging any target hit and forcing them to drop any items they are holding."
magic_fluff_string = "..And draw the Hunter, an alien master of rapid assault."
tech_fluff_string = "Boot sequence complete. Charge modules loaded. Holoparasite swarm online."
carp_fluff_string = "CARP CARP CARP! Caught one! It's a charger carp, that likes running at people. But it doesn't have any legs..."
diff --git a/code/modules/mob/living/simple_animal/guardian/types/dextrous.dm b/code/modules/mob/living/simple_animal/guardian/types/dextrous.dm
index e7dbbda242..52a007e24c 100644
--- a/code/modules/mob/living/simple_animal/guardian/types/dextrous.dm
+++ b/code/modules/mob/living/simple_animal/guardian/types/dextrous.dm
@@ -3,7 +3,7 @@
melee_damage_lower = 10
melee_damage_upper = 10
damage_coeff = list(BRUTE = 0.75, BURN = 0.75, TOX = 0.75, CLONE = 0.75, STAMINA = 0, OXY = 0.75)
- playstyle_string = "As a dextrous type you can hold items, store an item within yourself, and have medium damage resistance, but do low damage on attacks. Recalling and leashing will force you to drop unstored items!"
+ playstyle_string = "As a dextrous type you can hold items, store an item within yourself, and take half damage, but do low damage on attacks. Recalling and leashing will force you to drop unstored items!"
magic_fluff_string = "..And draw the Drone, a dextrous master of construction and repair."
tech_fluff_string = "Boot sequence complete. Dextrous combat modules loaded. Holoparasite swarm online."
carp_fluff_string = "CARP CARP CARP! You caught one! It can hold stuff in its fins, sort of."
@@ -52,7 +52,7 @@
return 1
return 0
-/mob/living/simple_animal/hostile/guardian/dextrous/can_equip(obj/item/I, slot)
+/mob/living/simple_animal/hostile/guardian/dextrous/can_equip(obj/item/I, slot, disable_warning = FALSE, bypass_equip_delay_self = FALSE)
switch(slot)
if(SLOT_GENERC_DEXTROUS_STORAGE)
if(internal_storage)
diff --git a/code/modules/mob/living/simple_animal/guardian/types/explosive.dm b/code/modules/mob/living/simple_animal/guardian/types/explosive.dm
index 8fb1de18df..531c513819 100644
--- a/code/modules/mob/living/simple_animal/guardian/types/explosive.dm
+++ b/code/modules/mob/living/simple_animal/guardian/types/explosive.dm
@@ -1,10 +1,7 @@
//Bomb
/mob/living/simple_animal/hostile/guardian/bomb
- melee_damage_lower = 15
- melee_damage_upper = 15
damage_coeff = list(BRUTE = 0.6, BURN = 0.6, TOX = 0.6, CLONE = 0.6, STAMINA = 0, OXY = 0.6)
- range = 13
- playstyle_string = "As an explosive type, you have moderate close combat abilities, may explosively teleport targets on attack, and are capable of converting nearby items and objects into disguised bombs via alt click."
+ playstyle_string = "As an explosive type, you have moderate close combat abilities, take half damage, may explosively teleport targets on attack, and are capable of converting nearby items and objects into disguised bombs via alt click."
magic_fluff_string = "..And draw the Scientist, master of explosive death."
tech_fluff_string = "Boot sequence complete. Explosive modules active. Holoparasite swarm online."
carp_fluff_string = "CARP CARP CARP! Caught one! It's an explosive carp! Boom goes the fishy."
@@ -22,7 +19,7 @@
var/mob/living/M = target
if(!M.anchored && M != summoner && !hasmatchingsummoner(M))
new /obj/effect/temp_visual/guardian/phase/out(get_turf(M))
- do_teleport(M, M, 10)
+ do_teleport(M, M, 10, channel = TELEPORT_CHANNEL_BLUESPACE)
for(var/mob/living/L in range(1, M))
if(hasmatchingsummoner(L)) //if the summoner matches don't hurt them
continue
diff --git a/code/modules/mob/living/simple_animal/guardian/types/fire.dm b/code/modules/mob/living/simple_animal/guardian/types/fire.dm
index 7a469dd12c..b111caae50 100644
--- a/code/modules/mob/living/simple_animal/guardian/types/fire.dm
+++ b/code/modules/mob/living/simple_animal/guardian/types/fire.dm
@@ -1,13 +1,13 @@
//Fire
/mob/living/simple_animal/hostile/guardian/fire
a_intent = INTENT_HELP
- melee_damage_lower = 7
- melee_damage_upper = 7
+ melee_damage_lower = 10
+ melee_damage_upper = 10
attack_sound = 'sound/items/welder.ogg'
attacktext = "ignites"
- damage_coeff = list(BRUTE = 0.7, BURN = 0.7, TOX = 0.7, CLONE = 0.7, STAMINA = 0, OXY = 0.7)
- range = 7
- playstyle_string = "As a chaos type, you have only light damage resistance, but will ignite any enemy you bump into. In addition, your melee attacks will cause human targets to see everyone as you."
+ melee_damage_type = BURN
+ damage_coeff = list(BRUTE = 0.7, BURN = 0, TOX = 0.7, CLONE = 0.7, STAMINA = 0, OXY = 0.7)
+ playstyle_string = "As a chaos type, you take 30% damage reduction to all but burn, which you are immune to. You will ignite any enemy you bump into. in addition, your melee attacks will cause human targets to see everyone as you."
magic_fluff_string = "..And draw the Wizard, bringer of endless chaos!"
tech_fluff_string = "Boot sequence complete. Crowd control modules activated. Holoparasite swarm online."
carp_fluff_string = "CARP CARP CARP! You caught one! OH GOD, EVERYTHING'S ON FIRE. Except you and the fish."
@@ -38,6 +38,6 @@
/mob/living/simple_animal/hostile/guardian/fire/proc/collision_ignite(AM as mob|obj)
if(isliving(AM))
var/mob/living/M = AM
- if(!hasmatchingsummoner(M) && M != summoner && M.fire_stacks < 7)
- M.fire_stacks = 7
+ if(!hasmatchingsummoner(M) && M != summoner && M.fire_stacks < 10)
+ M.fire_stacks = 10
M.IgniteMob()
diff --git a/code/modules/mob/living/simple_animal/guardian/types/lightning.dm b/code/modules/mob/living/simple_animal/guardian/types/lightning.dm
index ad1c47732b..7b7651822a 100644
--- a/code/modules/mob/living/simple_animal/guardian/types/lightning.dm
+++ b/code/modules/mob/living/simple_animal/guardian/types/lightning.dm
@@ -4,14 +4,13 @@
layer = LYING_MOB_LAYER
/mob/living/simple_animal/hostile/guardian/beam
- melee_damage_lower = 7
- melee_damage_upper = 7
+ melee_damage_lower = 10
+ melee_damage_upper = 10
attacktext = "shocks"
melee_damage_type = BURN
attack_sound = 'sound/machines/defib_zap.ogg'
damage_coeff = list(BRUTE = 0.7, BURN = 0.7, TOX = 0.7, CLONE = 0.7, STAMINA = 0, OXY = 0.7)
- range = 7
- playstyle_string = "As a lightning type, you will apply lightning chains to targets on attack and have a lightning chain to your summoner. Lightning chains will shock anyone near them."
+ playstyle_string = "As a lightning type, you have 30% damage reduction, apply lightning chains to targets on attack and have a lightning chain to your summoner. Lightning chains will shock anyone near them."
magic_fluff_string = "..And draw the Tesla, a shocking, lethal source of power."
tech_fluff_string = "Boot sequence complete. Lightning modules active. Holoparasite swarm online."
carp_fluff_string = "CARP CARP CARP! Caught one! It's a lightning carp! Everyone else goes zap zap."
@@ -31,7 +30,7 @@
var/datum/beam/C = pick(enemychains)
qdel(C)
enemychains -= C
- enemychains += Beam(target, "lightning[rand(1,12)]", time=70, maxdistance=7, beam_type=/obj/effect/ebeam/chain)
+ enemychains += Beam(target, "lightning[rand(1,12)]", time=70, maxdistance=13, beam_type=/obj/effect/ebeam/chain)
/mob/living/simple_animal/hostile/guardian/beam/Destroy()
removechains()
diff --git a/code/modules/mob/living/simple_animal/guardian/types/protector.dm b/code/modules/mob/living/simple_animal/guardian/types/protector.dm
index 14430bb269..53964254cd 100644
--- a/code/modules/mob/living/simple_animal/guardian/types/protector.dm
+++ b/code/modules/mob/living/simple_animal/guardian/types/protector.dm
@@ -1,7 +1,5 @@
//Protector
/mob/living/simple_animal/hostile/guardian/protector
- melee_damage_lower = 15
- melee_damage_upper = 15
range = 15 //worse for it due to how it leashes
damage_coeff = list(BRUTE = 0.4, BURN = 0.4, TOX = 0.4, CLONE = 0.4, STAMINA = 0, OXY = 0.4)
playstyle_string = "As a protector type you cause your summoner to leash to you instead of you leashing to them and have two modes; Combat Mode, where you do and take medium damage, and Protection Mode, where you do and take almost no damage, but move slightly slower."
@@ -33,9 +31,9 @@
cooldown = world.time + 10
if(toggle)
cut_overlays()
- melee_damage_lower = initial(melee_damage_lower)
- melee_damage_upper = initial(melee_damage_upper)
- speed = initial(speed)
+ melee_damage_lower = 15
+ melee_damage_upper = 15
+ speed = 0
damage_coeff = list(BRUTE = 0.4, BURN = 0.4, TOX = 0.4, CLONE = 0.4, STAMINA = 0, OXY = 0.4)
to_chat(src, "You switch to combat mode.")
toggle = FALSE
@@ -44,8 +42,8 @@
if(namedatum)
shield_overlay.color = namedatum.colour
add_overlay(shield_overlay)
- melee_damage_lower = 2
- melee_damage_upper = 2
+ melee_damage_lower = 5
+ melee_damage_upper = 5
speed = 1
damage_coeff = list(BRUTE = 0.05, BURN = 0.05, TOX = 0.05, CLONE = 0.05, STAMINA = 0, OXY = 0.05) //damage? what's damage?
to_chat(src, "You switch to protection mode.")
diff --git a/code/modules/mob/living/simple_animal/guardian/types/ranged.dm b/code/modules/mob/living/simple_animal/guardian/types/ranged.dm
index 5adcc8b292..0e8f632dbd 100644
--- a/code/modules/mob/living/simple_animal/guardian/types/ranged.dm
+++ b/code/modules/mob/living/simple_animal/guardian/types/ranged.dm
@@ -16,8 +16,7 @@
ranged_cooldown_time = 1 //fast!
projectilesound = 'sound/effects/hit_on_shattered_glass.ogg'
ranged = 1
- range = 13
- playstyle_string = "As a ranged type, you have only light damage resistance, but are capable of spraying shards of crystal at incredibly high speed. You can also deploy surveillance snares to monitor enemy movement. Finally, you can switch to scout mode, in which you can't attack, but can move without limit."
+ playstyle_string = "As a ranged type, you have 10% damage reduction, but are capable of spraying shards of crystal at incredibly high speed. You can also deploy surveillance snares to monitor enemy movement. Finally, you can switch to scout mode, in which you can't attack, but can move without limit."
magic_fluff_string = "..And draw the Sentinel, an alien master of ranged combat."
tech_fluff_string = "Boot sequence complete. Ranged combat modules active. Holoparasite swarm online."
carp_fluff_string = "CARP CARP CARP! Caught one, it's a ranged carp. This fishy can watch people pee in the ocean."
@@ -36,7 +35,7 @@
obj_damage = initial(obj_damage)
environment_smash = initial(environment_smash)
alpha = 255
- range = initial(range)
+ range = 13
to_chat(src, "You switch to combat mode.")
toggle = FALSE
else
diff --git a/code/modules/mob/living/simple_animal/guardian/types/standard.dm b/code/modules/mob/living/simple_animal/guardian/types/standard.dm
index 4edd9d9e41..2285167df5 100644
--- a/code/modules/mob/living/simple_animal/guardian/types/standard.dm
+++ b/code/modules/mob/living/simple_animal/guardian/types/standard.dm
@@ -3,9 +3,9 @@
melee_damage_lower = 20
melee_damage_upper = 20
obj_damage = 80
- next_move_modifier = 0.8 //attacks 20% faster
+ next_move_modifier = 0.5 //attacks 50% faster
environment_smash = ENVIRONMENT_SMASH_WALLS
- playstyle_string = "As a standard type you have no special abilities, but have a high damage resistance and a powerful attack capable of smashing through walls."
+ playstyle_string = "As a standard type you have no special abilities, but take half damage and have powerful attack capable of smashing through walls."
magic_fluff_string = "..And draw the Assistant, faceless and generic, but never to be underestimated."
tech_fluff_string = "Boot sequence complete. Standard combat modules loaded. Holoparasite swarm online."
carp_fluff_string = "CARP CARP CARP! You caught one! It's really boring and standard. Better punch some walls to ease the tension."
diff --git a/code/modules/mob/living/simple_animal/guardian/types/support.dm b/code/modules/mob/living/simple_animal/guardian/types/support.dm
index 8ef70e439f..8bf1874d84 100644
--- a/code/modules/mob/living/simple_animal/guardian/types/support.dm
+++ b/code/modules/mob/living/simple_animal/guardian/types/support.dm
@@ -2,11 +2,8 @@
/mob/living/simple_animal/hostile/guardian/healer
a_intent = INTENT_HARM
friendly = "heals"
- speed = 0
damage_coeff = list(BRUTE = 0.7, BURN = 0.7, TOX = 0.7, CLONE = 0.7, STAMINA = 0, OXY = 0.7)
- melee_damage_lower = 15
- melee_damage_upper = 15
- playstyle_string = "As a support type, you may toggle your basic attacks to a healing mode. In addition, Alt-Clicking on an adjacent object or mob will warp them to your bluespace beacon after a short delay."
+ playstyle_string = "As a support type, you have 30% damage reduction and may toggle your basic attacks to a healing mode. In addition, Alt-Clicking on an adjacent object or mob will warp them to your bluespace beacon after a short delay."
magic_fluff_string = "..And draw the CMO, a potent force of life... and death."
carp_fluff_string = "CARP CARP CARP! You caught a support carp. It's a kleptocarp!"
tech_fluff_string = "Boot sequence complete. Support modules active. Holoparasite swarm online."
@@ -142,5 +139,5 @@
L.flash_act()
A.visible_message("[A] disappears in a flash of light!", \
"Your vision is obscured by a flash of light!")
- do_teleport(A, beacon, 0)
+ do_teleport(A, beacon, 0, channel = TELEPORT_CHANNEL_BLUESPACE)
new /obj/effect/temp_visual/guardian/phase(get_turf(A))
diff --git a/code/modules/mob/living/simple_animal/hostile/giant_spider.dm b/code/modules/mob/living/simple_animal/hostile/giant_spider.dm
index cfdf302d6b..f5b1706f87 100644
--- a/code/modules/mob/living/simple_animal/hostile/giant_spider.dm
+++ b/code/modules/mob/living/simple_animal/hostile/giant_spider.dm
@@ -90,7 +90,7 @@
if(key)
to_chat(user, "Someone else already took this spider.")
return 1
- key = user.key
+ user.transfer_ckey(src, FALSE)
return 1
//nursemaids - these create webs and eggs
diff --git a/code/modules/mob/living/simple_animal/hostile/goose.dm b/code/modules/mob/living/simple_animal/hostile/goose.dm
new file mode 100644
index 0000000000..b67770fb4b
--- /dev/null
+++ b/code/modules/mob/living/simple_animal/hostile/goose.dm
@@ -0,0 +1,35 @@
+#define GOOSE_SATIATED 50
+
+/mob/living/simple_animal/hostile/retaliate/goose
+ name = "goose"
+ desc = "It's loose"
+ icon_state = "goose" // sprites by cogwerks from goonstation, used with permission
+ icon_living = "goose"
+ icon_dead = "goose_dead"
+ mob_biotypes = list(MOB_ORGANIC, MOB_BEAST)
+ speak_chance = 0
+ turns_per_move = 5
+ butcher_results = list(/obj/item/reagent_containers/food/snacks/meat = 2)
+ response_help = "pets"
+ response_disarm = "gently pushes aside"
+ response_harm = "kicks"
+ emote_taunt = list("hisses")
+ taunt_chance = 30
+ speed = 0
+ maxHealth = 25
+ health = 25
+ harm_intent_damage = 5
+ melee_damage_lower = 5
+ melee_damage_upper = 5
+ attacktext = "pecks"
+ attack_sound = "goose"
+ speak_emote = list("honks")
+ faction = list("neutral")
+ attack_same = TRUE
+ gold_core_spawnable = HOSTILE_SPAWN
+ var/random_retaliate = TRUE
+
+/mob/living/simple_animal/hostile/retaliate/goose/handle_automated_movement()
+ . = ..()
+ if(prob(5) && random_retaliate == TRUE)
+ Retaliate()
diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm
index 7cf8defc0f..b43bf2bb51 100644
--- a/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm
+++ b/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm
@@ -585,7 +585,7 @@ Difficulty: Very Hard
var/be_helper = alert("Become a Lightgeist? (Warning, You can no longer be cloned!)",,"Yes","No")
if(be_helper == "Yes" && !QDELETED(src) && isobserver(user))
var/mob/living/simple_animal/hostile/lightgeist/W = new /mob/living/simple_animal/hostile/lightgeist(get_turf(loc))
- W.key = user.key
+ user.transfer_ckey(W, FALSE)
/obj/machinery/anomalous_crystal/helpers/Topic(href, href_list)
@@ -649,7 +649,7 @@ Difficulty: Very Hard
L.heal_overall_damage(heal_power, heal_power)
new /obj/effect/temp_visual/heal(get_turf(target), "#80F5FF")
-/mob/living/simple_animal/hostile/lightgeist/ghostize()
+/mob/living/simple_animal/hostile/lightgeist/ghostize(can_reenter_corpse = TRUE, send_the_signal = TRUE)
. = ..()
if(.)
death()
@@ -747,6 +747,12 @@ Difficulty: Very Hard
/obj/structure/closet/stasis/ex_act()
return
+/obj/structure/closet/stasis/handle_lock_addition()
+ return
+
+/obj/structure/closet/stasis/handle_lock_removal()
+ return
+
/obj/effect/proc_holder/spell/targeted/exit_possession
name = "Exit Possession"
desc = "Exits the body you are possessing."
diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/dragon_vore.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/dragon_vore.dm
index 26f146b952..8c06b01402 100644
--- a/code/modules/mob/living/simple_animal/hostile/megafauna/dragon_vore.dm
+++ b/code/modules/mob/living/simple_animal/hostile/megafauna/dragon_vore.dm
@@ -42,7 +42,7 @@
autotransferwait = 200
/obj/belly/megafauna/dragon/gut
- name = "stomach"
+ name = "gut"
vore_capacity = 5 //I doubt this many people will actually last in the gut, but...
vore_sound = "Tauric Swallow"
desc = "With a rush of burning ichor greeting you, you're introduced to the Drake's stomach. Wrinkled walls greedily grind against you, acidic slimes working into your body as you become fuel and nutriton for a superior predator. All that's left is your body's willingness to resist your destiny."
diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/hierophant.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/hierophant.dm
index 6577553a6a..cca39cfea6 100644
--- a/code/modules/mob/living/simple_animal/hostile/megafauna/hierophant.dm
+++ b/code/modules/mob/living/simple_animal/hostile/megafauna/hierophant.dm
@@ -158,6 +158,8 @@ Difficulty: Normal
else
burst_range = 3
INVOKE_ASYNC(src, .proc/burst, get_turf(src), 0.25) //melee attacks on living mobs cause it to release a fast burst if on cooldown
+ if(L.stat == CONSCIOUS && L.health >= 30)
+ OpenFire()
else
devour(L)
else
@@ -426,6 +428,7 @@ Difficulty: Normal
/mob/living/simple_animal/hostile/megafauna/hierophant/proc/burst(turf/original, spread_speed = 0.5) //release a wave of blasts
playsound(original,'sound/machines/airlockopen.ogg', 200, 1)
var/last_dist = 0
+ var/list/hit_mobs = list() //don't hit people multiple times.
for(var/t in spiral_range_turfs(burst_range, original))
var/turf/T = t
if(!T)
@@ -434,7 +437,7 @@ Difficulty: Normal
if(dist > last_dist)
last_dist = dist
sleep(1 + min(burst_range - last_dist, 12) * spread_speed) //gets faster as it gets further out
- new /obj/effect/temp_visual/hierophant/blast(T, src, FALSE)
+ new /obj/effect/temp_visual/hierophant/blast(T, src, FALSE, hit_mobs)
/mob/living/simple_animal/hostile/megafauna/hierophant/AltClickOn(atom/A) //player control handler(don't give this to a player holy fuck)
if(!istype(A) || get_dist(A, src) <= 2)
@@ -591,8 +594,10 @@ Difficulty: Normal
var/friendly_fire_check = FALSE
var/bursting = FALSE //if we're bursting and need to hit anyone crossing us
-/obj/effect/temp_visual/hierophant/blast/Initialize(mapload, new_caster, friendly_fire)
+/obj/effect/temp_visual/hierophant/blast/Initialize(mapload, new_caster, friendly_fire, list/only_hit_once)
. = ..()
+ if(only_hit_once)
+ hit_things = only_hit_once
friendly_fire_check = friendly_fire
if(new_caster)
hit_things += new_caster
diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/megafauna.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/megafauna.dm
index 301b270e36..d62f59cdd7 100644
--- a/code/modules/mob/living/simple_animal/hostile/megafauna/megafauna.dm
+++ b/code/modules/mob/living/simple_animal/hostile/megafauna/megafauna.dm
@@ -127,7 +127,7 @@
var/client/C = L.client
SSmedals.UnlockMedal("Boss [BOSS_KILL_MEDAL]", C)
SSmedals.UnlockMedal("[medaltype] [BOSS_KILL_MEDAL]", C)
- if(crusher_kill && istype(L.get_active_held_item(), /obj/item/twohanded/required/kinetic_crusher))
+ if(crusher_kill && istype(L.get_active_held_item(), /obj/item/twohanded/kinetic_crusher))
SSmedals.UnlockMedal("[medaltype] [BOSS_KILL_MEDAL_CRUSHER]", C)
SSmedals.SetScore(BOSS_SCORE, C, 1)
SSmedals.SetScore(score_type, C, 1)
diff --git a/code/modules/mob/living/simple_animal/hostile/retaliate/bat.dm b/code/modules/mob/living/simple_animal/hostile/retaliate/bat.dm
index 5cee4ef1b7..cc54ad3bef 100644
--- a/code/modules/mob/living/simple_animal/hostile/retaliate/bat.dm
+++ b/code/modules/mob/living/simple_animal/hostile/retaliate/bat.dm
@@ -38,3 +38,13 @@
atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0)
minbodytemp = 0
+/mob/living/simple_animal/hostile/retaliate/bat/secbat
+ name = "Security Bat"
+ icon_state = "secbat"
+ icon_living = "secbat"
+ icon_dead = "secbat_dead"
+ icon_gib = "secbat_dead"
+ desc = "A fruit bat with a tiny little security hat who is ready to inject cuteness into any security operation."
+ emote_see = list("is ready to law down the law.", "flaps about with an air of authority.")
+ response_help = "respects the authority of"
+ gold_core_spawnable = FRIENDLY_SPAWN
diff --git a/code/modules/mob/living/simple_animal/hostile/wizard.dm b/code/modules/mob/living/simple_animal/hostile/wizard.dm
index a946cbf45b..f047a7aed1 100644
--- a/code/modules/mob/living/simple_animal/hostile/wizard.dm
+++ b/code/modules/mob/living/simple_animal/hostile/wizard.dm
@@ -46,7 +46,8 @@
fireball.human_req = 0
fireball.player_lock = 0
AddSpell(fireball)
- implants += new /obj/item/implant/exile(src)
+ var/obj/item/implant/exile/I = new
+ I.implant(src, null, TRUE)
mm = new /obj/effect/proc_holder/spell/targeted/projectile/magic_missile
mm.clothes_req = 0
diff --git a/code/modules/mob/living/simple_animal/hostile/zombie.dm b/code/modules/mob/living/simple_animal/hostile/zombie.dm
index 21c2d4804a..7d89941687 100644
--- a/code/modules/mob/living/simple_animal/hostile/zombie.dm
+++ b/code/modules/mob/living/simple_animal/hostile/zombie.dm
@@ -55,4 +55,28 @@
/mob/living/simple_animal/hostile/zombie/drop_loot()
. = ..()
corpse.forceMove(drop_location())
- corpse.create()
\ No newline at end of file
+ corpse.create()
+
+/mob/living/simple_animal/hostile/unemployedclone
+ name = "Failed clone"
+ desc = "Somebody failed chemistry."
+ icon = 'icons/mob/human.dmi'
+ icon_state = "husk"
+ icon_living = "husk"
+ icon_dead = "husk"
+ mob_biotypes = list(MOB_ORGANIC, MOB_HUMANOID)
+ speak_chance = 0
+ stat_attack = UNCONSCIOUS //braains
+ maxHealth = 100
+ health = 100
+ harm_intent_damage = 5
+ melee_damage_lower = 21
+ melee_damage_upper = 21
+ attacktext = "bites"
+ attack_sound = 'sound/hallucinations/growl1.ogg'
+ a_intent = INTENT_HARM
+ atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0)
+ minbodytemp = 0
+ spacewalk = FALSE
+ status_flags = CANPUSH
+ del_on_death = 0
diff --git a/code/modules/mob/living/simple_animal/parrot.dm b/code/modules/mob/living/simple_animal/parrot.dm
index 4fdf431fa0..5e2798cc8e 100644
--- a/code/modules/mob/living/simple_animal/parrot.dm
+++ b/code/modules/mob/living/simple_animal/parrot.dm
@@ -181,94 +181,85 @@
*/
/mob/living/simple_animal/parrot/show_inv(mob/user)
user.set_machine(src)
- var/dat = "
Inventory of [name]
"
- if(ears)
- dat += " Headset: [ears] (Remove)"
- else
- dat += " Headset:Nothing"
- user << browse(dat, "window=mob[real_name];size=325x500")
- onclose(user, "mob[real_name]")
+ var/dat = "
Inventory of [name]
"
+ dat += " Headset:[ears]" : "add_inv=ears'>Nothing"]"
+
+ user << browse(dat, "window=mob[REF(src)];size=325x500")
+ onclose(user, "window=mob[REF(src)]")
/mob/living/simple_animal/parrot/Topic(href, href_list)
-
- //Can the usr physically do this?
- if(usr.incapacitated() || !usr.Adjacent(loc))
+ if(!(iscarbon(usr) || iscyborg(usr)) || !usr.canUseTopic(src, BE_CLOSE, FALSE, NO_TK))
+ usr << browse(null, "window=mob[REF(src)]")
+ usr.unset_machine()
return
- //Is the usr's mob type able to do this? (lolaliens)
- if(ishuman(usr) || ismonkey(usr) || iscyborg(usr) || isalienadult(usr))
+ //Removing from inventory
+ if(href_list["remove_inv"])
+ var/remove_from = href_list["remove_inv"]
+ switch(remove_from)
+ if("ears")
+ if(!ears)
+ to_chat(usr, "There is nothing to remove from its [remove_from]!")
+ return
+ if(!stat)
+ say("[available_channels.len ? "[pick(available_channels)] " : null]BAWWWWWK LEAVE THE HEADSET BAWKKKKK!")
+ ears.forceMove(drop_location())
+ ears = null
+ for(var/possible_phrase in speak)
+ if(copytext(possible_phrase,1,3) in GLOB.department_radio_keys)
+ possible_phrase = copytext(possible_phrase,3)
- //Removing from inventory
- if(href_list["remove_inv"])
- var/remove_from = href_list["remove_inv"]
- switch(remove_from)
- if("ears")
- if(ears)
- if(!stat)
- if(available_channels.len)
- src.say("[pick(available_channels)] BAWWWWWK LEAVE THE HEADSET BAWKKKKK!")
- else
- src.say("BAWWWWWK LEAVE THE HEADSET BAWKKKKK!")
- ears.forceMove(src.loc)
- ears = null
- for(var/possible_phrase in speak)
- if(copytext(possible_phrase,1,3) in GLOB.department_radio_keys)
- possible_phrase = copytext(possible_phrase,3)
- else
- to_chat(usr, "There is nothing to remove from its [remove_from]!")
+ //Adding things to inventory
+ else if(href_list["add_inv"])
+ var/add_to = href_list["add_inv"]
+ if(!usr.get_active_held_item())
+ to_chat(usr, "You have nothing in your hand to put on its [add_to]!")
+ return
+ switch(add_to)
+ if("ears")
+ if(ears)
+ to_chat(usr, "It's already wearing something!")
+ return
+ else
+ var/obj/item/item_to_add = usr.get_active_held_item()
+ if(!item_to_add)
return
- //Adding things to inventory
- else if(href_list["add_inv"])
- var/add_to = href_list["add_inv"]
- if(!usr.get_active_held_item())
- to_chat(usr, "You have nothing in your hand to put on its [add_to]!")
- return
- switch(add_to)
- if("ears")
- if(ears)
- to_chat(usr, "It's already wearing something!")
+ if( !istype(item_to_add, /obj/item/radio/headset) )
+ to_chat(usr, "This object won't fit!")
return
- else
- var/obj/item/item_to_add = usr.get_active_held_item()
- if(!item_to_add)
- return
- if( !istype(item_to_add, /obj/item/radio/headset) )
- to_chat(usr, "This object won't fit!")
- return
+ var/obj/item/radio/headset/headset_to_add = item_to_add
- var/obj/item/radio/headset/headset_to_add = item_to_add
+ if(!usr.transferItemToLoc(headset_to_add, src))
+ return
+ ears = headset_to_add
+ to_chat(usr, "You fit the headset onto [src].")
- if(!usr.transferItemToLoc(headset_to_add, src))
- return
- src.ears = headset_to_add
- to_chat(usr, "You fit the headset onto [src].")
+ clearlist(available_channels)
+ for(var/ch in headset_to_add.channels)
+ switch(ch)
+ if(RADIO_CHANNEL_ENGINEERING)
+ available_channels.Add(RADIO_TOKEN_ENGINEERING)
+ if(RADIO_CHANNEL_COMMAND)
+ available_channels.Add(RADIO_TOKEN_COMMAND)
+ if(RADIO_CHANNEL_SECURITY)
+ available_channels.Add(RADIO_TOKEN_SECURITY)
+ if(RADIO_CHANNEL_SCIENCE)
+ available_channels.Add(RADIO_TOKEN_SCIENCE)
+ if(RADIO_CHANNEL_MEDICAL)
+ available_channels.Add(RADIO_TOKEN_MEDICAL)
+ if(RADIO_CHANNEL_SUPPLY)
+ available_channels.Add(RADIO_TOKEN_SUPPLY)
+ if(RADIO_CHANNEL_SERVICE)
+ available_channels.Add(RADIO_TOKEN_SERVICE)
- clearlist(available_channels)
- for(var/ch in headset_to_add.channels)
- switch(ch)
- if("Engineering")
- available_channels.Add(":e")
- if("Command")
- available_channels.Add(":c")
- if("Security")
- available_channels.Add(":s")
- if("Science")
- available_channels.Add(":n")
- if("Medical")
- available_channels.Add(":m")
- if("Supply")
- available_channels.Add(":u")
- if("Service")
- available_channels.Add(":v")
-
- if(headset_to_add.translate_binary)
- available_channels.Add(":b")
- else
- ..()
+ if(headset_to_add.translate_binary)
+ available_channels.Add(MODE_TOKEN_BINARY)
+ else
+ return ..()
/*
@@ -906,6 +897,11 @@
. = ..()
+/mob/living/simple_animal/parrot/Poly/say(message, bubble_type,var/list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null)
+ . = ..()
+ if(. && !client && prob(1) && prob(1)) //Only the one true bird may speak across dimensions.
+ world.TgsTargetedChatBroadcast("A stray squawk is heard... \"[message]\"", FALSE)
+
/mob/living/simple_animal/parrot/Poly/Life()
if(!stat && SSticker.current_state == GAME_STATE_FINISHED && !memory_saved)
Write_Memory(FALSE)
@@ -920,7 +916,7 @@
if(mind)
mind.transfer_to(G)
else
- G.key = key
+ transfer_ckey(G)
..(gibbed)
/mob/living/simple_animal/parrot/Poly/proc/Read_Memory()
diff --git a/code/modules/mob/living/simple_animal/simple_animal_vr.dm b/code/modules/mob/living/simple_animal/simple_animal_vr.dm
index 4d808a11a9..700ee3b7ce 100644
--- a/code/modules/mob/living/simple_animal/simple_animal_vr.dm
+++ b/code/modules/mob/living/simple_animal/simple_animal_vr.dm
@@ -29,36 +29,41 @@
/mob/living/simple_animal/Destroy()
release_vore_contents(include_absorbed = TRUE, silent = TRUE)
prey_excludes.Cut()
+ QDEL_NULL_LIST(vore_organs)
. = ..()
// Update fullness based on size & quantity of belly contents
/mob/living/simple_animal/proc/update_fullness(var/atom/movable/M)
var/new_fullness = 0
- for(var/I in vore_organs)
- var/datum/belly/B = vore_organs[I]
- if (!(M in B.internal_contents))
+ for(var/belly in vore_organs)
+ var/obj/belly/B = vore_organs[belly]
+ if (!(M in B.contents))
return FALSE // Nothing's inside
new_fullness += M
vore_fullness = new_fullness
/mob/living/simple_animal/death()
- release_vore_contents(silent = TRUE)
+ release_vore_contents()
. = ..()
// Simple animals have only one belly. This creates it (if it isn't already set up)
/mob/living/simple_animal/init_vore()
vore_init = TRUE
- if(vore_organs.len)
+ if(CHECK_BITFIELD(flags_1, HOLOGRAM_1))
return
- if(no_vore) //If it can't vore, let's not give it a stomach.
+ if(!vore_active || no_vore) //If it can't vore, let's not give it a stomach.
return
if(vore_active && !IsAdvancedToolUser()) //vore active, but doesn't have thumbs to grab people with.
verbs |= /mob/living/simple_animal/proc/animal_nom
- var/obj/belly/B = new /obj/belly(src)
+ if(LAZYLEN(vore_organs))
+ return
+
+ LAZYINITLIST(vore_organs)
+ var/obj/belly/B = new (src)
vore_selected = B
- B.immutable = 1
+ B.immutable = TRUE
B.name = vore_stomach_name ? vore_stomach_name : "stomach"
B.desc = vore_stomach_flavor ? vore_stomach_flavor : "Your surroundings are warm, soft, and slimy. Makes sense, considering you're inside \the [name]."
B.digest_mode = vore_default_mode
@@ -105,6 +110,9 @@
var/mob/living/carbon/human/user = usr
if(!istype(user) || user.stat) return
+ if(!vore_active)
+ return
+
if(vore_selected.digest_mode == DM_HOLD)
var/confirm = alert(usr, "Enabling digestion on [name] will cause it to digest all stomach contents. Using this to break OOC prefs is against the rules. Digestion will disable itself after 20 minutes.", "Enabling [name]'s Digestion", "Enable", "Cancel")
if(confirm == "Enable")
@@ -120,13 +128,12 @@
// Simple nom proc for if you get ckey'd into a simple_animal mob! Avoids grabs.
//
/mob/living/simple_animal/proc/animal_nom(var/mob/living/T in oview(1))
- set name = "Animal Nom"
+ set name = "Animal Nom (pull target)"
set category = "Vore"
set desc = "Since you can't grab, you get a verb!"
if (stat != CONSCIOUS)
return
- if (T.devourable == FALSE)
- to_chat(usr, "You can't eat this!")
+ if(!T.devourable)
return
- return vore_attack(usr,T,usr)
+ return vore_attack(src,T,src)
diff --git a/modular_citadel/code/modules/mob/living/simple_animal/simplemob_vore_values.dm b/code/modules/mob/living/simple_animal/simplemob_vore_values.dm
similarity index 92%
rename from modular_citadel/code/modules/mob/living/simple_animal/simplemob_vore_values.dm
rename to code/modules/mob/living/simple_animal/simplemob_vore_values.dm
index 2bfd63a25d..22ed5e3ab4 100644
--- a/modular_citadel/code/modules/mob/living/simple_animal/simplemob_vore_values.dm
+++ b/code/modules/mob/living/simple_animal/simplemob_vore_values.dm
@@ -41,6 +41,10 @@
devourable = TRUE
digestable = TRUE
+/mob/living/simple_animal/kiwi
+ devourable = TRUE
+ digestable = TRUE
+
//STATION PETS
/mob/living/simple_animal/pet
devourable = TRUE
@@ -65,7 +69,6 @@
/mob/living/simple_animal/sloth
devourable = TRUE
digestable = TRUE
- feeding = TRUE
/mob/living/simple_animal/parrot
devourable = TRUE
@@ -84,50 +87,43 @@
/mob/living/simple_animal/hostile/lizard
devourable = TRUE
digestable = TRUE
+ feeding = TRUE
+ vore_active = TRUE
+ isPredator = TRUE
+ vore_default_mode = DM_DIGEST
/mob/living/simple_animal/hostile/alien
- devourable = TRUE
- digestable = TRUE
feeding = TRUE
vore_active = TRUE
isPredator = TRUE
vore_default_mode = DM_DIGEST
/mob/living/simple_animal/hostile/bear
- devourable = TRUE
- digestable = TRUE
feeding = TRUE
vore_active = TRUE
isPredator = TRUE
vore_default_mode = DM_DIGEST
/mob/living/simple_animal/hostile/poison/giant_spider
- devourable = TRUE
- digestable = TRUE
feeding = TRUE
vore_active = TRUE
+ isPredator = TRUE
vore_default_mode = DM_DIGEST
/mob/living/simple_animal/hostile/retaliate/poison/snake
- devourable = TRUE
- digestable = TRUE
feeding = TRUE
vore_active = TRUE
isPredator = TRUE
vore_default_mode = DM_DIGEST
/mob/living/simple_animal/hostile/gorilla
- devourable = TRUE
- digestable = TRUE
feeding = TRUE
vore_active = TRUE
isPredator = TRUE
vore_default_mode = DM_DIGEST
/mob/living/simple_animal/hostile/asteroid/goliath
- devourable = TRUE
- digestable = TRUE
- feeding = TRUE
+ feeding = TRUE //for pet Goliaths I guess or something.
vore_active = TRUE
isPredator = TRUE
vore_default_mode = DM_DIGEST
diff --git a/code/modules/mob/living/simple_animal/slime/life.dm b/code/modules/mob/living/simple_animal/slime/life.dm
index ebb34fe77a..29b4689317 100644
--- a/code/modules/mob/living/simple_animal/slime/life.dm
+++ b/code/modules/mob/living/simple_animal/slime/life.dm
@@ -61,7 +61,7 @@
break
if(Target in view(1,src))
- if(issilicon(Target))
+ if(!CanFeedon(Target)) //If they're not able to be fed upon, ignore them.
if(!Atkcool)
Atkcool = 1
spawn(45)
@@ -69,7 +69,7 @@
if(Target.Adjacent(src))
Target.attack_slime(src)
- return
+ break
if(!Target.lying && prob(80))
if(Target.client && Target.health >= 20)
@@ -600,7 +600,8 @@
phrases += "[M]... friend..."
if (nutrition < get_hunger_nutrition())
phrases += "[M]... feed me..."
- say (pick(phrases))
+ if(!stat)
+ say (pick(phrases))
/mob/living/simple_animal/slime/proc/get_max_nutrition() // Can't go above it
if (is_adult)
diff --git a/code/modules/mob/living/simple_animal/slime/powers.dm b/code/modules/mob/living/simple_animal/slime/powers.dm
index 96e84a1754..bf80ab9ff4 100644
--- a/code/modules/mob/living/simple_animal/slime/powers.dm
+++ b/code/modules/mob/living/simple_animal/slime/powers.dm
@@ -48,34 +48,58 @@
var/mob/living/simple_animal/slime/S = owner
S.Feed()
-/mob/living/simple_animal/slime/proc/CanFeedon(mob/living/M)
+/mob/living/simple_animal/slime/proc/CanFeedon(mob/living/M, silent = FALSE)
if(!Adjacent(M))
- return 0
+ return FALSE
if(buckled)
Feedstop()
- return 0
+ return FALSE
+
+ if(issilicon(M))
+ return FALSE
+
+ if(isanimal(M))
+ var/mob/living/simple_animal/S = M
+ if(S.damage_coeff[TOX] <= 0 && S.damage_coeff[CLONE] <= 0) //The creature wouldn't take any damage, it must be too weird even for us.
+ if(silent)
+ return FALSE
+ to_chat(src, "[pick("This subject is incompatible", \
+ "This subject does not have life energy", "This subject is empty", \
+ "I am not satisified", "I can not feed from this subject", \
+ "I do not feel nourished", "This subject is not food")]!")
+ return FALSE
if(isslime(M))
+ if(silent)
+ return FALSE
to_chat(src, "I can't latch onto another slime...")
- return 0
+ return FALSE
if(docile)
+ if(silent)
+ return FALSE
to_chat(src, "I'm not hungry anymore...")
- return 0
+ return FALSE
if(stat)
+ if(silent)
+ return FALSE
to_chat(src, "I must be conscious to do this...")
- return 0
+ return FALSE
if(M.stat == DEAD)
+ if(silent)
+ return FALSE
to_chat(src, "This subject does not have a strong enough life energy...")
- return 0
+ return FALSE
if(locate(/mob/living/simple_animal/slime) in M.buckled_mobs)
+ if(silent)
+ return FALSE
to_chat(src, "Another slime is already feeding on this subject...")
- return 0
- return 1
+ return FALSE
+ return TRUE
/mob/living/simple_animal/slime/proc/Feedon(mob/living/M)
M.unbuckle_all_mobs(force=1) //Slimes rip other mobs (eg: shoulder parrots) off (Slimes Vs Slimes is already handled in CanFeedon())
@@ -174,7 +198,7 @@
if(src.mind)
src.mind.transfer_to(new_slime)
else
- new_slime.key = src.key
+ transfer_ckey(new_slime)
qdel(src)
else
to_chat(src, "I am not ready to reproduce yet...")
diff --git a/code/modules/mob/living/status_procs.dm b/code/modules/mob/living/status_procs.dm
index 5006bd2920..0880f7f432 100644
--- a/code/modules/mob/living/status_procs.dm
+++ b/code/modules/mob/living/status_procs.dm
@@ -62,15 +62,15 @@
return K.duration - world.time
return 0
-/mob/living/proc/Knockdown(amount, updating = TRUE, ignore_canknockdown = FALSE) //Can't go below remaining duration
+/mob/living/proc/Knockdown(amount, updating = TRUE, ignore_canknockdown = FALSE, override_hardstun, override_stamdmg) //Can't go below remaining duration
if(((status_flags & CANKNOCKDOWN) && !HAS_TRAIT(src, TRAIT_STUNIMMUNE)) || ignore_canknockdown)
- if(absorb_stun(amount, ignore_canknockdown))
+ if(absorb_stun(isnull(override_hardstun)? amount : override_hardstun, ignore_canknockdown))
return
var/datum/status_effect/incapacitating/knockdown/K = IsKnockdown()
if(K)
- K.duration = max(world.time + amount, K.duration)
- else if(amount > 0)
- K = apply_status_effect(STATUS_EFFECT_KNOCKDOWN, amount, updating)
+ K.duration = max(world.time + (isnull(override_hardstun)? amount : override_hardstun), K.duration)
+ else if((amount || override_hardstun) > 0)
+ K = apply_status_effect(STATUS_EFFECT_KNOCKDOWN, amount, updating, override_hardstun, override_stamdmg)
return K
/mob/living/proc/SetKnockdown(amount, updating = TRUE, ignore_canknockdown = FALSE) //Sets remaining duration
diff --git a/code/modules/mob/living/taste.dm b/code/modules/mob/living/taste.dm
index fec024cebf..e4d1aa94a5 100644
--- a/code/modules/mob/living/taste.dm
+++ b/code/modules/mob/living/taste.dm
@@ -32,4 +32,31 @@
last_taste_time = world.time
last_taste_text = text_output
+//FermiChem - How to check pH of a beaker without a meter/pH paper.
+//Basically checks the pH of the holder and burns your poor tongue if it's too acidic!
+//TRAIT_AGEUSIA players can't taste, unless it's burning them.
+//taking sips of a strongly acidic/alkaline substance will burn your tongue.
+/mob/living/carbon/taste(datum/reagents/from)
+ var/obj/item/organ/tongue/T = getorganslot("tongue")
+ if (!T)
+ return
+ .=..()
+ if ((from.pH > 12.5) || (from.pH < 1.5))
+ to_chat(src, "You taste chemical burns!")
+ T.applyOrganDamage(5)
+ if(istype(T, /obj/item/organ/tongue/cybernetic))
+ to_chat(src, "Your tongue moves on it's own in response to the liquid.")
+ say("The pH is appropriately [round(from.pH, 1)].")
+ return
+ if (!HAS_TRAIT(src, TRAIT_AGEUSIA)) //I'll let you get away with not having 1 damage.
+ switch(from.pH)
+ if(11.5 to INFINITY)
+ to_chat(src, "You taste a strong alkaline flavour!")
+ if(8.5 to 11.5)
+ to_chat(src, "You taste a sort of soapy tone in the mixture.")
+ if(2.5 to 5.5)
+ to_chat(src, "You taste a sort of acid tone in the mixture.")
+ if(-INFINITY to 2.5)
+ to_chat(src, "You taste a strong acidic flavour!")
+
#undef DEFAULT_TASTE_SENSITIVITY
diff --git a/code/modules/mob/living/ventcrawling.dm b/code/modules/mob/living/ventcrawling.dm
index 251739c935..930656228d 100644
--- a/code/modules/mob/living/ventcrawling.dm
+++ b/code/modules/mob/living/ventcrawling.dm
@@ -91,14 +91,15 @@ GLOBAL_LIST_INIT(ventcrawl_machinery, typecacheof(list(
if(!totalMembers.len)
return
- for(var/X in totalMembers)
- var/obj/machinery/atmospherics/A = X //all elements in totalMembers are necessarily of this type.
- if(!A.pipe_vision_img)
- A.pipe_vision_img = image(A, A.loc, layer = ABOVE_HUD_LAYER, dir = A.dir)
- A.pipe_vision_img.plane = ABOVE_HUD_PLANE
- pipes_shown += A.pipe_vision_img
- if(client)
- client.images += A.pipe_vision_img
+ if(client)
+ for(var/X in totalMembers)
+ var/obj/machinery/atmospherics/A = X //all elements in totalMembers are necessarily of this type.
+ if(in_view_range(client.mob, A))
+ if(!A.pipe_vision_img)
+ A.pipe_vision_img = image(A, A.loc, layer = ABOVE_HUD_LAYER, dir = A.dir)
+ A.pipe_vision_img.plane = ABOVE_HUD_PLANE
+ client.images += A.pipe_vision_img
+ pipes_shown += A.pipe_vision_img
movement_type |= VENTCRAWLING
diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm
index b098801da8..5a7c6e21ab 100644
--- a/code/modules/mob/mob.dm
+++ b/code/modules/mob/mob.dm
@@ -116,19 +116,23 @@
// vision_distance (optional) define how many tiles away the message can be seen.
// ignored_mob (optional) doesn't show any message to a given mob if TRUE.
-/atom/proc/visible_message(message, self_message, blind_message, vision_distance, ignored_mob)
+/atom/proc/visible_message(message, self_message, blind_message, vision_distance, list/ignored_mobs, no_ghosts = FALSE)
var/turf/T = get_turf(src)
if(!T)
return
+ if(!islist(ignored_mobs))
+ ignored_mobs = list(ignored_mobs)
var/range = 7
if(vision_distance)
range = vision_distance
for(var/mob/M in get_hearers_in_view(range, src))
if(!M.client)
continue
- if(M == ignored_mob)
+ if(M in ignored_mobs)
continue
var/msg = message
+ if(isobserver(M) && no_ghosts)
+ continue
if(M == src) //the src always see the main message or self message
if(self_message)
msg = self_message
@@ -155,7 +159,7 @@
// deaf_message (optional) is what deaf people will see.
// hearing_distance (optional) is the range, how many tiles away the message can be heard.
-/mob/audible_message(message, deaf_message, hearing_distance, self_message)
+/mob/audible_message(message, deaf_message, hearing_distance, self_message, no_ghosts = FALSE)
var/range = 7
if(hearing_distance)
range = hearing_distance
@@ -163,6 +167,8 @@
var/msg = message
if(self_message && M==src)
msg = self_message
+ if(no_ghosts && isobserver(M))
+ continue
M.show_message( msg, 2, deaf_message, 1)
// Show a message to all mobs in earshot of this atom
@@ -171,11 +177,13 @@
// deaf_message (optional) is what deaf people will see.
// hearing_distance (optional) is the range, how many tiles away the message can be heard.
-/atom/proc/audible_message(message, deaf_message, hearing_distance)
+/atom/proc/audible_message(message, deaf_message, hearing_distance, no_ghosts = FALSE)
var/range = 7
if(hearing_distance)
range = hearing_distance
for(var/mob/M in get_hearers_in_view(range, src))
+ if(no_ghosts && isobserver(M))
+ continue
M.show_message( message, 2, deaf_message, 1)
/mob/proc/Life()
@@ -436,7 +444,13 @@
// M.Login() //wat
return
-
+/mob/proc/transfer_ckey(mob/new_mob, send_signal = TRUE)
+ if(!ckey)
+ return FALSE
+ if(send_signal)
+ SEND_SIGNAL(src, COMSIG_MOB_KEY_CHANGE, new_mob, src)
+ new_mob.ckey = ckey
+ return TRUE
/mob/verb/cancel_camera()
set name = "Cancel Camera View"
@@ -444,18 +458,28 @@
reset_perspective(null)
unset_machine()
+GLOBAL_VAR_INIT(exploit_warn_spam_prevention, 0)
+
//suppress the .click/dblclick macros so people can't use them to identify the location of items or aimbot
/mob/verb/DisClick(argu = null as anything, sec = "" as text, number1 = 0 as num , number2 = 0 as num)
set name = ".click"
set hidden = TRUE
set category = null
- return
+ if(GLOB.exploit_warn_spam_prevention < world.time)
+ var/msg = "[key_name_admin(src)]([ADMIN_KICK(src)]) attempted to use the .click macro!"
+ log_admin(msg)
+ message_admins(msg)
+ GLOB.exploit_warn_spam_prevention = world.time + 10
/mob/verb/DisDblClick(argu = null as anything, sec = "" as text, number1 = 0 as num , number2 = 0 as num)
set name = ".dblclick"
set hidden = TRUE
set category = null
- return
+ if(GLOB.exploit_warn_spam_prevention < world.time)
+ var/msg = "[key_name_admin(src)]([ADMIN_KICK(src)]) attempted to use the .dblclick macro!"
+ log_admin(msg)
+ message_admins(msg)
+ GLOB.exploit_warn_spam_prevention = world.time + 10
/mob/Topic(href, href_list)
if(href_list["mach_close"])
@@ -516,7 +540,12 @@
return
if(isAI(M))
return
- show_inv(usr)
+
+/mob/MouseDrop_T(atom/dropping, atom/user)
+ . = ..()
+ if(ismob(dropping) && dropping != user)
+ var/mob/M = dropping
+ M.show_inv(user)
/mob/proc/is_muzzled()
return 0
@@ -787,6 +816,9 @@
/mob/proc/canUseTopic(atom/movable/M, be_close=FALSE, no_dextery=FALSE, no_tk=FALSE)
return
+/mob/proc/canUseStorage()
+ return FALSE
+
/mob/proc/faction_check_mob(mob/target, exact_match)
if(exact_match) //if we need an exact match, we need to do some bullfuckery.
var/list/faction_src = faction.Copy()
diff --git a/code/modules/mob/mob_defines.dm b/code/modules/mob/mob_defines.dm
index a0126f5fdd..0cb886f11b 100644
--- a/code/modules/mob/mob_defines.dm
+++ b/code/modules/mob/mob_defines.dm
@@ -65,6 +65,7 @@
var/active_hand_index = 1
var/list/held_items = list() //len = number of hands, eg: 2 nulls is 2 empty hands, 1 item and 1 null is 1 full hand and 1 empty hand.
//held_items[active_hand_index] is the actively held item, but please use get_active_held_item() instead, because OOP
+ var/bloody_hands = 0
var/datum/component/storage/active_storage = null//Carbon
diff --git a/code/modules/mob/mob_helpers.dm b/code/modules/mob/mob_helpers.dm
index 776bd04935..93b4d32123 100644
--- a/code/modules/mob/mob_helpers.dm
+++ b/code/modules/mob/mob_helpers.dm
@@ -429,8 +429,8 @@ It's fairly easy to fix if dealing with single letters but not so much with comp
var/mob/dead/observer/C = pick(candidates)
to_chat(M, "Your mob has been taken over by a ghost!")
message_admins("[key_name_admin(C)] has taken control of ([key_name_admin(M)])")
- M.ghostize(0)
- M.key = C.key
+ M.ghostize(FALSE, TRUE)
+ C.transfer_ckey(M, FALSE)
return TRUE
else
to_chat(M, "There were no ghosts willing to take control.")
@@ -486,3 +486,15 @@ It's fairly easy to fix if dealing with single letters but not so much with comp
/mob/proc/can_hear()
. = TRUE
+
+//Examine text for traits shared by multiple types. I wish examine was less copypasted.
+/mob/proc/common_trait_examine()
+ if(HAS_TRAIT(src, TRAIT_DISSECTED))
+ var/dissectionmsg = ""
+ if(HAS_TRAIT_FROM(src, TRAIT_DISSECTED,"Extraterrestrial Dissection"))
+ dissectionmsg = " via Extraterrestrial Dissection. It is no longer worth experimenting on"
+ else if(HAS_TRAIT_FROM(src, TRAIT_DISSECTED,"Experimental Dissection"))
+ dissectionmsg = " via Experimental Dissection"
+ else if(HAS_TRAIT_FROM(src, TRAIT_DISSECTED,"Thorough Dissection"))
+ dissectionmsg = " via Thorough Dissection"
+ . += "This body has been dissected and analyzed[dissectionmsg]. "
\ No newline at end of file
diff --git a/code/modules/mob/mob_transformation_simple.dm b/code/modules/mob/mob_transformation_simple.dm
index 673548ff48..a11e7a228e 100644
--- a/code/modules/mob/mob_transformation_simple.dm
+++ b/code/modules/mob/mob_transformation_simple.dm
@@ -53,7 +53,7 @@
if(mind && isliving(M))
mind.transfer_to(M, 1) // second argument to force key move to new mob
else
- M.key = key
+ transfer_ckey(M)
if(delete_old_mob)
QDEL_IN(src, 1)
diff --git a/code/modules/mob/say.dm b/code/modules/mob/say.dm
index 9e127d4746..ecb869790c 100644
--- a/code/modules/mob/say.dm
+++ b/code/modules/mob/say.dm
@@ -69,16 +69,11 @@
if(name != real_name)
alt_name = " (died as [real_name])"
- var/K
-
- if(key)
- K = src.key
-
- var/spanned = src.say_quote(message, get_spans())
+ var/spanned = say_quote(message)
message = emoji_parse(message)
var/rendered = "DEAD:[name][alt_name] [emoji_parse(spanned)]"
log_talk(message, LOG_SAY, tag="DEAD")
- deadchat_broadcast(rendered, follow_target = src, speaker_key = K)
+ deadchat_broadcast(rendered, follow_target = src, speaker_key = key)
/mob/proc/check_emote(message)
if(copytext(message, 1, 2) == "*")
diff --git a/code/modules/mob/say_readme.dm b/code/modules/mob/say_readme.dm
index 00e0f66246..0e76d9b6ed 100644
--- a/code/modules/mob/say_readme.dm
+++ b/code/modules/mob/say_readme.dm
@@ -78,10 +78,6 @@ global procs
say_quote(input, spans, message_mode)
Adds a verb and quotes to a message. Also attaches span classes to a message. Verbs are determined by verb_say/verb_ask/verb_yell variables. Called on the speaker.
- get_spans(input, spans)
- Returns the list of spans that are always applied to messages of this atom.
- Always return ..() | + youroutput when overriding this proc!
-
/mob
say_dead(message)
Sends a message to all dead people. Does not use Hear().
diff --git a/code/modules/mob/say_vr.dm b/code/modules/mob/say_vr.dm
index 66444abf91..1fc97c31e4 100644
--- a/code/modules/mob/say_vr.dm
+++ b/code/modules/mob/say_vr.dm
@@ -172,15 +172,11 @@ proc/get_top_level_mob(var/mob/S)
user.log_message(message, INDIVIDUAL_EMOTE_LOG)
message = "[user] " + "[message]"
- for(var/mob/M)
- if(M in list(/mob/living))
- M.show_message(message)
-
if(emote_type == EMOTE_AUDIBLE)
- user.audible_message(message=message,hearing_distance=1)
+ user.audible_message(message=message,hearing_distance=1, no_ghosts = TRUE)
else
- user.visible_message(message=message,self_message=message,vision_distance=1)
- log_emote("[key_name(user)] : [message]")
+ user.visible_message(message=message,self_message=message,vision_distance=1, no_ghosts = TRUE)
+ log_emote("[key_name(user)] : (SUBTLER) [message]")
message = null
diff --git a/code/modules/mob/transform_procs.dm b/code/modules/mob/transform_procs.dm
index 5703e2190b..6394b45aa7 100644
--- a/code/modules/mob/transform_procs.dm
+++ b/code/modules/mob/transform_procs.dm
@@ -75,7 +75,7 @@
O.setOxyLoss(getOxyLoss(), 0)
O.setCloneLoss(getCloneLoss(), 0)
O.adjustFireLoss(getFireLoss(), 0)
- O.setBrainLoss(getBrainLoss(), 0)
+ O.setOrganLoss(ORGAN_SLOT_BRAIN, getOrganLoss(ORGAN_SLOT_BRAIN), 0)
O.adjustStaminaLoss(getStaminaLoss(), 0)//CIT CHANGE - makes monkey transformations inherit stamina
O.updatehealth()
O.radiation = radiation
@@ -236,7 +236,7 @@
O.setOxyLoss(getOxyLoss(), 0)
O.setCloneLoss(getCloneLoss(), 0)
O.adjustFireLoss(getFireLoss(), 0)
- O.setBrainLoss(getBrainLoss(), 0)
+ O.setOrganLoss(ORGAN_SLOT_BRAIN, getOrganLoss(ORGAN_SLOT_BRAIN), 0)
O.adjustStaminaLoss(getStaminaLoss(), 0)//CIT CHANGE - makes monkey transformations inherit stamina
O.updatehealth()
O.radiation = radiation
@@ -382,7 +382,7 @@
mind.active = FALSE
mind.transfer_to(R)
else if(transfer_after)
- R.key = key
+ transfer_ckey(R)
R.apply_pref_name("cyborg")
@@ -401,7 +401,7 @@
qdel(src)
//human -> alien
-/mob/living/carbon/human/proc/Alienize()
+/mob/living/carbon/human/proc/Alienize(mind_transfer = TRUE)
if (notransform)
return
for(var/obj/item/W in src)
@@ -425,13 +425,16 @@
new_xeno = new /mob/living/carbon/alien/humanoid/drone(loc)
new_xeno.a_intent = INTENT_HARM
- new_xeno.key = key
+ if(mind && mind_transfer)
+ mind.transfer_to(new_xeno)
+ else
+ transfer_ckey(new_xeno)
to_chat(new_xeno, "You are now an alien.")
. = new_xeno
qdel(src)
-/mob/living/carbon/human/proc/slimeize(reproduce as num)
+/mob/living/carbon/human/proc/slimeize(reproduce, mind_transfer = TRUE)
if (notransform)
return
for(var/obj/item/W in src)
@@ -457,20 +460,26 @@
else
new_slime = new /mob/living/simple_animal/slime(loc)
new_slime.a_intent = INTENT_HARM
- new_slime.key = key
+ if(mind && mind_transfer)
+ mind.transfer_to(new_slime)
+ else
+ transfer_ckey(new_slime)
to_chat(new_slime, "You are now a slime. Skreee!")
. = new_slime
qdel(src)
-/mob/proc/become_overmind(starting_points = 60)
+/mob/proc/become_overmind(starting_points = 60, mind_transfer = FALSE)
var/mob/camera/blob/B = new /mob/camera/blob(get_turf(src), starting_points)
- B.key = key
+ if(mind && mind_transfer)
+ mind.transfer_to(B)
+ else
+ transfer_ckey(B)
. = B
qdel(src)
-/mob/living/carbon/human/proc/corgize()
+/mob/living/carbon/human/proc/corgize(mind_transfer = TRUE)
if (notransform)
return
for(var/obj/item/W in src)
@@ -485,13 +494,16 @@
var/mob/living/simple_animal/pet/dog/corgi/new_corgi = new /mob/living/simple_animal/pet/dog/corgi (loc)
new_corgi.a_intent = INTENT_HARM
- new_corgi.key = key
+ if(mind && mind_transfer)
+ mind.transfer_to(new_corgi)
+ else
+ transfer_ckey(new_corgi)
to_chat(new_corgi, "You are now a Corgi. Yap Yap!")
. = new_corgi
qdel(src)
-/mob/living/carbon/proc/gorillize()
+/mob/living/carbon/proc/gorillize(mind_transfer = TRUE)
if(notransform)
return
@@ -509,22 +521,22 @@
invisibility = INVISIBILITY_MAXIMUM
var/mob/living/simple_animal/hostile/gorilla/new_gorilla = new (get_turf(src))
new_gorilla.a_intent = INTENT_HARM
- if(mind)
+ if(mind && mind_transfer)
mind.transfer_to(new_gorilla)
else
- new_gorilla.key = key
+ transfer_ckey(new_gorilla)
to_chat(new_gorilla, "You are now a gorilla. Ooga ooga!")
. = new_gorilla
qdel(src)
-/mob/living/carbon/human/Animalize()
+/mob/living/carbon/human/Animalize(mind_transfer = TRUE)
var/list/mobtypes = typesof(/mob/living/simple_animal)
- var/mobpath = input("Which type of mob should [src] turn into?", "Choose a type") in mobtypes
-
- if(!safe_animal(mobpath))
- to_chat(usr, "Sorry but this mob type is currently unavailable.")
+ var/mobpath = input("Which type of mob should [src] turn into?", "Choose a type") as null|anything in mobtypes
+ if(!mobpath)
return
+ if(mind)
+ mind_transfer = alert("Want to transfer their mind into the new mob", "Mind Transfer", "Yes", "No") == "Yes" ? TRUE : FALSE
if(notransform)
return
@@ -532,8 +544,8 @@
dropItemToGround(W)
regenerate_icons()
- notransform = 1
- canmove = 0
+ notransform = TRUE
+ canmove = FALSE
icon = null
invisibility = INVISIBILITY_MAXIMUM
@@ -541,8 +553,10 @@
qdel(t)
var/mob/new_mob = new mobpath(src.loc)
-
- new_mob.key = key
+ if(mind && mind_transfer)
+ mind.transfer_to(new_mob)
+ else
+ transfer_ckey(new_mob)
new_mob.a_intent = INTENT_HARM
@@ -550,59 +564,23 @@
. = new_mob
qdel(src)
-/mob/proc/Animalize()
+/mob/proc/Animalize(mind_transfer = TRUE)
var/list/mobtypes = typesof(/mob/living/simple_animal)
- var/mobpath = input("Which type of mob should [src] turn into?", "Choose a type") in mobtypes
-
- if(!safe_animal(mobpath))
- to_chat(usr, "Sorry but this mob type is currently unavailable.")
+ var/mobpath = input("Which type of mob should [src] turn into?", "Choose a type") as null|anything in mobtypes
+ if(!mobpath)
return
+ if(mind)
+ mind_transfer = alert("Want to transfer their mind into the new mob", "Mind Transfer", "Yes", "No") == "Yes" ? TRUE : FALSE
var/mob/new_mob = new mobpath(src.loc)
- new_mob.key = key
+ if(mind && mind_transfer)
+ mind.transfer_to(new_mob)
+ else
+ transfer_ckey(new_mob)
new_mob.a_intent = INTENT_HARM
to_chat(new_mob, "You feel more... animalistic")
. = new_mob
qdel(src)
-
-/* Certain mob types have problems and should not be allowed to be controlled by players.
- *
- * This proc is here to force coders to manually place their mob in this list, hopefully tested.
- * This also gives a place to explain -why- players shouldnt be turn into certain mobs and hopefully someone can fix them.
- */
-/mob/proc/safe_animal(MP)
-
-//Bad mobs! - Remember to add a comment explaining what's wrong with the mob
- if(!MP)
- return 0 //Sanity, this should never happen.
-
- if(ispath(MP, /mob/living/simple_animal/hostile/construct))
- return 0 //Verbs do not appear for players.
-
-//Good mobs!
- if(ispath(MP, /mob/living/simple_animal/pet/cat))
- return 1
- if(ispath(MP, /mob/living/simple_animal/pet/dog/corgi))
- return 1
- if(ispath(MP, /mob/living/simple_animal/crab))
- return 1
- if(ispath(MP, /mob/living/simple_animal/hostile/carp))
- return 1
- if(ispath(MP, /mob/living/simple_animal/hostile/mushroom))
- return 1
- if(ispath(MP, /mob/living/simple_animal/shade))
- return 1
- if(ispath(MP, /mob/living/simple_animal/hostile/killertomato))
- return 1
- if(ispath(MP, /mob/living/simple_animal/mouse))
- return 1 //It is impossible to pull up the player panel for mice (Fixed! - Nodrak)
- if(ispath(MP, /mob/living/simple_animal/hostile/bear))
- return 1 //Bears will auto-attack mobs, even if they're player controlled (Fixed! - Nodrak)
- if(ispath(MP, /mob/living/simple_animal/parrot))
- return 1 //Parrots are no longer unfinished! -Nodrak
-
- //Not in here? Must be untested!
- return 0
diff --git a/code/modules/modular_computers/computers/item/computer.dm b/code/modules/modular_computers/computers/item/computer.dm
index 580374c5c0..db4cdc2ff5 100644
--- a/code/modules/modular_computers/computers/item/computer.dm
+++ b/code/modules/modular_computers/computers/item/computer.dm
@@ -178,13 +178,13 @@
turn_on(user)
/obj/item/modular_computer/emag_act(mob/user)
+ . = ..()
if(obj_flags & EMAGGED)
to_chat(user, "\The [src] was already emagged.")
- return 0
- else
- obj_flags |= EMAGGED
- to_chat(user, "You emag \the [src]. It's screen briefly shows a \"OVERRIDE ACCEPTED: New software downloads available.\" message.")
- return 1
+ return
+ obj_flags |= EMAGGED
+ to_chat(user, "You emag \the [src]. It's screen briefly shows a \"OVERRIDE ACCEPTED: New software downloads available.\" message.")
+ return TRUE
/obj/item/modular_computer/examine(mob/user)
..()
diff --git a/code/modules/modular_computers/computers/machinery/modular_computer.dm b/code/modules/modular_computers/computers/machinery/modular_computer.dm
index 19d3b56046..b3476e7046 100644
--- a/code/modules/modular_computers/computers/machinery/modular_computer.dm
+++ b/code/modules/modular_computers/computers/machinery/modular_computer.dm
@@ -44,7 +44,9 @@
cpu.attack_ghost(user)
/obj/machinery/modular_computer/emag_act(mob/user)
- return cpu ? cpu.emag_act(user) : 1
+ . = ..()
+ if(cpu)
+ . |= cpu.emag_act(user)
/obj/machinery/modular_computer/update_icon()
cut_overlays()
diff --git a/code/modules/ninja/suit/gloves.dm b/code/modules/ninja/suit/gloves.dm
index a01b354ca1..276de652bc 100644
--- a/code/modules/ninja/suit/gloves.dm
+++ b/code/modules/ninja/suit/gloves.dm
@@ -79,5 +79,5 @@
/obj/item/clothing/gloves/space_ninja/examine(mob/user)
..()
- if(item_flags & NODROP)
+ if(HAS_TRAIT_FROM(src, TRAIT_NODROP, NINJA_SUIT_TRAIT))
to_chat(user, "The energy drain mechanism is [candrain?"active":"inactive"].")
diff --git a/code/modules/ninja/suit/mask.dm b/code/modules/ninja/suit/mask.dm
index 2200af7cab..e97e39643a 100644
--- a/code/modules/ninja/suit/mask.dm
+++ b/code/modules/ninja/suit/mask.dm
@@ -17,16 +17,19 @@ Contents:
item_state = "s-ninja_mask"
strip_delay = 120
resistance_flags = LAVA_PROOF | FIRE_PROOF | ACID_PROOF
+ modifies_speech = TRUE
-/obj/item/clothing/mask/gas/space_ninja/speechModification(message)
- if(copytext(message, 1, 2) != "*")
+/obj/item/clothing/mask/gas/space_ninja/handle_speech(datum/source, list/speech_args)
+ var/message = speech_args[SPEECH_MESSAGE]
+ if(message[1] != "*")
var/list/temp_message = text2list(message, " ")
var/list/pick_list = list()
- for(var/i = 1, i <= temp_message.len, i++)
+ for(var/i in 1 to temp_message.len)
pick_list += i
- for(var/i=1, i <= abs(temp_message.len/3), i++)
+ for(var/i in 1 to abs(temp_message.len/3))
var/H = pick(pick_list)
- if(findtext(temp_message[H], "*") || findtext(temp_message[H], ";") || findtext(temp_message[H], ":")) continue
+ if(findtext(temp_message[H], "*") || findtext(temp_message[H], ";") || findtext(temp_message[H], ":"))
+ continue
temp_message[H] = ninjaspeak(temp_message[H])
pick_list -= H
message = list2text(temp_message, " ")
@@ -56,5 +59,4 @@ Contents:
message = replacetext(message, "than", "sen")
message = replacetext(message, ".", "")
message = lowertext(message)
-
- return message
+ speech_args[SPEECH_MESSAGE] = message
diff --git a/code/modules/ninja/suit/n_suit_verbs/energy_net_nets.dm b/code/modules/ninja/suit/n_suit_verbs/energy_net_nets.dm
index c98a0440e3..334136cc33 100644
--- a/code/modules/ninja/suit/n_suit_verbs/energy_net_nets.dm
+++ b/code/modules/ninja/suit/n_suit_verbs/energy_net_nets.dm
@@ -18,8 +18,8 @@ It is possible to destroy the net by the occupant or someone else.
can_buckle = 1
buckle_lying = 0
buckle_prevents_pull = TRUE
- var/mob/living/carbon/affecting//Who it is currently affecting, if anyone.
- var/mob/living/carbon/master//Who shot web. Will let this person know if the net was successful or failed.
+ var/mob/living/carbon/affecting //Who it is currently affecting, if anyone.
+ var/mob/living/carbon/master //Who shot web. Will let this person know if the net was successful or failed.
var/check = 15//30 seconds before teleportation. Could be extended I guess.
var/success = FALSE
diff --git a/code/modules/ninja/suit/n_suit_verbs/ninja_net.dm b/code/modules/ninja/suit/n_suit_verbs/ninja_net.dm
index 41f7b8af83..61355ca89b 100644
--- a/code/modules/ninja/suit/n_suit_verbs/ninja_net.dm
+++ b/code/modules/ninja/suit/n_suit_verbs/ninja_net.dm
@@ -7,28 +7,26 @@
//If there's only one valid target, let's actually try to capture it, rather than forcing
//the user to fiddle with the dialog displaying a list of one
//Also, let's make this smarter and not list mobs you can't currently net.
- var/Candidates[]
- for(var/mob/mob in oview(H))
- if(!mob.client)//Monkeys without a client can still step_to() and bypass the net. Also, netting inactive people is lame.
- //to_chat(H, "[C.p_they(TRUE)] will bring no honor to your Clan!")
+ var/list/candidates
+ for(var/mob/M in oview(H))
+ if(!M.client)//Monkeys without a client can still step_to() and bypass the net. Also, netting inactive people is lame.
continue
- if(locate(/obj/structure/energy_net) in get_turf(mob))//Check if they are already being affected by an energy net.
- //to_chat(H, "[C.p_they(TRUE)] are already trapped inside an energy net!")
- continue
- for(var/turf/T in getline(get_turf(H), get_turf(mob)))
- if(T.density)//Don't want them shooting nets through walls. It's kind of cheesy.
- //to_chat(H, "You may not use an energy net through solid obstacles!")
+ for(var/obj/structure/energy_net/E in get_turf(M))//Check if they are already being affected by an energy net.
+ if(E.affecting == M)
continue
- Candidates+=mob
+ LAZYADD(candidates, M)
- if(Candidates.len == 1)
- C = Candidates[1]
+ if(!LAZYLEN(candidates))
+ return FALSE
+
+ if(candidates.len == 1)
+ C = candidates[1]
else
- C = input("Select who to capture:","Capture who?",null) as null|mob in Candidates
+ C = input("Select who to capture:","Capture who?",null) as null|mob in candidates
if(QDELETED(C)||!(C in oview(H)))
- return 0
+ return FALSE
if(!ninjacost(200,N_STEALTH_CANCEL))
H.Beam(C,"n_beam",time=15)
diff --git a/code/modules/ninja/suit/n_suit_verbs/ninja_stealth.dm b/code/modules/ninja/suit/n_suit_verbs/ninja_stealth.dm
index 1c3fbd8147..94be922fdf 100644
--- a/code/modules/ninja/suit/n_suit_verbs/ninja_stealth.dm
+++ b/code/modules/ninja/suit/n_suit_verbs/ninja_stealth.dm
@@ -8,33 +8,37 @@ Contents:
/obj/item/clothing/suit/space/space_ninja/proc/toggle_stealth()
- var/mob/living/carbon/human/U = affecting
- if(!U)
+ if(!affecting)
return
if(stealth)
cancel_stealth()
else
if(cell.charge <= 0)
- to_chat(U, "You don't have enough power to enable Stealth!")
+ to_chat(affecting, "You don't have enough power to enable Stealth!")
return
stealth = !stealth
- animate(U, alpha = 50,time = 15)
- U.visible_message("[U.name] vanishes into thin air!", \
+ animate(affecting, alpha = 10,time = 15)
+ affecting.visible_message("[affecting.name] vanishes into thin air!", \
"You are now mostly invisible to normal detection.")
+ RegisterSignal(affecting, list(COMSIG_MOB_ITEM_ATTACK, COMSIG_MOB_ATTACK_RANGED, COMSIG_MOB_ATTACK_HAND, COMSIG_MOB_THROW, COMSIG_PARENT_ATTACKBY), .proc/reduce_stealth)
+ RegisterSignal(affecting, COMSIG_MOVABLE_BUMP, .proc/bumping_stealth)
+/obj/item/clothing/suit/space/space_ninja/proc/reduce_stealth()
+ affecting.alpha = min(affecting.alpha + 30, 80)
+
+/obj/item/clothing/suit/space/space_ninja/proc/bumping_stealth(datum/source, atom/A)
+ if(isliving(A))
+ affecting.alpha = min(affecting.alpha + 15, 80)
/obj/item/clothing/suit/space/space_ninja/proc/cancel_stealth()
- var/mob/living/carbon/human/U = affecting
- if(!U)
- return 0
- if(stealth)
- stealth = !stealth
- animate(U, alpha = 255, time = 15)
- U.visible_message("[U.name] appears from thin air!", \
- "You are now visible.")
- return 1
- return 0
-
+ if(!affecting || !stealth)
+ return FALSE
+ stealth = !stealth
+ UnregisterSignal(affecting, list(COMSIG_MOB_ITEM_ATTACK, COMSIG_MOB_ATTACK_RANGED, COMSIG_MOB_ATTACK_HAND, COMSIG_MOB_THROW, COMSIG_PARENT_ATTACKBY, COMSIG_MOVABLE_BUMP))
+ animate(affecting, alpha = 255, time = 15)
+ affecting.visible_message("[affecting.name] appears from thin air!", \
+ "You are now visible.")
+ return TRUE
/obj/item/clothing/suit/space/space_ninja/proc/stealth()
if(!s_busy)
diff --git a/code/modules/ninja/suit/shoes.dm b/code/modules/ninja/suit/shoes.dm
index 1b935a00de..1bda62e064 100644
--- a/code/modules/ninja/suit/shoes.dm
+++ b/code/modules/ninja/suit/shoes.dm
@@ -1,9 +1,7 @@
-
/obj/item/clothing/shoes/space_ninja
name = "ninja shoes"
desc = "A pair of running shoes. Excellent for running and even better for smashing skulls."
icon_state = "s-ninja"
- item_state = "secshoes"
permeability_coefficient = 0.01
clothing_flags = NOSLIP
resistance_flags = LAVA_PROOF | FIRE_PROOF | ACID_PROOF
@@ -13,3 +11,12 @@
min_cold_protection_temperature = SHOES_MIN_TEMP_PROTECT
heat_protection = FEET
max_heat_protection_temperature = SHOES_MAX_TEMP_PROTECT
+
+/obj/item/clothing/shoes/space_ninja/equipped(mob/user, slot)
+ . = ..()
+ if(slot == SLOT_SHOES)
+ ADD_TRAIT(user, TRAIT_SILENT_STEP, "ninja_shoes_[REF(src)]")
+
+/obj/item/clothing/shoes/space_ninja/dropped(mob/user)
+ . = ..()
+ REMOVE_TRAIT(user, TRAIT_SILENT_STEP, "ninja_shoes_[REF(src)]")
diff --git a/code/modules/ninja/suit/suit.dm b/code/modules/ninja/suit/suit.dm
index 6110f2b0c0..ac1ef3b96a 100644
--- a/code/modules/ninja/suit/suit.dm
+++ b/code/modules/ninja/suit/suit.dm
@@ -111,15 +111,15 @@ Contents:
to_chat(H, "ERROR: 110223 UNABLE TO LOCATE HAND GEAR\nABORTING...")
return FALSE
affecting = H
- item_flags |= NODROP //colons make me go all |=
+ ADD_TRAIT(src, TRAIT_NODROP, NINJA_SUIT_TRAIT) //colons make me go all |=
slowdown = 0
n_hood = H.head
- n_hood.item_flags |= NODROP
+ ADD_TRAIT(n_hood, TRAIT_NODROP, NINJA_SUIT_TRAIT)
n_shoes = H.shoes
- n_shoes.item_flags |= NODROP
+ ADD_TRAIT(n_shoes, TRAIT_NODROP, NINJA_SUIT_TRAIT)
n_shoes.slowdown--
n_gloves = H.gloves
- n_gloves.item_flags |= NODROP
+ ADD_TRAIT(n_gloves, TRAIT_NODROP, NINJA_SUIT_TRAIT)
return TRUE
/obj/item/clothing/suit/space/space_ninja/proc/lockIcons(mob/living/carbon/human/H)
@@ -131,30 +131,29 @@ Contents:
//This proc allows the suit to be taken off.
/obj/item/clothing/suit/space/space_ninja/proc/unlock_suit()
affecting = null
- item_flags &= ~NODROP
+ REMOVE_TRAIT(src, TRAIT_NODROP, NINJA_SUIT_TRAIT)
slowdown = 1
icon_state = "s-ninja"
if(n_hood)//Should be attached, might not be attached.
- n_hood.item_flags &= ~NODROP
+ REMOVE_TRAIT(n_hood, TRAIT_NODROP, NINJA_SUIT_TRAIT)
if(n_shoes)
- n_shoes.item_flags &= ~NODROP
+ REMOVE_TRAIT(n_shoes, TRAIT_NODROP, NINJA_SUIT_TRAIT)
n_shoes.slowdown++
if(n_gloves)
n_gloves.icon_state = "s-ninja"
n_gloves.item_state = "s-ninja"
- n_gloves.item_flags &= ~NODROP
+ REMOVE_TRAIT(n_gloves, TRAIT_NODROP, NINJA_SUIT_TRAIT)
n_gloves.candrain=0
n_gloves.draining=0
/obj/item/clothing/suit/space/space_ninja/examine(mob/user)
..()
- if(s_initialized)
- if(user == affecting)
- to_chat(user, "All systems operational. Current energy capacity: [DisplayEnergy(cell.charge)].")
- to_chat(user, "The CLOAK-tech device is [stealth?"active":"inactive"].")
- to_chat(user, "There are [s_bombs] smoke bomb\s remaining.")
- to_chat(user, "There are [a_boost] adrenaline booster\s remaining.")
+ if(s_initialized && user == affecting)
+ to_chat(user, "All systems operational. Current energy capacity: [DisplayEnergy(cell.charge)].\n\
+ The CLOAK-tech device is [stealth?"active":"inactive"].\n\
+ There are [s_bombs] smoke bomb\s remaining.\n\
+ There are [a_boost] adrenaline booster\s remaining.")
/obj/item/clothing/suit/space/space_ninja/ui_action_click(mob/user, action)
if(istype(action, /datum/action/item_action/initialize_ninja_suit))
diff --git a/code/modules/ninja/suit/suit_initialisation.dm b/code/modules/ninja/suit/suit_initialisation.dm
index 4b159557bc..3d80282fe7 100644
--- a/code/modules/ninja/suit/suit_initialisation.dm
+++ b/code/modules/ninja/suit/suit_initialisation.dm
@@ -48,7 +48,7 @@
/obj/item/clothing/suit/space/space_ninja/proc/ninitialize_seven(delay, mob/living/carbon/human/U)
to_chat(U, "All systems operational. Welcome to SpiderOS, [U.real_name].")
s_initialized = TRUE
- ntick()
+ START_PROCESSING(SSprocessing, src)
s_busy = FALSE
@@ -91,4 +91,5 @@
unlock_suit()
U.regenerate_icons()
s_initialized = FALSE
+ STOP_PROCESSING(SSprocessing, src)
s_busy = FALSE
diff --git a/code/modules/ninja/suit/suit_process.dm b/code/modules/ninja/suit/suit_process.dm
index 4a89a59f75..850fb837b4 100644
--- a/code/modules/ninja/suit/suit_process.dm
+++ b/code/modules/ninja/suit/suit_process.dm
@@ -1,20 +1,17 @@
-/obj/item/clothing/suit/space/space_ninja/proc/ntick(mob/living/carbon/human/U = affecting)
- //Runs in the background while the suit is initialized.
- //Requires charge or stealth to process.
- spawn while(s_initialized)
- if(!affecting)
- terminate()//Kills the suit and attached objects.
+/obj/item/clothing/suit/space/space_ninja/process()
+ if(!affecting || !s_initialized)
+ return PROCESS_KILL
- else if(cell.charge > 0)
- if(s_coold)
- s_coold--//Checks for ability s_cooldown first.
+ if(cell.charge > 0)
+ if(s_coold)
+ s_coold--//Checks for ability s_cooldown first.
- cell.charge -= s_cost//s_cost is the default energy cost each ntick, usually 5.
- if(stealth)//If stealth is active.
- cell.charge -= s_acost
+ cell.charge -= s_cost//s_cost is the default energy cost each tick, usually 5.
+ if(stealth)//If stealth is active.
+ cell.charge -= s_acost
+ affecting.alpha = max(affecting.alpha - 10, 10)
- else
- cell.charge = 0
+ else
+ cell.charge = 0
+ if(stealth)
cancel_stealth()
-
- sleep(10)//Checks every second.
diff --git a/code/modules/oracle_ui/README.md b/code/modules/oracle_ui/README.md
new file mode 100644
index 0000000000..bc96eb1f51
--- /dev/null
+++ b/code/modules/oracle_ui/README.md
@@ -0,0 +1,233 @@
+# `/datum/oracle_ui`
+
+This datum is a replacement for tgui which does not use any Node.js dependencies, and works entirely through raw HTML, JS and CSS. It's designed to be reasonably easy to port something from tgui to oracle_ui.
+
+### How to create a UI
+
+For this example, we're going to port the disposals bin from tgui to oracle_ui.
+
+#### Step 1
+
+In order to create a UI, you will first need to create an instance of `/datum/oracle_ui` or one of its subclasses, in this case `/datum/oracle_ui/themed/nano`.
+
+You need to pass in `src`, the width of the window, the height of the window, and the template to render from. You can optionally set some flags to disallow window resizing and whether to automatically refresh the UI.
+
+`code/modules/recycling/disposal-unit.dm`
+```dm
+/obj/machinery/disposal/bin/Initialize(mapload, obj/structure/disposalconstruct/make_from)
+ . = ..()
+ ui = new /datum/oracle_ui/themed/nano(src, 330, 190, "disposal_bin")
+ ui.auto_refresh = TRUE
+ ui.can_resize = FALSE
+```
+
+#### Step 2
+
+You will now need to make a template in `html/oracle_ui/content/{template_name}`.
+
+Values defined as `@{value}` will get replaced at runtime by oracle_ui.
+
+`html/oracle_ui/content/disposal_bin/index.html`
+```html
+
+
+ State:
+
@{full_pressure}
+
+
+ Pressure:
+
+
+
+
@{per}
+
+
+
+
+ Handle:
+
@{flush}
+
+
+ Eject:
+
@{contents}
+
+
+ Compressor:
+
@{pressure_charging}
+
+
+```
+
+#### Step 3
+
+Now you need to implement the methods that provide data to oracle_ui. `oui_data` can be adapted from the `ui_data` proc that tgui uses.
+
+The `act` proc generates a hyperlink that will result in `oui_act` getting called on your object when clicked. The `class` argument defines a css class to be added to the hyperlink, and disabled determines whether the hyperlink will be disabled or not.
+
+Calling `soft_update_fields` will result in the UI being updated on all clients, which is useful when the object changes state.
+
+`code/modules/recycling/disposal-unit.dm`
+```dm
+/obj/machinery/disposal/bin/oui_data(mob/user)
+ var/list/data = list()
+ data["flush"] = flush ? ui.act("Disengage", user, "handle-0", class="active") : ui.act("Engage", user, "handle-1")
+ data["full_pressure"] = full_pressure ? "Ready" : (pressure_charging ? "Pressurizing" : "Off")
+ data["pressure_charging"] = pressure_charging ? ui.act("Turn Off", user, "pump-0", class="active", disabled=full_pressure) : ui.act("Turn On", user, "pump-1", disabled=full_pressure)
+ var/per = full_pressure ? 100 : Clamp(100* air_contents.return_pressure() / (SEND_PRESSURE), 0, 99)
+ data["per"] = "[round(per, 1)]%"
+ data["contents"] = ui.act("Eject Contents", user, "eject", disabled=contents.len < 1)
+ data["isai"] = isAI(user)
+ return data
+/obj/machinery/disposal/bin/oui_act(mob/user, action, list/params)
+ if(..())
+ return
+ switch(action)
+ if("handle-0")
+ flush = FALSE
+ update_icon()
+ . = TRUE
+ if("handle-1")
+ if(!panel_open)
+ flush = TRUE
+ update_icon()
+ . = TRUE
+ if("pump-0")
+ if(pressure_charging)
+ pressure_charging = FALSE
+ update_icon()
+ . = TRUE
+ if("pump-1")
+ if(!pressure_charging)
+ pressure_charging = TRUE
+ update_icon()
+ . = TRUE
+ if("eject")
+ eject()
+ . = TRUE
+ ui.soft_update_fields()
+```
+
+#### Step 4
+
+You now need to hook in and ensure oracle_ui is invoked upon clicking. `render` should be used to open the UI for a user, typically on click.
+
+`code/modules/recycling/disposal-unit.dm`
+```dm
+/obj/machinery/disposal/bin/ui_interact(mob/user, state)
+ if(stat & BROKEN)
+ return
+ if(user.loc == src)
+ to_chat(user, "You cannot reach the controls from inside!")
+ return
+ ui.render(user)
+```
+
+#### Done
+
+
+
+You should have a functional UI at this point. Some additional odds and ends can be discovered throughout `code/modules/recycling/disposal-unit.dm`. For a full diff of the changes made to it, refer to [the original pull request on GitHub](https://github.com/OracleStation/OracleStation/pull/702/files#diff-4b6c20ec7d37222630e7524d9577e230).
+
+### API Reference
+
+#### `/datum/oracle_ui`
+
+The main datum which handles the UI.
+
+##### `get_content(mob/target)`
+Returns the HTML that should be displayed for a specified target mob. Calls `oui_getcontent` on the datasource to get the return value. *This proc is not used in the themed subclass.*
+
+##### `can_view(mob/target)`
+Returns whether the specified target mob can view the UI. Calls `oui_canview` on the datasource to get the return value.
+
+##### `test_viewer(mob/target, updating)`
+Tests whether the client is valid and can view the UI. If updating is TRUE, checks to see if they still have the UI window open.
+
+##### `render(mob/target, updating = FALSE)`
+Opens the UI for a target mob, sending HTML. If updating is TRUE, will only do it to clients which still have the window open.
+
+##### `render_all()`
+Does the above, but for all viewers and with updating set to TRUE.
+
+##### `close(mob/target)`
+Closes the UI for the specified target mob.
+
+##### `close_all()`
+Does the above, but for all viewers.
+
+##### `check_view(mob/target)`
+Checks if the specified target mob can view the UI, and if they can't closes their UI
+
+##### `check_view_all()`
+Does the above, but for all viewers.
+
+##### `call_js(mob/target, js_func, list/parameters = list())`
+Invokes `js_func` in the UI of the specified target mob with the specified parameters.
+
+##### `call_js_all(js_func, list/parameters = list()))`
+Does the above, but for all viewers.
+
+##### `steal_focus(mob/target)`
+Causes the UI to steal focus for the specified target mob.
+
+##### `steal_focus_all()`
+Does the above, but for all viewers.
+
+##### `flash(mob/target, times = -1)`
+Causes the UI to flash for the specified target mob the specified number of times, the default keeps the element flashing until focused.
+
+##### `flash_all()`
+Does the above, but for all viewers.
+
+##### `href(mob/user, action, list/parameters = list())`
+Generates a href for the specified user which will invoke `oui_act` on the datasource with the specified action and parameters.
+
+#### `/datum/oracle_ui/themed`
+
+A subclass which supports templating and theming.
+
+##### `get_file(path)`
+Loads a file from disk and returns the contents. Caches files loaded from disk for you.
+
+##### `get_content_file(filename)`
+Loads a file from the current content folder and returns the contents.
+
+##### `get_themed_file(filename)`
+Loads a file from the current theme folder and returns the contents.
+
+##### `process_template(template, variables)`
+Processes a template and populates it with the provided variables.
+
+##### `get_inner_content(mob/target)`
+Returns the templated content to be inserted into the main template for the specified target mob.
+
+##### `soft_update_fields()`
+For all viewers, updates the fields in the template via the `updateFields` javaScript function.
+
+##### `soft_update_all()`
+For all viewers, updates the content body in the template via the `replaceContent` javaScript function.
+
+##### `change_page(var/newpage)`
+Changes the template to use to draw the page and forces an update to all viewers
+
+##### `act(label, mob/user, action, list/parameters = list(), class = "", disabled = FALSE`
+Returns a fully formatted hyperlink for the specified user. `label` will be the hyperlink label, `action` and `parameters` are what will be passed to `oui_act`, `class` is any CSS classes to apply to the hyperlink and `disabled` will disable the hyperlink.
+
+#### `/datum`
+
+Functions built into all objects to support oracle_ui. There are default implementations for most major superclasses.
+
+##### `oui_canview(mob/user)`
+Returns whether the specified user view the UI at this time.
+
+##### `oui_getcontent(mob/user)`
+Returns the raw HTML to be sent to the specified user. *This proc is not used in the themed subclass of oracle_ui.*
+
+##### `oui_data(mob/user)`
+Returns templating data for the specified user. *This proc is only used in the themed subclass of oracle_ui.*
+
+##### `oui_data_debug(mob/user)`
+Returns the above, but JSON-encoded and escaped, for copy pasting into the web IDE. *This proc is only used for debugging purposes.*
+
+##### `oui_act(mob/user, action, list/params)`
+Called when a hyperlink is clicked in the UI.
diff --git a/code/modules/oracle_ui/assets.dm b/code/modules/oracle_ui/assets.dm
new file mode 100644
index 0000000000..5d26d80a81
--- /dev/null
+++ b/code/modules/oracle_ui/assets.dm
@@ -0,0 +1,8 @@
+/datum/asset/simple/oui_theme_nano
+ assets = list(
+ // JavaScript
+ "sui-nano-common.js" = 'html/oracle_ui/themes/nano/sui-nano-common.js',
+ "sui-nano-jquery.min.js" = 'html/oracle_ui/themes/nano/sui-nano-jquery.min.js',
+ // Stylesheets
+ "sui-nano-common.css" = 'html/oracle_ui/themes/nano/sui-nano-common.css',
+ )
diff --git a/code/modules/oracle_ui/hookup_procs.dm b/code/modules/oracle_ui/hookup_procs.dm
new file mode 100644
index 0000000000..e6038744c1
--- /dev/null
+++ b/code/modules/oracle_ui/hookup_procs.dm
@@ -0,0 +1,44 @@
+/datum/proc/oui_canview(mob/user)
+ return TRUE
+
+/datum/proc/oui_getcontent(mob/user)
+ return "Default Implementation"
+
+/datum/proc/oui_canuse(mob/user)
+ if(isobserver(user) && !user.has_unlimited_silicon_privilege)
+ return FALSE
+ return oui_canview(user)
+
+/datum/proc/oui_data(mob/user)
+ return list()
+
+/datum/proc/oui_data_debug(mob/user)
+ return html_encode(json_encode(oui_data(user)))
+
+/datum/proc/oui_act(mob/user, action, list/params)
+ // No Implementation
+
+/atom/oui_canview(mob/user)
+ if(isobserver(user))
+ return TRUE
+ if(user.incapacitated())
+ return FALSE
+ if(isturf(src.loc) && Adjacent(user))
+ return TRUE
+ return FALSE
+
+/obj/item/oui_canview(mob/user)
+ if(src.loc == user)
+ return src in user.held_items
+ return ..()
+
+/obj/machinery/oui_canview(mob/user)
+ if(user.has_unlimited_silicon_privilege)
+ return TRUE
+ if(!can_interact())
+ return FALSE
+ if(iscyborg(user))
+ return can_see(user, src, 7)
+ if(isAI(user))
+ return GLOB.cameranet.checkTurfVis(get_turf_pixel(src))
+ return ..()
diff --git a/code/modules/oracle_ui/oracle_ui.dm b/code/modules/oracle_ui/oracle_ui.dm
new file mode 100644
index 0000000000..5e8d6b9c7b
--- /dev/null
+++ b/code/modules/oracle_ui/oracle_ui.dm
@@ -0,0 +1,134 @@
+/datum/oracle_ui
+ var/width = 512
+ var/height = 512
+ var/can_close = TRUE
+ var/can_minimize = FALSE
+ var/can_resize = TRUE
+ var/titlebar = TRUE
+ var/window_id = null
+ var/viewers[0]
+ var/auto_check_view = TRUE
+ var/auto_refresh = FALSE
+ var/atom/datasource = null
+ var/datum/asset/assets = null
+
+/datum/oracle_ui/New(atom/n_datasource, n_width = 512, n_height = 512, n_assets = null)
+ datasource = n_datasource
+ window_id = REF(src)
+ width = n_width
+ height = n_height
+
+/datum/oracle_ui/Destroy()
+ close_all()
+ if(src.datum_flags & DF_ISPROCESSING)
+ STOP_PROCESSING(SSobj, src)
+ return ..()
+
+/datum/oracle_ui/process()
+ if(auto_check_view)
+ check_view_all()
+ if(auto_refresh)
+ render_all()
+
+/datum/oracle_ui/proc/get_content(mob/target)
+ return call(datasource, "oui_getcontent")(target)
+
+/datum/oracle_ui/proc/can_view(mob/target)
+ return call(datasource, "oui_canview")(target)
+
+/datum/oracle_ui/proc/test_viewer(mob/target, updating)
+ //If the target is null or does not have a client, remove from viewers and return
+ if(!target | !target.client | !can_view(target))
+ viewers -= target
+ if(viewers.len < 1 && (src.datum_flags & DF_ISPROCESSING))
+ STOP_PROCESSING(SSobj, src) //No more viewers, stop polling
+ close(target)
+ return FALSE
+ //If this is an update, and they have closed the window, remove from viewers and return
+ if(updating && winget(target, window_id, "is-visible") != "true")
+ viewers -= target
+ if(viewers.len < 1 && (src.datum_flags & DF_ISPROCESSING))
+ STOP_PROCESSING(SSobj, src) //No more viewers, stop polling
+ return FALSE
+ return TRUE
+
+/datum/oracle_ui/proc/render(mob/target, updating = FALSE)
+ set waitfor = FALSE //Makes this an async call
+ if(!can_view(target))
+ return
+ //Check to see if they have the window open still if updating
+ if(updating && !test_viewer(target, updating))
+ return
+ //Send assets
+ if(!updating && assets)
+ assets.send(target)
+ //Add them to the viewers if they aren't there already
+ viewers |= target
+ if(!(src.datum_flags & DF_ISPROCESSING) && (auto_refresh | auto_check_view))
+ START_PROCESSING(SSobj, src) //Start processing to poll for viewability
+ //Send the content
+ if(updating)
+ target << output(get_content(target), "[window_id].browser")
+ else
+ target << browse(get_content(target), "window=[window_id];size=[width]x[height];can_close=[can_close];can_minimize=[can_minimize];can_resize=[can_resize];titlebar=[titlebar];focus=false;")
+ steal_focus(target)
+
+/datum/oracle_ui/proc/render_all()
+ for(var/viewer in viewers)
+ render(viewer, TRUE)
+
+/datum/oracle_ui/proc/close(mob/target)
+ if(target && target.client)
+ target << browse(null, "window=[window_id]")
+
+/datum/oracle_ui/proc/close_all()
+ for(var/viewer in viewers)
+ close(viewer)
+ viewers = list()
+
+/datum/oracle_ui/proc/check_view_all()
+ for(var/viewer in viewers)
+ check_view(viewer)
+
+/datum/oracle_ui/proc/check_view(mob/target)
+ set waitfor = FALSE //Makes this an async call
+ if(!test_viewer(target, TRUE))
+ close(target)
+
+/datum/oracle_ui/proc/call_js(mob/target, js_func, list/parameters = list())
+ set waitfor = FALSE //Makes this an async call
+ if(!test_viewer(target, TRUE))
+ return
+ target << output(list2params(parameters),"[window_id].browser:[js_func]")
+
+/datum/oracle_ui/proc/call_js_all(js_func, list/parameters = list())
+ for(var/viewer in viewers)
+ call_js(viewer, js_func, parameters)
+
+/datum/oracle_ui/proc/steal_focus(mob/target)
+ set waitfor = FALSE //Makes this an async call
+ winset(target, "[window_id]","focus=true")
+
+/datum/oracle_ui/proc/steal_focus_all()
+ for(var/viewer in viewers)
+ steal_focus(viewer)
+
+/datum/oracle_ui/proc/flash(mob/target, times = -1)
+ set waitfor = FALSE //Makes this an async call
+ winset(target, "[window_id]","flash=[times]")
+
+/datum/oracle_ui/proc/flash_all(times = -1)
+ for(var/viewer in viewers)
+ flash(viewer, times)
+
+/datum/oracle_ui/proc/href(mob/user, action, list/parameters = list())
+ var/params_string = replacetext(list2params(parameters),"&",";")
+ return "?src=[REF(src)];sui_action=[action];sui_user=[REF(user)];[params_string]"
+
+/datum/oracle_ui/Topic(href, parameters)
+ var/action = parameters["sui_action"]
+ var/mob/current_user = locate(parameters["sui_user"])
+ if(!call(datasource, "oui_canuse")(current_user))
+ return
+ if(datasource)
+ call(datasource, "oui_act")(current_user, action, parameters);
diff --git a/code/modules/oracle_ui/themed.dm b/code/modules/oracle_ui/themed.dm
new file mode 100644
index 0000000000..56b82c2647
--- /dev/null
+++ b/code/modules/oracle_ui/themed.dm
@@ -0,0 +1,82 @@
+/datum/oracle_ui/themed
+ var/theme = ""
+ var/content_root = ""
+ var/current_page = "index.html"
+ var/root_template = ""
+
+/datum/oracle_ui/themed/New(atom/n_datasource, n_width = 512, n_height = 512, n_content_root = "")
+ root_template = get_themed_file("index.html")
+ content_root = n_content_root
+ return ..(n_datasource, n_width, n_height, get_asset_datum(/datum/asset/simple/oui_theme_nano))
+
+/datum/oracle_ui/themed/process()
+ if(auto_check_view)
+ check_view_all()
+ if(auto_refresh)
+ soft_update_fields()
+
+GLOBAL_LIST_EMPTY(oui_template_variables)
+GLOBAL_LIST_EMPTY(oui_file_cache)
+
+/datum/oracle_ui/themed/proc/get_file(path)
+ if(GLOB.oui_file_cache[path])
+ return GLOB.oui_file_cache[path]
+ else if(fexists(path))
+ var/data = file2text(path)
+ GLOB.oui_file_cache[path] = data
+ return data
+ else
+ var/errormsg = "MISSING PATH '[path]'"
+#ifndef UNIT_TESTS
+ log_world(errormsg) //Because Travis absolutely hates these procs
+#endif
+ return errormsg
+
+/datum/oracle_ui/themed/proc/get_content_file(filename)
+ return get_file("./html/oracle_ui/content/[content_root]/[filename]")
+
+/datum/oracle_ui/themed/proc/get_themed_file(filename)
+ return get_file("./html/oracle_ui/themes/[theme]/[filename]")
+
+/datum/oracle_ui/themed/proc/process_template(template, variables)
+ var/regex/pattern = regex("\\@\\{(\\w+)\\}","gi")
+ GLOB.oui_template_variables = variables
+ var/replaced = pattern.Replace(template, /proc/oui_process_template_replace)
+ GLOB.oui_template_variables = null
+ return replaced
+
+/proc/oui_process_template_replace(match, group1)
+ var/value = GLOB.oui_template_variables[group1]
+ return "[value]"
+
+/datum/oracle_ui/themed/proc/get_inner_content(mob/target)
+ var/list/data = call(datasource, "oui_data")(target)
+ return process_template(get_content_file(current_page), data)
+
+/datum/oracle_ui/themed/get_content(mob/target)
+ var/list/template_data = list("title" = datasource.name, "body" = get_inner_content(target))
+ return process_template(root_template, template_data)
+
+/datum/oracle_ui/themed/proc/soft_update_fields()
+ for(var/viewer in viewers)
+ var/json = json_encode(call(datasource, "oui_data")(viewer))
+ call_js(viewer, "updateFields", list(json))
+
+/datum/oracle_ui/themed/proc/soft_update_all()
+ for(var/viewer in viewers)
+ call_js(viewer, "replaceContent", list(get_inner_content(viewer)))
+
+/datum/oracle_ui/themed/proc/change_page(newpage)
+ if(newpage == current_page)
+ return
+ current_page = newpage
+ render_all()
+
+/datum/oracle_ui/themed/proc/act(label, mob/user, action, list/parameters = list(), class = "", disabled = FALSE)
+ if(disabled)
+ return "[label]"
+ else
+ return "[label]"
+
+/datum/oracle_ui/themed/nano
+ theme = "nano"
diff --git a/code/modules/paperwork/filingcabinet.dm b/code/modules/paperwork/filingcabinet.dm
index 907fccdc5b..ccbcf34d8c 100644
--- a/code/modules/paperwork/filingcabinet.dm
+++ b/code/modules/paperwork/filingcabinet.dm
@@ -67,6 +67,9 @@
/obj/structure/filingcabinet/ui_interact(mob/user)
. = ..()
+ if(isobserver(user))
+ return
+
if(contents.len <= 0)
to_chat(user, "[src] is empty.")
return
diff --git a/code/modules/paperwork/paper.dm b/code/modules/paperwork/paper.dm
index 37877ffb09..059a42bb36 100644
--- a/code/modules/paperwork/paper.dm
+++ b/code/modules/paperwork/paper.dm
@@ -31,6 +31,7 @@
var/spam_flag = 0
var/contact_poison // Reagent ID to transfer on contact
var/contact_poison_volume = 0
+ var/datum/oracle_ui/ui = null
/obj/item/paper/pickup(user)
@@ -40,16 +41,40 @@
if(!istype(G) || G.transfer_prints)
H.reagents.add_reagent(contact_poison,contact_poison_volume)
contact_poison = null
+ ui.check_view_all()
..()
+/obj/item/paper/dropped(mob/user)
+ ui.check_view(user)
+ return ..()
+
/obj/item/paper/Initialize()
. = ..()
pixel_y = rand(-8, 8)
pixel_x = rand(-9, 9)
+ ui = new /datum/oracle_ui(src, 420, 600, get_asset_datum(/datum/asset/spritesheet/simple/paper))
+ ui.can_resize = FALSE
update_icon()
updateinfolinks()
+/obj/item/paper/oui_getcontent(mob/target)
+ if(!target.is_literate())
+ return "[name][stars(info)][stamps]"
+ else if(istype(target.get_active_held_item(), /obj/item/pen) | istype(target.get_active_held_item(), /obj/item/toy/crayon))
+ return "[name][info_links][stamps]
":"")] "
+
+ if(CR)
+ if(CR.required_container)
+ /*var/obj/item/I
+ I = istype(I, CR.required_container) if you can work out how to get this to work, by all means.
+ outstring += "
Required container: [I.name]
"*/
+ outstring += "
Required container: [CR.required_container]
"
+
+ outstring += "
|\n"
+ return outstring
+
+//Generate the big list of reaction based reactions.
+//|Name | Reagents | Reaction vars | Description | Chem properties
+/proc/generate_chemreactwiki_line(datum/chemical_reaction/CR)
+ if(CR.results.len) //Handled prior
+ return
+ var/outstring = "|[CR.name] |
").append(m.parseHTML(a)).find(d):a)}).complete(c&&function(a,b){g.each(c,e||[a.responseText,b,a])}),this},m.expr.filters.animated=function(a){return m.grep(m.timers,function(b){return a===b.elem}).length};var cd=a.document.documentElement;function dd(a){return m.isWindow(a)?a:9===a.nodeType?a.defaultView||a.parentWindow:!1}m.offset={setOffset:function(a,b,c){var d,e,f,g,h,i,j,k=m.css(a,"position"),l=m(a),n={};"static"===k&&(a.style.position="relative"),h=l.offset(),f=m.css(a,"top"),i=m.css(a,"left"),j=("absolute"===k||"fixed"===k)&&m.inArray("auto",[f,i])>-1,j?(d=l.position(),g=d.top,e=d.left):(g=parseFloat(f)||0,e=parseFloat(i)||0),m.isFunction(b)&&(b=b.call(a,c,h)),null!=b.top&&(n.top=b.top-h.top+g),null!=b.left&&(n.left=b.left-h.left+e),"using"in b?b.using.call(a,n):l.css(n)}},m.fn.extend({offset:function(a){if(arguments.length)return void 0===a?this:this.each(function(b){m.offset.setOffset(this,a,b)});var b,c,d={top:0,left:0},e=this[0],f=e&&e.ownerDocument;if(f)return b=f.documentElement,m.contains(b,e)?(typeof e.getBoundingClientRect!==K&&(d=e.getBoundingClientRect()),c=dd(f),{top:d.top+(c.pageYOffset||b.scrollTop)-(b.clientTop||0),left:d.left+(c.pageXOffset||b.scrollLeft)-(b.clientLeft||0)}):d},position:function(){if(this[0]){var a,b,c={top:0,left:0},d=this[0];return"fixed"===m.css(d,"position")?b=d.getBoundingClientRect():(a=this.offsetParent(),b=this.offset(),m.nodeName(a[0],"html")||(c=a.offset()),c.top+=m.css(a[0],"borderTopWidth",!0),c.left+=m.css(a[0],"borderLeftWidth",!0)),{top:b.top-c.top-m.css(d,"marginTop",!0),left:b.left-c.left-m.css(d,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||cd;while(a&&!m.nodeName(a,"html")&&"static"===m.css(a,"position"))a=a.offsetParent;return a||cd})}}),m.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(a,b){var c=/Y/.test(b);m.fn[a]=function(d){return V(this,function(a,d,e){var f=dd(a);return void 0===e?f?b in f?f[b]:f.document.documentElement[d]:a[d]:void(f?f.scrollTo(c?m(f).scrollLeft():e,c?e:m(f).scrollTop()):a[d]=e)},a,d,arguments.length,null)}}),m.each(["top","left"],function(a,b){m.cssHooks[b]=Lb(k.pixelPosition,function(a,c){return c?(c=Jb(a,b),Hb.test(c)?m(a).position()[b]+"px":c):void 0})}),m.each({Height:"height",Width:"width"},function(a,b){m.each({padding:"inner"+a,content:b,"":"outer"+a},function(c,d){m.fn[d]=function(d,e){var f=arguments.length&&(c||"boolean"!=typeof d),g=c||(d===!0||e===!0?"margin":"border");return V(this,function(b,c,d){var e;return m.isWindow(b)?b.document.documentElement["client"+a]:9===b.nodeType?(e=b.documentElement,Math.max(b.body["scroll"+a],e["scroll"+a],b.body["offset"+a],e["offset"+a],e["client"+a])):void 0===d?m.css(b,c,g):m.style(b,c,d,g)},b,f?d:void 0,f,null)}})}),m.fn.size=function(){return this.length},m.fn.andSelf=m.fn.addBack,"function"==typeof define&&define.amd&&define("jquery",[],function(){return m});var ed=a.jQuery,fd=a.$;return m.noConflict=function(b){return a.$===m&&(a.$=fd),b&&a.jQuery===m&&(a.jQuery=ed),m},typeof b===K&&(a.jQuery=a.$=m),m});
\ No newline at end of file
diff --git a/icons/UI_Icons/Pills/pill1.png b/icons/UI_Icons/Pills/pill1.png
new file mode 100644
index 0000000000..3412ca9d0e
Binary files /dev/null and b/icons/UI_Icons/Pills/pill1.png differ
diff --git a/icons/UI_Icons/Pills/pill10.png b/icons/UI_Icons/Pills/pill10.png
new file mode 100644
index 0000000000..06829b9b93
Binary files /dev/null and b/icons/UI_Icons/Pills/pill10.png differ
diff --git a/icons/UI_Icons/Pills/pill11.png b/icons/UI_Icons/Pills/pill11.png
new file mode 100644
index 0000000000..780d7abdfb
Binary files /dev/null and b/icons/UI_Icons/Pills/pill11.png differ
diff --git a/icons/UI_Icons/Pills/pill12.png b/icons/UI_Icons/Pills/pill12.png
new file mode 100644
index 0000000000..587936f748
Binary files /dev/null and b/icons/UI_Icons/Pills/pill12.png differ
diff --git a/icons/UI_Icons/Pills/pill13.png b/icons/UI_Icons/Pills/pill13.png
new file mode 100644
index 0000000000..226cefdadb
Binary files /dev/null and b/icons/UI_Icons/Pills/pill13.png differ
diff --git a/icons/UI_Icons/Pills/pill14.png b/icons/UI_Icons/Pills/pill14.png
new file mode 100644
index 0000000000..26c0c66ecb
Binary files /dev/null and b/icons/UI_Icons/Pills/pill14.png differ
diff --git a/icons/UI_Icons/Pills/pill15.png b/icons/UI_Icons/Pills/pill15.png
new file mode 100644
index 0000000000..1e338ca26a
Binary files /dev/null and b/icons/UI_Icons/Pills/pill15.png differ
diff --git a/icons/UI_Icons/Pills/pill16.png b/icons/UI_Icons/Pills/pill16.png
new file mode 100644
index 0000000000..e733281709
Binary files /dev/null and b/icons/UI_Icons/Pills/pill16.png differ
diff --git a/icons/UI_Icons/Pills/pill17.png b/icons/UI_Icons/Pills/pill17.png
new file mode 100644
index 0000000000..0f28a7c28a
Binary files /dev/null and b/icons/UI_Icons/Pills/pill17.png differ
diff --git a/icons/UI_Icons/Pills/pill18.png b/icons/UI_Icons/Pills/pill18.png
new file mode 100644
index 0000000000..4658d7dfc4
Binary files /dev/null and b/icons/UI_Icons/Pills/pill18.png differ
diff --git a/icons/UI_Icons/Pills/pill19.png b/icons/UI_Icons/Pills/pill19.png
new file mode 100644
index 0000000000..cf74e83ba2
Binary files /dev/null and b/icons/UI_Icons/Pills/pill19.png differ
diff --git a/icons/UI_Icons/Pills/pill2.png b/icons/UI_Icons/Pills/pill2.png
new file mode 100644
index 0000000000..f996df8536
Binary files /dev/null and b/icons/UI_Icons/Pills/pill2.png differ
diff --git a/icons/UI_Icons/Pills/pill20.png b/icons/UI_Icons/Pills/pill20.png
new file mode 100644
index 0000000000..4e10a166e9
Binary files /dev/null and b/icons/UI_Icons/Pills/pill20.png differ
diff --git a/icons/UI_Icons/Pills/pill21.png b/icons/UI_Icons/Pills/pill21.png
new file mode 100644
index 0000000000..9b4450da27
Binary files /dev/null and b/icons/UI_Icons/Pills/pill21.png differ
diff --git a/icons/UI_Icons/Pills/pill22.png b/icons/UI_Icons/Pills/pill22.png
new file mode 100644
index 0000000000..0d9d5c3c55
Binary files /dev/null and b/icons/UI_Icons/Pills/pill22.png differ
diff --git a/icons/UI_Icons/Pills/pill3.png b/icons/UI_Icons/Pills/pill3.png
new file mode 100644
index 0000000000..365fa39e7d
Binary files /dev/null and b/icons/UI_Icons/Pills/pill3.png differ
diff --git a/icons/UI_Icons/Pills/pill4.png b/icons/UI_Icons/Pills/pill4.png
new file mode 100644
index 0000000000..d7aa72da3b
Binary files /dev/null and b/icons/UI_Icons/Pills/pill4.png differ
diff --git a/icons/UI_Icons/Pills/pill5.png b/icons/UI_Icons/Pills/pill5.png
new file mode 100644
index 0000000000..ed9a84427a
Binary files /dev/null and b/icons/UI_Icons/Pills/pill5.png differ
diff --git a/icons/UI_Icons/Pills/pill6.png b/icons/UI_Icons/Pills/pill6.png
new file mode 100644
index 0000000000..675adab56d
Binary files /dev/null and b/icons/UI_Icons/Pills/pill6.png differ
diff --git a/icons/UI_Icons/Pills/pill7.png b/icons/UI_Icons/Pills/pill7.png
new file mode 100644
index 0000000000..9961be2878
Binary files /dev/null and b/icons/UI_Icons/Pills/pill7.png differ
diff --git a/icons/UI_Icons/Pills/pill8.png b/icons/UI_Icons/Pills/pill8.png
new file mode 100644
index 0000000000..879a5bcd5b
Binary files /dev/null and b/icons/UI_Icons/Pills/pill8.png differ
diff --git a/icons/UI_Icons/Pills/pill9.png b/icons/UI_Icons/Pills/pill9.png
new file mode 100644
index 0000000000..235cf5b280
Binary files /dev/null and b/icons/UI_Icons/Pills/pill9.png differ
diff --git a/icons/misc/minesweeper_tiles/eight.png b/icons/UI_Icons/minesweeper_tiles/eight.png
similarity index 100%
rename from icons/misc/minesweeper_tiles/eight.png
rename to icons/UI_Icons/minesweeper_tiles/eight.png
diff --git a/icons/misc/minesweeper_tiles/empty.png b/icons/UI_Icons/minesweeper_tiles/empty.png
similarity index 100%
rename from icons/misc/minesweeper_tiles/empty.png
rename to icons/UI_Icons/minesweeper_tiles/empty.png
diff --git a/icons/misc/minesweeper_tiles/five.png b/icons/UI_Icons/minesweeper_tiles/five.png
similarity index 100%
rename from icons/misc/minesweeper_tiles/five.png
rename to icons/UI_Icons/minesweeper_tiles/five.png
diff --git a/icons/misc/minesweeper_tiles/flag.png b/icons/UI_Icons/minesweeper_tiles/flag.png
similarity index 100%
rename from icons/misc/minesweeper_tiles/flag.png
rename to icons/UI_Icons/minesweeper_tiles/flag.png
diff --git a/icons/misc/minesweeper_tiles/four.png b/icons/UI_Icons/minesweeper_tiles/four.png
similarity index 100%
rename from icons/misc/minesweeper_tiles/four.png
rename to icons/UI_Icons/minesweeper_tiles/four.png
diff --git a/icons/misc/minesweeper_tiles/hidden.png b/icons/UI_Icons/minesweeper_tiles/hidden.png
similarity index 100%
rename from icons/misc/minesweeper_tiles/hidden.png
rename to icons/UI_Icons/minesweeper_tiles/hidden.png
diff --git a/icons/misc/minesweeper_tiles/mine.png b/icons/UI_Icons/minesweeper_tiles/mine.png
similarity index 100%
rename from icons/misc/minesweeper_tiles/mine.png
rename to icons/UI_Icons/minesweeper_tiles/mine.png
diff --git a/icons/misc/minesweeper_tiles/minehit.png b/icons/UI_Icons/minesweeper_tiles/minehit.png
similarity index 100%
rename from icons/misc/minesweeper_tiles/minehit.png
rename to icons/UI_Icons/minesweeper_tiles/minehit.png
diff --git a/icons/misc/minesweeper_tiles/one.png b/icons/UI_Icons/minesweeper_tiles/one.png
similarity index 100%
rename from icons/misc/minesweeper_tiles/one.png
rename to icons/UI_Icons/minesweeper_tiles/one.png
diff --git a/icons/misc/minesweeper_tiles/seven.png b/icons/UI_Icons/minesweeper_tiles/seven.png
similarity index 100%
rename from icons/misc/minesweeper_tiles/seven.png
rename to icons/UI_Icons/minesweeper_tiles/seven.png
diff --git a/icons/misc/minesweeper_tiles/six.png b/icons/UI_Icons/minesweeper_tiles/six.png
similarity index 100%
rename from icons/misc/minesweeper_tiles/six.png
rename to icons/UI_Icons/minesweeper_tiles/six.png
diff --git a/icons/misc/minesweeper_tiles/three.png b/icons/UI_Icons/minesweeper_tiles/three.png
similarity index 100%
rename from icons/misc/minesweeper_tiles/three.png
rename to icons/UI_Icons/minesweeper_tiles/three.png
diff --git a/icons/misc/minesweeper_tiles/two.png b/icons/UI_Icons/minesweeper_tiles/two.png
similarity index 100%
rename from icons/misc/minesweeper_tiles/two.png
rename to icons/UI_Icons/minesweeper_tiles/two.png
diff --git a/icons/effects/crayondecal.dmi b/icons/effects/crayondecal.dmi
index fcd27698e7..8c42e6610c 100644
Binary files a/icons/effects/crayondecal.dmi and b/icons/effects/crayondecal.dmi differ
diff --git a/icons/effects/effects.dmi b/icons/effects/effects.dmi
index 29086033c8..e13ce10347 100644
Binary files a/icons/effects/effects.dmi and b/icons/effects/effects.dmi differ
diff --git a/icons/emoji.dmi b/icons/emoji.dmi
index 089c0ff3f8..925b072a5f 100644
Binary files a/icons/emoji.dmi and b/icons/emoji.dmi differ
diff --git a/icons/mecha/mecha_mouse-disable.dmi b/icons/mecha/mecha_mouse-disable.dmi
new file mode 100644
index 0000000000..48924c58c2
Binary files /dev/null and b/icons/mecha/mecha_mouse-disable.dmi differ
diff --git a/icons/member_content.dmi b/icons/member_content.dmi
index bb59764627..7744371119 100644
Binary files a/icons/member_content.dmi and b/icons/member_content.dmi differ
diff --git a/icons/mob/accessories.dmi b/icons/mob/accessories.dmi
index 8e3e48230f..33964645ee 100644
Binary files a/icons/mob/accessories.dmi and b/icons/mob/accessories.dmi differ
diff --git a/icons/mob/actions.dmi b/icons/mob/actions.dmi
index 65b5733ffe..4e23c102ce 100644
Binary files a/icons/mob/actions.dmi and b/icons/mob/actions.dmi differ
diff --git a/icons/mob/aibots.dmi b/icons/mob/aibots.dmi
index f4049abc41..5f5987f2d3 100644
Binary files a/icons/mob/aibots.dmi and b/icons/mob/aibots.dmi differ
diff --git a/icons/mob/alien.dmi b/icons/mob/alien.dmi
index c50351eef3..21238366f1 100644
Binary files a/icons/mob/alien.dmi and b/icons/mob/alien.dmi differ
diff --git a/icons/mob/alienqueen.dmi b/icons/mob/alienqueen.dmi
index 115bd17cb0..1176f43edd 100644
Binary files a/icons/mob/alienqueen.dmi and b/icons/mob/alienqueen.dmi differ
diff --git a/icons/mob/animal.dmi b/icons/mob/animal.dmi
index c98ca98c42..e9343c7ed2 100644
Binary files a/icons/mob/animal.dmi and b/icons/mob/animal.dmi differ
diff --git a/icons/mob/back.dmi b/icons/mob/back.dmi
index e3c80708c3..26f81b70df 100644
Binary files a/icons/mob/back.dmi and b/icons/mob/back.dmi differ
diff --git a/icons/mob/belt.dmi b/icons/mob/belt.dmi
index 1ae0b0a2b5..6843bac6bf 100644
Binary files a/icons/mob/belt.dmi and b/icons/mob/belt.dmi differ
diff --git a/icons/mob/custom_w.dmi b/icons/mob/custom_w.dmi
index d974d0ca6c..f29231c48d 100644
Binary files a/icons/mob/custom_w.dmi and b/icons/mob/custom_w.dmi differ
diff --git a/icons/mob/eyes.dmi b/icons/mob/eyes.dmi
index aad4718cbd..b981a18f06 100644
Binary files a/icons/mob/eyes.dmi and b/icons/mob/eyes.dmi differ
diff --git a/icons/mob/feet.dmi b/icons/mob/feet.dmi
index e7598f35c9..c57a7cc112 100644
Binary files a/icons/mob/feet.dmi and b/icons/mob/feet.dmi differ
diff --git a/icons/mob/head.dmi b/icons/mob/head.dmi
index 09d6fe5374..842562b007 100644
Binary files a/icons/mob/head.dmi and b/icons/mob/head.dmi differ
diff --git a/icons/mob/hud.dmi b/icons/mob/hud.dmi
index 10474f94da..164fabe806 100644
Binary files a/icons/mob/hud.dmi and b/icons/mob/hud.dmi differ
diff --git a/icons/mob/human_parts.dmi b/icons/mob/human_parts.dmi
index 70aeba6a69..6bd504674f 100644
Binary files a/icons/mob/human_parts.dmi and b/icons/mob/human_parts.dmi differ
diff --git a/icons/mob/human_parts_greyscale.dmi b/icons/mob/human_parts_greyscale.dmi
index 3bb3874d35..8b894fea6b 100644
Binary files a/icons/mob/human_parts_greyscale.dmi and b/icons/mob/human_parts_greyscale.dmi differ
diff --git a/icons/mob/inhands/clothing_lefthand.dmi b/icons/mob/inhands/clothing_lefthand.dmi
index 2b39acd3d8..90d96492f2 100644
Binary files a/icons/mob/inhands/clothing_lefthand.dmi and b/icons/mob/inhands/clothing_lefthand.dmi differ
diff --git a/icons/mob/inhands/clothing_righthand.dmi b/icons/mob/inhands/clothing_righthand.dmi
index ef6c9b3f06..c5e10b01df 100644
Binary files a/icons/mob/inhands/clothing_righthand.dmi and b/icons/mob/inhands/clothing_righthand.dmi differ
diff --git a/icons/mob/inhands/equipment/backpack_lefthand.dmi b/icons/mob/inhands/equipment/backpack_lefthand.dmi
index 0e466486c5..3238b98757 100644
Binary files a/icons/mob/inhands/equipment/backpack_lefthand.dmi and b/icons/mob/inhands/equipment/backpack_lefthand.dmi differ
diff --git a/icons/mob/inhands/equipment/backpack_righthand.dmi b/icons/mob/inhands/equipment/backpack_righthand.dmi
index 1c265a4137..a103c1a13f 100644
Binary files a/icons/mob/inhands/equipment/backpack_righthand.dmi and b/icons/mob/inhands/equipment/backpack_righthand.dmi differ
diff --git a/icons/mob/inhands/equipment/belt_lefthand.dmi b/icons/mob/inhands/equipment/belt_lefthand.dmi
index 366493eebd..beac56725a 100644
Binary files a/icons/mob/inhands/equipment/belt_lefthand.dmi and b/icons/mob/inhands/equipment/belt_lefthand.dmi differ
diff --git a/icons/mob/inhands/equipment/belt_righthand.dmi b/icons/mob/inhands/equipment/belt_righthand.dmi
index 81b075f706..da31cc9710 100644
Binary files a/icons/mob/inhands/equipment/belt_righthand.dmi and b/icons/mob/inhands/equipment/belt_righthand.dmi differ
diff --git a/icons/mob/inhands/equipment/briefcase_lefthand.dmi b/icons/mob/inhands/equipment/briefcase_lefthand.dmi
index 08bc3814b0..38aaa98254 100644
Binary files a/icons/mob/inhands/equipment/briefcase_lefthand.dmi and b/icons/mob/inhands/equipment/briefcase_lefthand.dmi differ
diff --git a/icons/mob/inhands/equipment/briefcase_righthand.dmi b/icons/mob/inhands/equipment/briefcase_righthand.dmi
index 5cc42559cd..fbcea4580e 100644
Binary files a/icons/mob/inhands/equipment/briefcase_righthand.dmi and b/icons/mob/inhands/equipment/briefcase_righthand.dmi differ
diff --git a/icons/mob/inhands/equipment/kitchen_lefthand.dmi b/icons/mob/inhands/equipment/kitchen_lefthand.dmi
index 277a7d8f05..93cd988cff 100644
Binary files a/icons/mob/inhands/equipment/kitchen_lefthand.dmi and b/icons/mob/inhands/equipment/kitchen_lefthand.dmi differ
diff --git a/icons/mob/inhands/equipment/kitchen_righthand.dmi b/icons/mob/inhands/equipment/kitchen_righthand.dmi
index 0103bd19b5..075b4c2033 100644
Binary files a/icons/mob/inhands/equipment/kitchen_righthand.dmi and b/icons/mob/inhands/equipment/kitchen_righthand.dmi differ
diff --git a/icons/mob/inhands/equipment/security_lefthand.dmi b/icons/mob/inhands/equipment/security_lefthand.dmi
index 6ccdfba3fc..01f8e2ca27 100644
Binary files a/icons/mob/inhands/equipment/security_lefthand.dmi and b/icons/mob/inhands/equipment/security_lefthand.dmi differ
diff --git a/icons/mob/inhands/equipment/security_righthand.dmi b/icons/mob/inhands/equipment/security_righthand.dmi
index e3f930a13e..d2126fe22f 100644
Binary files a/icons/mob/inhands/equipment/security_righthand.dmi and b/icons/mob/inhands/equipment/security_righthand.dmi differ
diff --git a/icons/mob/inhands/equipment/shields_lefthand.dmi b/icons/mob/inhands/equipment/shields_lefthand.dmi
index e9d0dd0d2b..3f29410f98 100644
Binary files a/icons/mob/inhands/equipment/shields_lefthand.dmi and b/icons/mob/inhands/equipment/shields_lefthand.dmi differ
diff --git a/icons/mob/inhands/equipment/shields_righthand.dmi b/icons/mob/inhands/equipment/shields_righthand.dmi
index dda17156fe..2c3f291e43 100644
Binary files a/icons/mob/inhands/equipment/shields_righthand.dmi and b/icons/mob/inhands/equipment/shields_righthand.dmi differ
diff --git a/icons/mob/inhands/equipment/toolbox_lefthand.dmi b/icons/mob/inhands/equipment/toolbox_lefthand.dmi
index b2fa42ce5e..801df7abb8 100644
Binary files a/icons/mob/inhands/equipment/toolbox_lefthand.dmi and b/icons/mob/inhands/equipment/toolbox_lefthand.dmi differ
diff --git a/icons/mob/inhands/equipment/toolbox_righthand.dmi b/icons/mob/inhands/equipment/toolbox_righthand.dmi
index ccb15982dd..f38ddfc9a4 100644
Binary files a/icons/mob/inhands/equipment/toolbox_righthand.dmi and b/icons/mob/inhands/equipment/toolbox_righthand.dmi differ
diff --git a/icons/mob/inhands/equipment/tools_lefthand.dmi b/icons/mob/inhands/equipment/tools_lefthand.dmi
index 5b497afe53..72b994328d 100644
Binary files a/icons/mob/inhands/equipment/tools_lefthand.dmi and b/icons/mob/inhands/equipment/tools_lefthand.dmi differ
diff --git a/icons/mob/inhands/equipment/tools_righthand.dmi b/icons/mob/inhands/equipment/tools_righthand.dmi
index dbed4c43d2..ef1001c438 100644
Binary files a/icons/mob/inhands/equipment/tools_righthand.dmi and b/icons/mob/inhands/equipment/tools_righthand.dmi differ
diff --git a/icons/mob/inhands/items_lefthand.dmi b/icons/mob/inhands/items_lefthand.dmi
index f1b125eb20..71c453856a 100644
Binary files a/icons/mob/inhands/items_lefthand.dmi and b/icons/mob/inhands/items_lefthand.dmi differ
diff --git a/icons/mob/inhands/items_righthand.dmi b/icons/mob/inhands/items_righthand.dmi
index 7ef316be17..0ca09ad810 100644
Binary files a/icons/mob/inhands/items_righthand.dmi and b/icons/mob/inhands/items_righthand.dmi differ
diff --git a/icons/mob/inhands/misc/tiles_lefthand.dmi b/icons/mob/inhands/misc/tiles_lefthand.dmi
new file mode 100644
index 0000000000..d7903fcd48
Binary files /dev/null and b/icons/mob/inhands/misc/tiles_lefthand.dmi differ
diff --git a/icons/mob/inhands/misc/tiles_righthand.dmi b/icons/mob/inhands/misc/tiles_righthand.dmi
new file mode 100644
index 0000000000..9295ac7344
Binary files /dev/null and b/icons/mob/inhands/misc/tiles_righthand.dmi differ
diff --git a/icons/mob/inhands/weapons/guns_lefthand.dmi b/icons/mob/inhands/weapons/guns_lefthand.dmi
index f6ac6ca499..8978d17237 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 504121feba..3f8a876d43 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/inhands/weapons/hammers_lefthand.dmi b/icons/mob/inhands/weapons/hammers_lefthand.dmi
index 0ea340f1f3..306fd0db8a 100644
Binary files a/icons/mob/inhands/weapons/hammers_lefthand.dmi and b/icons/mob/inhands/weapons/hammers_lefthand.dmi differ
diff --git a/icons/mob/inhands/weapons/hammers_righthand.dmi b/icons/mob/inhands/weapons/hammers_righthand.dmi
index dbe34513ea..674e4d510b 100644
Binary files a/icons/mob/inhands/weapons/hammers_righthand.dmi and b/icons/mob/inhands/weapons/hammers_righthand.dmi differ
diff --git a/icons/mob/inhands/weapons/polearms_lefthand.dmi b/icons/mob/inhands/weapons/polearms_lefthand.dmi
index 5529edfa51..5e04b3daa3 100644
Binary files a/icons/mob/inhands/weapons/polearms_lefthand.dmi and b/icons/mob/inhands/weapons/polearms_lefthand.dmi differ
diff --git a/icons/mob/inhands/weapons/polearms_righthand.dmi b/icons/mob/inhands/weapons/polearms_righthand.dmi
index e902dcdc3b..4b304cfc3e 100644
Binary files a/icons/mob/inhands/weapons/polearms_righthand.dmi and b/icons/mob/inhands/weapons/polearms_righthand.dmi differ
diff --git a/icons/mob/inhands/weapons/staves_lefthand.dmi b/icons/mob/inhands/weapons/staves_lefthand.dmi
index ecc92c7d20..8b96b84236 100644
Binary files a/icons/mob/inhands/weapons/staves_lefthand.dmi and b/icons/mob/inhands/weapons/staves_lefthand.dmi differ
diff --git a/icons/mob/inhands/weapons/staves_righthand.dmi b/icons/mob/inhands/weapons/staves_righthand.dmi
index 92e5b91323..202bab9e21 100644
Binary files a/icons/mob/inhands/weapons/staves_righthand.dmi and b/icons/mob/inhands/weapons/staves_righthand.dmi differ
diff --git a/icons/mob/inhands/weapons/swords_lefthand.dmi b/icons/mob/inhands/weapons/swords_lefthand.dmi
index d6950f4e1e..d306e22892 100644
Binary files a/icons/mob/inhands/weapons/swords_lefthand.dmi and b/icons/mob/inhands/weapons/swords_lefthand.dmi differ
diff --git a/icons/mob/inhands/weapons/swords_righthand.dmi b/icons/mob/inhands/weapons/swords_righthand.dmi
index 9655829113..3e0c3424d3 100644
Binary files a/icons/mob/inhands/weapons/swords_righthand.dmi and b/icons/mob/inhands/weapons/swords_righthand.dmi differ
diff --git a/icons/mob/mask.dmi b/icons/mob/mask.dmi
index 5f0b665ff4..019ae09517 100644
Binary files a/icons/mob/mask.dmi and b/icons/mob/mask.dmi differ
diff --git a/icons/mob/mob.dmi b/icons/mob/mob.dmi
index 1649706279..9beedfb417 100644
Binary files a/icons/mob/mob.dmi and b/icons/mob/mob.dmi differ
diff --git a/icons/mob/mutant_bodyparts.dmi b/icons/mob/mutant_bodyparts.dmi
index 19ebe0a4be..f8d1f22860 100644
Binary files a/icons/mob/mutant_bodyparts.dmi and b/icons/mob/mutant_bodyparts.dmi differ
diff --git a/icons/mob/neck.dmi b/icons/mob/neck.dmi
index 3463065bc7..5eb270d23f 100644
Binary files a/icons/mob/neck.dmi and b/icons/mob/neck.dmi differ
diff --git a/icons/mob/pets.dmi b/icons/mob/pets.dmi
index fe0146ccc4..10f29f51e4 100644
Binary files a/icons/mob/pets.dmi and b/icons/mob/pets.dmi differ
diff --git a/icons/mob/restraints.dmi b/icons/mob/restraints.dmi
new file mode 100644
index 0000000000..fa7eb43aa3
Binary files /dev/null and b/icons/mob/restraints.dmi differ
diff --git a/icons/mob/robots.dmi b/icons/mob/robots.dmi
index c53fa85262..896a87ff3a 100644
Binary files a/icons/mob/robots.dmi and b/icons/mob/robots.dmi differ
diff --git a/icons/mob/screen_gen.dmi b/icons/mob/screen_gen.dmi
index 77450b6ac3..3cd7388378 100644
Binary files a/icons/mob/screen_gen.dmi and b/icons/mob/screen_gen.dmi differ
diff --git a/icons/mob/suit.dmi b/icons/mob/suit.dmi
index 42c32f134a..38ac61f318 100644
Binary files a/icons/mob/suit.dmi and b/icons/mob/suit.dmi differ
diff --git a/icons/mob/underwear.dmi b/icons/mob/underwear.dmi
index 5e6725cec7..3174397b54 100644
Binary files a/icons/mob/underwear.dmi and b/icons/mob/underwear.dmi differ
diff --git a/icons/mob/uniform.dmi b/icons/mob/uniform.dmi
index 3d281606de..072511b444 100644
Binary files a/icons/mob/uniform.dmi and b/icons/mob/uniform.dmi differ
diff --git a/icons/mob/wings.dmi b/icons/mob/wings.dmi
index b2990a1509..58f4cb735c 100644
Binary files a/icons/mob/wings.dmi and b/icons/mob/wings.dmi differ
diff --git a/icons/obj/advancedtools.dmi b/icons/obj/advancedtools.dmi
new file mode 100644
index 0000000000..974202dd58
Binary files /dev/null and b/icons/obj/advancedtools.dmi differ
diff --git a/icons/obj/assemblies/new_assemblies.dmi b/icons/obj/assemblies/new_assemblies.dmi
index df9517aeaa..fa0c138d21 100644
Binary files a/icons/obj/assemblies/new_assemblies.dmi and b/icons/obj/assemblies/new_assemblies.dmi differ
diff --git a/icons/obj/barsigns.dmi b/icons/obj/barsigns.dmi
index 50bfbace89..2c4d401088 100644
Binary files a/icons/obj/barsigns.dmi and b/icons/obj/barsigns.dmi differ
diff --git a/icons/obj/bedsheets.dmi b/icons/obj/bedsheets.dmi
index 73dcad452b..1cc99e09e9 100644
Binary files a/icons/obj/bedsheets.dmi and b/icons/obj/bedsheets.dmi differ
diff --git a/icons/obj/bloodpack.dmi b/icons/obj/bloodpack.dmi
index 3a5b9fd706..82b4c2e543 100644
Binary files a/icons/obj/bloodpack.dmi and b/icons/obj/bloodpack.dmi differ
diff --git a/icons/obj/chairs.dmi b/icons/obj/chairs.dmi
index 3754ff052c..9e8fb64aba 100644
Binary files a/icons/obj/chairs.dmi and b/icons/obj/chairs.dmi differ
diff --git a/icons/obj/chemical.dmi b/icons/obj/chemical.dmi
index 93daa6149e..5b9e13ed52 100644
Binary files a/icons/obj/chemical.dmi and b/icons/obj/chemical.dmi differ
diff --git a/icons/obj/clothing/accessories.dmi b/icons/obj/clothing/accessories.dmi
index c35956687d..eb019bc44b 100644
Binary files a/icons/obj/clothing/accessories.dmi and b/icons/obj/clothing/accessories.dmi differ
diff --git a/icons/obj/clothing/belt_overlays.dmi b/icons/obj/clothing/belt_overlays.dmi
index d7bf32d7b1..da8ff0a20b 100644
Binary files a/icons/obj/clothing/belt_overlays.dmi and b/icons/obj/clothing/belt_overlays.dmi differ
diff --git a/icons/obj/clothing/belts.dmi b/icons/obj/clothing/belts.dmi
index 3e56574fbf..e329720cf7 100644
Binary files a/icons/obj/clothing/belts.dmi and b/icons/obj/clothing/belts.dmi differ
diff --git a/icons/obj/clothing/glasses.dmi b/icons/obj/clothing/glasses.dmi
index e7bec8dbe4..75a03a585c 100644
Binary files a/icons/obj/clothing/glasses.dmi and b/icons/obj/clothing/glasses.dmi differ
diff --git a/icons/obj/clothing/hats.dmi b/icons/obj/clothing/hats.dmi
index 8256c1fc07..c8a007041b 100644
Binary files a/icons/obj/clothing/hats.dmi and b/icons/obj/clothing/hats.dmi differ
diff --git a/icons/obj/clothing/masks.dmi b/icons/obj/clothing/masks.dmi
index a0153b1596..4ec45666c4 100644
Binary files a/icons/obj/clothing/masks.dmi and b/icons/obj/clothing/masks.dmi differ
diff --git a/icons/obj/clothing/shoes.dmi b/icons/obj/clothing/shoes.dmi
index db0634181b..e3a9f6d84e 100644
Binary files a/icons/obj/clothing/shoes.dmi and b/icons/obj/clothing/shoes.dmi differ
diff --git a/icons/obj/clothing/suits.dmi b/icons/obj/clothing/suits.dmi
index 568adb69b3..fb406fb65e 100644
Binary files a/icons/obj/clothing/suits.dmi and b/icons/obj/clothing/suits.dmi differ
diff --git a/icons/obj/clothing/uniforms.dmi b/icons/obj/clothing/uniforms.dmi
index 3e3a1cefcb..50f9e0a207 100644
Binary files a/icons/obj/clothing/uniforms.dmi and b/icons/obj/clothing/uniforms.dmi differ
diff --git a/icons/obj/computer.dmi b/icons/obj/computer.dmi
index c3374e9c1c..1307f063a8 100644
Binary files a/icons/obj/computer.dmi and b/icons/obj/computer.dmi differ
diff --git a/icons/obj/crates.dmi b/icons/obj/crates.dmi
index 4165719a3c..3ab7f4b510 100644
Binary files a/icons/obj/crates.dmi and b/icons/obj/crates.dmi differ
diff --git a/icons/obj/custom.dmi b/icons/obj/custom.dmi
index eb4f5813c8..92f7f50279 100644
Binary files a/icons/obj/custom.dmi and b/icons/obj/custom.dmi differ
diff --git a/icons/obj/device.dmi b/icons/obj/device.dmi
index 55c33e5e83..032b0c27ad 100644
Binary files a/icons/obj/device.dmi and b/icons/obj/device.dmi differ
diff --git a/icons/obj/drinks.dmi b/icons/obj/drinks.dmi
index dc3aca6b19..e8b3d434f5 100644
Binary files a/icons/obj/drinks.dmi and b/icons/obj/drinks.dmi differ
diff --git a/icons/obj/femur_breaker.dmi b/icons/obj/femur_breaker.dmi
new file mode 100644
index 0000000000..4b36f4b2b8
Binary files /dev/null and b/icons/obj/femur_breaker.dmi differ
diff --git a/icons/obj/food/containers.dmi b/icons/obj/food/containers.dmi
index f64dfd4740..d3bdc1b1ea 100644
Binary files a/icons/obj/food/containers.dmi and b/icons/obj/food/containers.dmi differ
diff --git a/icons/obj/food/food.dmi b/icons/obj/food/food.dmi
index 92b5203f5d..75df4b5cc5 100644
Binary files a/icons/obj/food/food.dmi and b/icons/obj/food/food.dmi differ
diff --git a/icons/obj/food/piecake.dmi b/icons/obj/food/piecake.dmi
index 57dda21757..a74acb4e29 100644
Binary files a/icons/obj/food/piecake.dmi and b/icons/obj/food/piecake.dmi differ
diff --git a/icons/obj/food/snowcones.dmi b/icons/obj/food/snowcones.dmi
index bdaa89fdf7..8a06cf4e82 100644
Binary files a/icons/obj/food/snowcones.dmi and b/icons/obj/food/snowcones.dmi differ
diff --git a/icons/obj/guns/energy.dmi b/icons/obj/guns/energy.dmi
index bcdb324715..a6d5c8a5e1 100644
Binary files a/icons/obj/guns/energy.dmi and b/icons/obj/guns/energy.dmi differ
diff --git a/icons/obj/guns/magic.dmi b/icons/obj/guns/magic.dmi
index 47d05e3470..0a4152af64 100644
Binary files a/icons/obj/guns/magic.dmi and b/icons/obj/guns/magic.dmi differ
diff --git a/icons/obj/guns/projectile.dmi b/icons/obj/guns/projectile.dmi
index 7d44d35f55..6b2d9e5c06 100644
Binary files a/icons/obj/guns/projectile.dmi and b/icons/obj/guns/projectile.dmi differ
diff --git a/icons/obj/halloween_items.dmi b/icons/obj/halloween_items.dmi
index c08ea71148..b4f11165dc 100644
Binary files a/icons/obj/halloween_items.dmi and b/icons/obj/halloween_items.dmi differ
diff --git a/icons/obj/hydroponics/equipment.dmi b/icons/obj/hydroponics/equipment.dmi
index 82dce552a7..dd4d1e1f93 100644
Binary files a/icons/obj/hydroponics/equipment.dmi and b/icons/obj/hydroponics/equipment.dmi differ
diff --git a/icons/obj/hydroponics/growing.dmi b/icons/obj/hydroponics/growing.dmi
index 162c6b047e..e7dee2290d 100644
Binary files a/icons/obj/hydroponics/growing.dmi and b/icons/obj/hydroponics/growing.dmi differ
diff --git a/icons/obj/hydroponics/growing_flowers.dmi b/icons/obj/hydroponics/growing_flowers.dmi
index 2752ad8dc5..245841a6b4 100644
Binary files a/icons/obj/hydroponics/growing_flowers.dmi and b/icons/obj/hydroponics/growing_flowers.dmi differ
diff --git a/icons/obj/hydroponics/growing_fruits.dmi b/icons/obj/hydroponics/growing_fruits.dmi
index d309884be0..029d49e196 100644
Binary files a/icons/obj/hydroponics/growing_fruits.dmi and b/icons/obj/hydroponics/growing_fruits.dmi differ
diff --git a/icons/obj/hydroponics/harvest.dmi b/icons/obj/hydroponics/harvest.dmi
index a1ab5b08e5..fa5728f3b4 100644
Binary files a/icons/obj/hydroponics/harvest.dmi and b/icons/obj/hydroponics/harvest.dmi differ
diff --git a/icons/obj/hydroponics/seeds.dmi b/icons/obj/hydroponics/seeds.dmi
index 5a2088c332..7caf346f91 100644
Binary files a/icons/obj/hydroponics/seeds.dmi and b/icons/obj/hydroponics/seeds.dmi differ
diff --git a/icons/obj/items_and_weapons.dmi b/icons/obj/items_and_weapons.dmi
index 655d37d4a6..6336669501 100644
Binary files a/icons/obj/items_and_weapons.dmi and b/icons/obj/items_and_weapons.dmi differ
diff --git a/icons/obj/iv_drip.dmi b/icons/obj/iv_drip.dmi
index 016513245e..f530688da7 100644
Binary files a/icons/obj/iv_drip.dmi and b/icons/obj/iv_drip.dmi differ
diff --git a/icons/obj/janitor.dmi b/icons/obj/janitor.dmi
index 687fef6065..1e1033e38e 100644
Binary files a/icons/obj/janitor.dmi and b/icons/obj/janitor.dmi differ
diff --git a/icons/obj/library.dmi b/icons/obj/library.dmi
index 140f6a4d9e..20e0f5f73c 100644
Binary files a/icons/obj/library.dmi and b/icons/obj/library.dmi differ
diff --git a/icons/obj/machines/harvester.dmi b/icons/obj/machines/harvester.dmi
index d6d9b01fc6..ce272c5774 100644
Binary files a/icons/obj/machines/harvester.dmi and b/icons/obj/machines/harvester.dmi differ
diff --git a/icons/obj/machines/sleeper.dmi b/icons/obj/machines/sleeper.dmi
index b027d0d7b1..ff9e2b197a 100644
Binary files a/icons/obj/machines/sleeper.dmi and b/icons/obj/machines/sleeper.dmi differ
diff --git a/icons/obj/mining.dmi b/icons/obj/mining.dmi
index 6e05bf548b..a7593e4a55 100644
Binary files a/icons/obj/mining.dmi and b/icons/obj/mining.dmi differ
diff --git a/icons/obj/monitors.dmi b/icons/obj/monitors.dmi
index 9880707010..e38760b84d 100644
Binary files a/icons/obj/monitors.dmi and b/icons/obj/monitors.dmi differ
diff --git a/icons/obj/objects.dmi b/icons/obj/objects.dmi
index 647722b1de..87db0caa67 100644
Binary files a/icons/obj/objects.dmi and b/icons/obj/objects.dmi differ
diff --git a/icons/obj/plushes.dmi b/icons/obj/plushes.dmi
index 586bca61aa..685b14cee5 100644
Binary files a/icons/obj/plushes.dmi and b/icons/obj/plushes.dmi differ
diff --git a/icons/obj/shards.dmi b/icons/obj/shards.dmi
index a575ab292f..e7875efa26 100644
Binary files a/icons/obj/shards.dmi and b/icons/obj/shards.dmi differ
diff --git a/icons/obj/smooth_structures/fancy_table_blue.dmi b/icons/obj/smooth_structures/fancy_table_blue.dmi
new file mode 100644
index 0000000000..07e13e99e6
Binary files /dev/null and b/icons/obj/smooth_structures/fancy_table_blue.dmi differ
diff --git a/icons/obj/smooth_structures/fancy_table_cyan.dmi b/icons/obj/smooth_structures/fancy_table_cyan.dmi
new file mode 100644
index 0000000000..4f1a90e33b
Binary files /dev/null and b/icons/obj/smooth_structures/fancy_table_cyan.dmi differ
diff --git a/icons/obj/smooth_structures/fancy_table_green.dmi b/icons/obj/smooth_structures/fancy_table_green.dmi
new file mode 100644
index 0000000000..ea7f8daa99
Binary files /dev/null and b/icons/obj/smooth_structures/fancy_table_green.dmi differ
diff --git a/icons/obj/smooth_structures/fancy_table_orange.dmi b/icons/obj/smooth_structures/fancy_table_orange.dmi
new file mode 100644
index 0000000000..fe0375ddbb
Binary files /dev/null and b/icons/obj/smooth_structures/fancy_table_orange.dmi differ
diff --git a/icons/obj/smooth_structures/fancy_table_purple.dmi b/icons/obj/smooth_structures/fancy_table_purple.dmi
new file mode 100644
index 0000000000..c404a9eb5f
Binary files /dev/null and b/icons/obj/smooth_structures/fancy_table_purple.dmi differ
diff --git a/icons/obj/smooth_structures/fancy_table_red.dmi b/icons/obj/smooth_structures/fancy_table_red.dmi
new file mode 100644
index 0000000000..8bca0ca8c9
Binary files /dev/null and b/icons/obj/smooth_structures/fancy_table_red.dmi differ
diff --git a/icons/obj/smooth_structures/fancy_table_royalblack.dmi b/icons/obj/smooth_structures/fancy_table_royalblack.dmi
new file mode 100644
index 0000000000..064b7c1945
Binary files /dev/null and b/icons/obj/smooth_structures/fancy_table_royalblack.dmi differ
diff --git a/icons/obj/smooth_structures/fancy_table_royalblue.dmi b/icons/obj/smooth_structures/fancy_table_royalblue.dmi
new file mode 100644
index 0000000000..9d0eba7265
Binary files /dev/null and b/icons/obj/smooth_structures/fancy_table_royalblue.dmi differ
diff --git a/icons/obj/smooth_structures/plasmaglass_table.dmi b/icons/obj/smooth_structures/plasmaglass_table.dmi
new file mode 100644
index 0000000000..808e79aa43
Binary files /dev/null and b/icons/obj/smooth_structures/plasmaglass_table.dmi differ
diff --git a/icons/obj/stack_objects.dmi b/icons/obj/stack_objects.dmi
index 6d2b2b64cd..ac2d42378a 100644
Binary files a/icons/obj/stack_objects.dmi and b/icons/obj/stack_objects.dmi differ
diff --git a/icons/obj/storage.dmi b/icons/obj/storage.dmi
index 9037bfc0d0..fae8134791 100644
Binary files a/icons/obj/storage.dmi and b/icons/obj/storage.dmi differ
diff --git a/icons/obj/structures.dmi b/icons/obj/structures.dmi
index 2e1fdfa7a8..cad28e12fe 100644
Binary files a/icons/obj/structures.dmi and b/icons/obj/structures.dmi differ
diff --git a/icons/obj/surgery.dmi b/icons/obj/surgery.dmi
index 1a3b344566..bdfbae3d75 100755
Binary files a/icons/obj/surgery.dmi and b/icons/obj/surgery.dmi differ
diff --git a/icons/obj/syringe.dmi b/icons/obj/syringe.dmi
index 80e681399a..59bc7a8e7c 100644
Binary files a/icons/obj/syringe.dmi and b/icons/obj/syringe.dmi differ
diff --git a/icons/obj/tiles.dmi b/icons/obj/tiles.dmi
index 9305e4b7bc..3aa6912da7 100644
Binary files a/icons/obj/tiles.dmi and b/icons/obj/tiles.dmi differ
diff --git a/icons/obj/tools.dmi b/icons/obj/tools.dmi
index cfb36bb3ae..6130c67eb3 100644
Binary files a/icons/obj/tools.dmi and b/icons/obj/tools.dmi differ
diff --git a/icons/turf/decals.dmi b/icons/turf/decals.dmi
index f7f259ab04..616cb1f521 100644
Binary files a/icons/turf/decals.dmi and b/icons/turf/decals.dmi differ
diff --git a/icons/turf/floors/carpet_blue.dmi b/icons/turf/floors/carpet_blue.dmi
new file mode 100644
index 0000000000..f797be9745
Binary files /dev/null and b/icons/turf/floors/carpet_blue.dmi differ
diff --git a/icons/turf/floors/carpet_cyan.dmi b/icons/turf/floors/carpet_cyan.dmi
new file mode 100644
index 0000000000..feca351ca9
Binary files /dev/null and b/icons/turf/floors/carpet_cyan.dmi differ
diff --git a/icons/turf/floors/carpet_green.dmi b/icons/turf/floors/carpet_green.dmi
new file mode 100644
index 0000000000..fdd1f071f7
Binary files /dev/null and b/icons/turf/floors/carpet_green.dmi differ
diff --git a/icons/turf/floors/carpet_orange.dmi b/icons/turf/floors/carpet_orange.dmi
new file mode 100644
index 0000000000..ddf239b63b
Binary files /dev/null and b/icons/turf/floors/carpet_orange.dmi differ
diff --git a/icons/turf/floors/carpet_purple.dmi b/icons/turf/floors/carpet_purple.dmi
new file mode 100644
index 0000000000..c1f40ec7fa
Binary files /dev/null and b/icons/turf/floors/carpet_purple.dmi differ
diff --git a/icons/turf/floors/carpet_red.dmi b/icons/turf/floors/carpet_red.dmi
new file mode 100644
index 0000000000..926655688e
Binary files /dev/null and b/icons/turf/floors/carpet_red.dmi differ
diff --git a/icons/turf/floors/carpet_royalblack.dmi b/icons/turf/floors/carpet_royalblack.dmi
new file mode 100644
index 0000000000..bc5cef1cf0
Binary files /dev/null and b/icons/turf/floors/carpet_royalblack.dmi differ
diff --git a/icons/turf/floors/carpet_royalblue.dmi b/icons/turf/floors/carpet_royalblue.dmi
new file mode 100644
index 0000000000..841e49e957
Binary files /dev/null and b/icons/turf/floors/carpet_royalblue.dmi differ
diff --git a/interface/interface.dm b/interface/interface.dm
index 8a4ba5b96b..215765c88d 100644
--- a/interface/interface.dm
+++ b/interface/interface.dm
@@ -4,11 +4,15 @@
set desc = "Type what you want to know about. This will open the wiki in your web browser. Type nothing to go to the main page."
set hidden = 1
var/wikiurl = CONFIG_GET(string/wikiurl)
+ var/wikiurltg = CONFIG_GET(string/wikiurltg)
if(wikiurl)
if(query)
- var/output = wikiurl + "/index.php?title=Special%3ASearch&profile=default&search=" + query
+ var/output = wikiurl + "?search=" + query
+ src << link(output)
+ output = wikiurltg + "/index.php?title=Special%3ASearch&profile=default&search=" + query
src << link(output)
else if (query != null)
+ src << link(wikiurltg)
src << link(wikiurl)
else
to_chat(src, "The wiki URL is not set in the server configuration.")
diff --git a/interface/stylesheet.dm b/interface/stylesheet.dm
index cdf6df2dab..c51778bbdb 100644
--- a/interface/stylesheet.dm
+++ b/interface/stylesheet.dm
@@ -153,6 +153,31 @@ h1.alert, h2.alert {color: #000000;}
.redtext {color: #FF0000; font-size: 3;}
.clown {color: #FF69Bf; font-size: 3; font-family: "Comic Sans MS", cursive, sans-serif; font-weight: bold;}
.his_grace {color: #15D512; font-family: "Courier New", cursive, sans-serif; font-style: italic;}
+.spooky {color: #FF9100;}
+.velvet {color: #660015; font-weight: bold; animation: velvet 5000ms infinite;}
+@keyframes velvet {
+ 0% { color: #400020; }
+ 40% { color: #FF0000; }
+ 50% { color: #FF8888; }
+ 60% { color: #FF0000; }
+ 100% { color: #400020; }
+}
+
+.hypnophrase {color: #3bb5d3; font-weight: bold; animation: hypnocolor 1500ms infinite;}
+@keyframes hypnocolor {
+ 0% { color: #0d0d0d; }
+ 25% { color: #410194; }
+ 50% { color: #7f17d8; }
+ 75% { color: #410194; }
+ 100% { color: #3bb5d3; }
+}
+
+.phobia {color: #dd0000; font-weight: bold; animation: phobia 750ms infinite;}
+ @keyframes phobia {
+ 0% { color: #0d0d0d; }
+ 50% { color: #dd0000; }
+ 100% { color: #0d0d0d; }
+}
.icon {height: 1em; width: auto;}
diff --git a/modular_citadel/code/_onclick/hud/screen_objects.dm b/modular_citadel/code/_onclick/hud/screen_objects.dm
index 511627b81f..3a0eb364cb 100644
--- a/modular_citadel/code/_onclick/hud/screen_objects.dm
+++ b/modular_citadel/code/_onclick/hud/screen_objects.dm
@@ -1,25 +1,3 @@
-/obj/screen/mov_intent
- icon = 'modular_citadel/icons/ui/screen_midnight.dmi'
-
-/obj/screen/sprintbutton
- name = "toggle sprint"
- icon = 'modular_citadel/icons/ui/screen_midnight.dmi'
- icon_state = "act_sprint"
- layer = ABOVE_HUD_LAYER - 0.1
-
-/obj/screen/sprintbutton/Click()
- if(ishuman(usr))
- var/mob/living/carbon/human/H = usr
- H.togglesprint()
-
-/obj/screen/sprintbutton/proc/insert_witty_toggle_joke_here(mob/living/carbon/human/H)
- if(!H)
- return
- if(H.sprinting)
- icon_state = "act_sprint_on"
- else
- icon_state = "act_sprint"
-
/obj/screen/restbutton
name = "rest"
icon = 'modular_citadel/icons/ui/screen_midnight.dmi'
diff --git a/modular_citadel/code/_onclick/hud/sprint.dm b/modular_citadel/code/_onclick/hud/sprint.dm
new file mode 100644
index 0000000000..290fcd368e
--- /dev/null
+++ b/modular_citadel/code/_onclick/hud/sprint.dm
@@ -0,0 +1,41 @@
+/obj/screen/mov_intent
+ icon = 'modular_citadel/icons/ui/screen_midnight.dmi'
+
+/obj/screen/sprintbutton
+ name = "toggle sprint"
+ icon = 'modular_citadel/icons/ui/screen_midnight.dmi'
+ icon_state = "act_sprint"
+ layer = ABOVE_HUD_LAYER - 0.1
+
+/obj/screen/sprintbutton/Click()
+ if(ishuman(usr))
+ var/mob/living/carbon/human/H = usr
+ H.togglesprint()
+
+/obj/screen/sprintbutton/proc/insert_witty_toggle_joke_here(mob/living/carbon/human/H)
+ if(!H)
+ return
+ if(H.sprinting)
+ icon_state = "act_sprint_on"
+ else
+ icon_state = "act_sprint"
+
+//Sprint buffer onscreen code.
+/datum/hud/var/obj/screen/sprint_buffer/sprint_buffer
+
+/obj/screen/sprint_buffer
+ name = "sprint buffer"
+ icon = 'icons/effects/progessbar.dmi'
+ icon_state = "prog_bar_100"
+
+/obj/screen/sprint_buffer/Click()
+ if(isliving(usr))
+ var/mob/living/L = usr
+ to_chat(L, "Your sprint buffer's maximum capacity is [L.sprint_buffer_max]. It is currently at [L.sprint_buffer], regenerating at [L.sprint_buffer_regen_ds * 10] per second. \
+ Sprinting while this is empty will incur a [L.sprint_stamina_cost] stamina cost per tile.")
+
+/obj/screen/sprint_buffer/proc/update_to_mob(mob/living/L)
+ var/amount = 0
+ if(L.sprint_buffer_max > 0)
+ amount = round(CLAMP((L.sprint_buffer / L.sprint_buffer_max) * 100, 0, 100), 5)
+ icon_state = "prog_bar_[amount]"
diff --git a/modular_citadel/code/_onclick/item_attack.dm b/modular_citadel/code/_onclick/item_attack.dm
index dcc9f567e2..80281ee084 100644
--- a/modular_citadel/code/_onclick/item_attack.dm
+++ b/modular_citadel/code/_onclick/item_attack.dm
@@ -17,9 +17,3 @@
/obj/item/proc/altafterattack(atom/target, mob/user, proximity_flag, click_parameters)
return FALSE
-
-/obj/item/proc/getweight()
- if(total_mass)
- return max(total_mass,MIN_MELEE_STAMCOST)
- else
- return w_class*1.25
diff --git a/modular_citadel/code/controllers/subsystem/job.dm b/modular_citadel/code/controllers/subsystem/job.dm
index c433042ae6..46aef6f529 100644
--- a/modular_citadel/code/controllers/subsystem/job.dm
+++ b/modular_citadel/code/controllers/subsystem/job.dm
@@ -13,7 +13,7 @@
var/permitted = TRUE
if(G.restricted_roles && G.restricted_roles.len && !(M.mind.assigned_role in G.restricted_roles))
permitted = FALSE
- if(G.ckeywhitelist && G.ckeywhitelist.len && !(the_mob.client.ckey in G.ckeywhitelist))
+ if(G.donoritem && !G.donator_ckey_check(the_mob.client.ckey))
permitted = FALSE
if(!equipbackpackstuff && G.category == SLOT_IN_BACKPACK)//snowflake check since plopping stuff in the backpack doesnt work for pre-job equip loadout stuffs
permitted = FALSE
diff --git a/modular_citadel/code/controllers/subsystem/shuttle.dm b/modular_citadel/code/controllers/subsystem/shuttle.dm
index bb4592f819..ce3f062bdd 100644
--- a/modular_citadel/code/controllers/subsystem/shuttle.dm
+++ b/modular_citadel/code/controllers/subsystem/shuttle.dm
@@ -1,6 +1,7 @@
-/datum/controller/subsystem/shuttle/proc/autoEnd() //CIT CHANGE - allows shift to end after 3 hours has passed.
- if((world.realtime - SSshuttle.realtimeofstart) > auto_call && EMERGENCY_IDLE_OR_RECALLED) //3 hours
- SSshuttle.emergency.request()
- priority_announce("The shift has come to an end and the shuttle called.")
+/datum/controller/subsystem/shuttle/proc/autoEnd() //CIT CHANGE - allows shift to end after 2 hours have passed.
+ if((world.realtime - SSshuttle.realtimeofstart) > auto_call && EMERGENCY_IDLE_OR_RECALLED) //2 hours
+ SSshuttle.emergency.request(silent = TRUE)
+ priority_announce("The shift has come to an end and the shuttle called. [seclevel2num(get_security_level()) == SEC_LEVEL_RED ? "Red Alert state confirmed: Dispatching priority shuttle. " : "" ]It will arrive in [emergency.timeLeft(600)] minutes.", null, 'sound/ai/shuttlecalled.ogg', "Priority")
log_game("Round time limit reached. Shuttle has been auto-called.")
message_admins("Round time limit reached. Shuttle called.")
+ emergencyNoRecall = TRUE
diff --git a/modular_citadel/code/datums/mood_events/chem_events.dm b/modular_citadel/code/datums/mood_events/chem_events.dm
new file mode 100644
index 0000000000..c496a0fa27
--- /dev/null
+++ b/modular_citadel/code/datums/mood_events/chem_events.dm
@@ -0,0 +1,59 @@
+/datum/mood_event/eigenstate
+ mood_change = -3
+ description = "Where the hell am I? Is this an alternative dimension ?\n"
+
+/datum/mood_event/enthrall
+ mood_change = 5
+
+/datum/mood_event/enthrall/add_effects(message)
+ description = "[message]\n"
+
+/datum/mood_event/enthrallpraise
+ mood_change = 10
+ timeout = 1 MINUTES
+
+/datum/mood_event/enthrallpraise/add_effects(message)
+ description = "[message]\n"
+
+/datum/mood_event/enthrallscold
+ mood_change = -10
+ timeout = 1 MINUTES
+
+/datum/mood_event/enthrallscold/add_effects(message)
+ description = "[message]\n"//aaa I'm not kinky enough for this
+
+/datum/mood_event/enthrallmissing1
+ mood_change = -5
+
+/datum/mood_event/enthrallmissing1/add_effects(message)
+ description = "[message]\n"
+
+/datum/mood_event/enthrallmissing2
+ mood_change = -10
+
+/datum/mood_event/enthrallmissing2/add_effects(message)
+ description = "[message]\n"
+
+/datum/mood_event/enthrallmissing3
+ mood_change = -15
+
+/datum/mood_event/enthrallmissing3/add_effects(message)
+ description = "[message]\n"
+
+/datum/mood_event/enthrallmissing4
+ mood_change = -25
+
+/datum/mood_event/enthrallmissing4/add_effects(message)
+ description = "[message]\n"
+
+/datum/mood_event/InLove
+ mood_change = 10
+
+/datum/mood_event/InLove/add_effects(message)
+ description = "[message]\n"
+
+/datum/mood_event/MissingLove
+ mood_change = -10
+
+/datum/mood_event/MissingLove/add_effects(message)
+ description = "[message]\n"
diff --git a/modular_citadel/code/datums/mood_events/generic_negative_events.dm b/modular_citadel/code/datums/mood_events/generic_negative_events.dm
index c0f2591656..bb04b0b283 100644
--- a/modular_citadel/code/datums/mood_events/generic_negative_events.dm
+++ b/modular_citadel/code/datums/mood_events/generic_negative_events.dm
@@ -3,19 +3,19 @@
/datum/mood_event/plushjack
description = "I have butchered a plush recently.\n"
mood_change = -1
- timeout = 1200
+ timeout = 2 MINUTES
/datum/mood_event/plush_nostuffing
description = "A plush I tried to pet had no stuffing...\n"
mood_change = -1
- timeout = 1200
+ timeout = 2 MINUTES
/datum/mood_event/emptypred
description = "I had to let someone out.\n"
mood_change = -2
- timeout = 600
+ timeout = 1 MINUTES
/datum/mood_event/emptyprey
description = "It feels quite cold out here.\n"
mood_change = -2
- timeout = 600
\ No newline at end of file
+ timeout = 1 MINUTES
diff --git a/modular_citadel/code/datums/mood_events/generic_positive_events.dm b/modular_citadel/code/datums/mood_events/generic_positive_events.dm
index 7b989d7700..ffa661e6e9 100644
--- a/modular_citadel/code/datums/mood_events/generic_positive_events.dm
+++ b/modular_citadel/code/datums/mood_events/generic_positive_events.dm
@@ -3,12 +3,12 @@
/datum/mood_event/headpat
description = "Headpats are nice.\n"
mood_change = 2
- timeout = 1200
+ timeout = 2 MINUTES
/datum/mood_event/hugbox
description = "I hugged a box of hugs recently.\n"
mood_change = 1
- timeout = 1200
+ timeout = 2 MINUTES
/datum/mood_event/plushpet
description = "I pet a plush recently.\n"
diff --git a/modular_citadel/code/datums/mood_events/moodular.dm b/modular_citadel/code/datums/mood_events/moodular.dm
index b53ce417e8..aa87f1b97a 100644
--- a/modular_citadel/code/datums/mood_events/moodular.dm
+++ b/modular_citadel/code/datums/mood_events/moodular.dm
@@ -3,9 +3,7 @@
// box of hugs
/obj/item/storage/box/hug/attack_self(mob/user)
. = ..()
- GET_COMPONENT_FROM(mood, /datum/component/mood, user)
- if(mood)
- mood.add_event("hugbox", /datum/mood_event/hugbox)
+ SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT,"hugbox", /datum/mood_event/hugbox)
//Removed headpats here, duplicate code?
@@ -13,25 +11,17 @@
/obj/item/toy/plush/attack_self(mob/user)
. = ..()
if(stuffed || grenade)
- GET_COMPONENT_FROM(mood, /datum/component/mood, user)
- if(mood)
- mood.add_event("plushpet", /datum/mood_event/plushpet)
+ SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT,"plushpet", /datum/mood_event/plushpet)
else
- GET_COMPONENT_FROM(mood, /datum/component/mood, user)
- if(mood)
- mood.add_event("plush_nostuffing", /datum/mood_event/plush_nostuffing)
+ SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT,"plush_nostuffing", /datum/mood_event/plush_nostuffing)
// Jack the Ripper starring plush
/obj/item/toy/plush/attackby(obj/item/I, mob/living/user, params)
. = ..()
if(I.is_sharp())
if(!grenade)
- GET_COMPONENT_FROM(mood, /datum/component/mood, user)
- if(mood)
- mood.add_event("plushjack", /datum/mood_event/plushjack)
+ SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT,"plushjack", /datum/mood_event/plushjack)
// plush playing (plush-on-plush action)
if(istype(I, /obj/item/toy/plush))
- GET_COMPONENT_FROM(mood, /datum/component/mood, user)
- if(mood)
- mood.add_event("plushplay", /datum/mood_event/plushplay)
+ SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT,"plushplay", /datum/mood_event/plushplay)
diff --git a/modular_citadel/code/datums/status_effects/chems.dm b/modular_citadel/code/datums/status_effects/chems.dm
new file mode 100644
index 0000000000..1b1886173b
--- /dev/null
+++ b/modular_citadel/code/datums/status_effects/chems.dm
@@ -0,0 +1,759 @@
+#define DICK_MOVEMENT_SPEED "hugedick"
+#define BREAST_MOVEMENT_SPEED "megamilk"
+
+/datum/status_effect/chem/SGDF
+ id = "SGDF"
+ var/mob/living/fermi_Clone
+ var/mob/living/original
+ var/datum/mind/originalmind
+ var/status_set = FALSE
+ alert_type = null
+
+/datum/status_effect/chem/SGDF/on_apply()
+ log_game("FERMICHEM: SGDF status appied on [owner], ID: [owner.key]")
+ fermi_Clone = owner
+ return ..()
+
+/datum/status_effect/chem/SGDF/tick()
+ if(!status_set)
+ return ..()
+ if(original.stat == DEAD || original == null || !original)
+ if((fermi_Clone && fermi_Clone.stat != DEAD) || (fermi_Clone == null))
+ if(originalmind)
+ owner.remove_status_effect(src)
+ ..()
+
+/datum/status_effect/chem/SGDF/on_remove(mob/living/carbon/M)
+ log_game("FERMICHEM: SGDF mind shift applied. [owner] is now playing as their clone and should not have memories after their clone split (look up SGDF status applied). ID: [owner.key]")
+ originalmind.transfer_to(fermi_Clone)
+ to_chat(owner, "Lucidity shoots to your previously blank mind as your mind suddenly finishes the cloning process. You marvel for a moment at yourself, as your mind subconciously recollects all your memories up until the point when you cloned yourself. curiously, you find that you memories are blank after you ingested the sythetic serum, leaving you to wonder where the other you is.")
+ to_chat(M, "Lucidity shoots to your previously blank mind as your mind suddenly finishes the cloning process. You marvel for a moment at yourself, as your mind subconciously recollects all your memories up until the point when you cloned yourself. curiously, you find that you memories are blank after you ingested the sythetic serum, leaving you to wonder where the other you is.")
+ fermi_Clone = null
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/datum/status_effect/chem/breast_enlarger
+ id = "breast_enlarger"
+ alert_type = null
+ var/moveCalc = 1
+ var/cachedmoveCalc = 1
+
+/datum/status_effect/chem/breast_enlarger/on_apply(mob/living/carbon/human/H)//Removes clothes, they're too small to contain you. You belong to space now.
+ log_game("FERMICHEM: [owner]'s breasts has reached comical sizes. ID: [owner.key]")
+ var/mob/living/carbon/human/o = owner
+ var/items = o.get_contents()
+ for(var/obj/item/W in items)
+ if(W == o.w_uniform || W == o.wear_suit)
+ o.dropItemToGround(W, TRUE)
+ playsound(o.loc, 'sound/items/poster_ripped.ogg', 50, 1)
+ to_chat(o, "Your clothes give, ripping into peices under the strain of your swelling breasts! Unless you manage to reduce the size of your breasts, there's no way you're going to be able to put anything on over these melons..!")
+ o.visible_message("[o]'s chest suddenly bursts forth, ripping their clothes off!'")
+ else
+ to_chat(o, "Your bountiful bosom is so rich with mass, you seriously doubt you'll be able to fit any clothes over it.")
+ return ..()
+
+/datum/status_effect/chem/breast_enlarger/tick(mob/living/carbon/human/H)//If you try to wear clothes, you fail. Slows you down if you're comically huge
+ var/mob/living/carbon/human/o = owner
+ var/obj/item/organ/genital/breasts/B = o.getorganslot("breasts")
+ moveCalc = 1+((round(B.cached_size) - 9)/3) //Afffects how fast you move, and how often you can click.
+ if(!B)
+ o.remove_movespeed_modifier(BREAST_MOVEMENT_SPEED)
+ sizeMoveMod(1)
+ owner.remove_status_effect(src)
+ var/items = o.get_contents()
+ for(var/obj/item/W in items)
+ if(W == o.w_uniform || W == o.wear_suit)
+ o.dropItemToGround(W, TRUE)
+ playsound(o.loc, 'sound/items/poster_ripped.ogg', 50, 1)
+ to_chat(owner, "Your enormous breasts are way too large to fit anything over them!")
+ if (B.size == "huge")
+ if(prob(1))
+ to_chat(owner, "Your back is feeling sore.")
+ var/target = o.get_bodypart(BODY_ZONE_CHEST)
+ o.apply_damage(0.1, BRUTE, target)
+ if(!B.cached_size == B.breast_values[B.prev_size])
+ o.add_movespeed_modifier(BREAST_MOVEMENT_SPEED, TRUE, 100, NONE, override = TRUE, multiplicative_slowdown = moveCalc)
+ sizeMoveMod(moveCalc)
+ return ..()
+ else if (B.breast_values[B.size] > B.breast_values[B.prev_size])
+ o.add_movespeed_modifier(BREAST_MOVEMENT_SPEED, TRUE, 100, NONE, override = TRUE, multiplicative_slowdown = moveCalc)
+ sizeMoveMod(moveCalc)
+ else if (B.breast_values[B.size] < B.breast_values[B.prev_size])
+ o.add_movespeed_modifier(BREAST_MOVEMENT_SPEED, TRUE, 100, NONE, override = TRUE, multiplicative_slowdown = moveCalc)
+ sizeMoveMod(moveCalc)
+ if((B.cached_size) < 16)
+ switch(round(B.cached_size))
+ if(9)
+ if (B.breast_values[B.prev_size] != B.breast_values[B.size])
+ to_chat(o, "Your expansive chest has become a more managable size, liberating your movements.")
+ if(10 to INFINITY)
+ if (B.breast_values[B.prev_size] != B.breast_values[B.size])
+ to_chat(H, "Your indulgent busom is so substantial, it's affecting your movements!")
+ if(prob(1))
+ to_chat(owner, "Your back is feeling a little sore.")
+ ..()
+
+/datum/status_effect/chem/breast_enlarger/on_remove(mob/living/carbon/M)
+ log_game("FERMICHEM: [owner]'s breasts has reduced to an acceptable size. ID: [owner.key]")
+ owner.remove_movespeed_modifier(BREAST_MOVEMENT_SPEED)
+ sizeMoveMod(1)
+
+/datum/status_effect/chem/breast_enlarger/proc/sizeMoveMod(var/value)
+ if(cachedmoveCalc == value)
+ return
+ owner.next_move_modifier /= cachedmoveCalc
+ owner.next_move_modifier *= value
+ cachedmoveCalc = value
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/datum/status_effect/chem/penis_enlarger
+ id = "penis_enlarger"
+ alert_type = null
+ var/bloodCalc
+ var/moveCalc
+
+/datum/status_effect/chem/penis_enlarger/on_apply(mob/living/carbon/human/H)//Removes clothes, they're too small to contain you. You belong to space now.
+ log_game("FERMICHEM: [owner]'s dick has reached comical sizes. ID: [owner.key]")
+ var/mob/living/carbon/human/o = owner
+ var/items = o.get_contents()
+ if(o.w_uniform || o.wear_suit)
+ to_chat(o, "Your clothes give, ripping into peices under the strain of your swelling pecker! Unless you manage to reduce the size of your emancipated trouser snake, there's no way you're going to be able to put anything on over this girth..!")
+ owner.visible_message("[o]'s schlong suddenly bursts forth, ripping their clothes off!'")
+ else
+ to_chat(o, "Your emancipated trouser snake is so ripe with girth, you seriously doubt you'll be able to fit any clothes over it.")
+ for(var/obj/item/W in items)
+ if(W == o.w_uniform || W == o.wear_suit)
+ o.dropItemToGround(W, TRUE)
+ playsound(o.loc, 'sound/items/poster_ripped.ogg', 50, 1)
+ return ..()
+
+
+/datum/status_effect/chem/penis_enlarger/tick(mob/living/carbon/M)
+ var/mob/living/carbon/human/o = owner
+ var/obj/item/organ/genital/penis/P = o.getorganslot("penis")
+ moveCalc = 1+((round(P.length) - 21)/3) //effects how fast you can move
+ bloodCalc = 1+((round(P.length) - 21)/15) //effects how much blood you need (I didn' bother adding an arousal check because I'm spending too much time on this organ already.)
+ if(!P)
+ o.remove_movespeed_modifier(DICK_MOVEMENT_SPEED)
+ o.ResetBloodVol()
+ owner.remove_status_effect(src)
+ var/items = o.get_contents()
+ for(var/obj/item/W in items)
+ if(W == o.w_uniform || W == o.wear_suit)
+ o.dropItemToGround(W, TRUE)
+ playsound(o.loc, 'sound/items/poster_ripped.ogg', 50, 1)
+ to_chat(owner, "Your enormous package is way to large to fit anything over!")
+ switch(round(P.cached_length))
+ if(21)
+ to_chat(o, "Your rascally willy has become a more managable size, liberating your movements.")
+ o.remove_movespeed_modifier(DICK_MOVEMENT_SPEED)
+ o.AdjustBloodVol(bloodCalc)
+ if(22 to INFINITY)
+ if(prob(2))
+ to_chat(o, "Your indulgent johnson is so substantial, it's taking all your blood and affecting your movements!")
+ o.add_movespeed_modifier(DICK_MOVEMENT_SPEED, TRUE, 100, NONE, override = TRUE, multiplicative_slowdown = moveCalc)
+ o.AdjustBloodVol(bloodCalc)
+ ..()
+
+/datum/status_effect/chem/penis_enlarger/on_remove(mob/living/carbon/human/o)
+ log_game("FERMICHEM: [owner]'s dick has reduced to an acceptable size. ID: [owner.key]")
+ owner.remove_movespeed_modifier(DICK_MOVEMENT_SPEED)
+ owner.ResetBloodVol()
+
+
+/*//////////////////////////////////////////
+ Mind control functions
+///////////////////////////////////////////
+*/
+
+//Preamble
+/*
+/mob/living
+ var/lewd = TRUE
+*/
+
+/mob/living/verb/toggle_lewd()
+ set category = "IC"
+ set name = "Toggle Lewdchem"
+ set desc = "Allows you to toggle if you'd like lewd flavour messages."
+ client.prefs.lewdchem = !(client.prefs.lewdchem)
+ to_chat(usr, "You [(client.prefs.lewdchem?"will":"no longer")] receive lewdchem messages.")
+
+/datum/status_effect/chem/enthrall
+ id = "enthrall"
+ alert_type = null
+ //examine_text TODO
+ var/enthrallTally = 1 //Keeps track of the enthralling process
+ var/resistanceTally = 0 //Keeps track of the resistance
+ var/deltaResist //The total resistance added per resist click
+
+ var/phase = 1 //-1: resisted state, due to be removed.0: sleeper agent, no effects unless triggered 1: initial, 2: 2nd stage - more commands, 3rd: fully enthralled, 4th Mindbroken
+
+ var/status = null //status effects
+ var/statusStrength = 0 //strength of status effect
+
+ var/mob/living/master //Enchanter's person
+ var/enthrallID //Enchanter's ckey
+ var/enthrallGender //Use master or mistress
+
+ var/mental_capacity //Higher it is, lower the cooldown on commands, capacity reduces with resistance.
+ var/datum/weakref/redirect_component //resistance
+
+ var/distancelist = list(2,1.5,1,0.8,0.6,0.5,0.4,0.3,0.2) //Distance multipliers
+
+ var/withdrawal = FALSE //withdrawl
+ var/withdrawalTick = 0 //counts how long withdrawl is going on for
+
+ var/list/customTriggers = list() //the list of custom triggers
+
+ var/cooldown = 0 //cooldown on commands
+ var/cooldownMsg = TRUE //If cooldown message has been sent
+ var/cTriggered = FALSE //If someone is triggered (so they can't trigger themselves with what they say for infinite loops)
+ var/resistGrowth = 0 //Resistance accrues over time
+ var/DistApart = 1 //Distance between master and owner
+ var/tranceTime = 0 //how long trance effects apply on trance status
+
+ var/customEcho //Custom looping text in owner
+ var/customSpan //Custom spans for looping text
+
+/datum/status_effect/chem/enthrall/on_apply()
+ var/mob/living/carbon/M = owner
+ var/datum/reagent/fermi/enthrall/E = locate(/datum/reagent/fermi/enthrall) in M.reagents.reagent_list
+ if(!E)
+ message_admins("WARNING: FermiChem: No master found in thrall, did you bus in the status? You need to set up the vars manually in the chem if it's not reacted/bussed. Someone set up the reaction/status proc incorrectly if not (Don't use donor blood). Console them with a chemcat plush maybe?")
+ owner.remove_status_effect(src)
+ enthrallID = E.creatorID
+ enthrallGender = E.creatorGender
+ master = get_mob_by_key(enthrallID)
+ //if(M.ckey == enthrallID)
+ // owner.remove_status_effect(src)//At the moment, a user can enthrall themselves, toggle this back in if that should be removed.
+ redirect_component = WEAKREF(owner.AddComponent(/datum/component/redirect, list(COMSIG_LIVING_RESIST = CALLBACK(src, .proc/owner_resist)))) //Do resistance calc if resist is pressed#
+ RegisterSignal(owner, COMSIG_MOVABLE_HEAR, .proc/owner_hear)
+ mental_capacity = 500 - M.getOrganLoss(ORGAN_SLOT_BRAIN)//It's their brain!
+ var/mob/living/carbon/human/H = owner
+ if(H)//Prefs
+ if(!H.canbearoused)
+ H.client?.prefs.lewdchem = FALSE
+ var/message = "[(owner.client?.prefs.lewdchem?"I am a good pet for [enthrallGender].":"[master] is a really inspirational person!")]"
+ SEND_SIGNAL(M, COMSIG_ADD_MOOD_EVENT, "enthrall", /datum/mood_event/enthrall, message)
+ to_chat(owner, "You feel inexplicably drawn towards [master], their words having a demonstrable effect on you. It seems the closer you are to them, the stronger the effect is. However you aren't fully swayed yet and can resist their effects by repeatedly resisting as much as you can!")
+ log_game("FERMICHEM: MKULTRA: Status applied on [owner] ckey: [owner.key] with a master of [master] ckey: [enthrallID].")
+ SSblackbox.record_feedback("tally", "fermi_chem", 1, "Enthrall attempts")
+ return ..()
+
+/datum/status_effect/chem/enthrall/tick()
+ var/mob/living/carbon/M = owner
+
+ //chem calculations
+ if(!owner.reagents.has_reagent("enthrall") && !owner.reagents.has_reagent("enthrallTest"))
+ if (phase < 3 && phase != 0)
+ deltaResist += 3//If you've no chem, then you break out quickly
+ if(prob(5))
+ to_chat(owner, "Your mind starts to restore some of it's clarity as you feel the effects of the drug wain.")
+ if (mental_capacity <= 500 || phase == 4)
+ if (owner.reagents.has_reagent("mannitol"))
+ mental_capacity += 5
+ if (owner.reagents.has_reagent("neurine"))
+ mental_capacity += 10
+
+ //mindshield check
+ if(HAS_TRAIT(M, TRAIT_MINDSHIELD))//If you manage to enrapture a head, wow, GJ. (resisting gives a bigger bonus with a mindshield) From what I can tell, this isn't possible.
+ resistanceTally += 2
+ if(prob(10))
+ to_chat(owner, "You feel lucidity returning to your mind as the mindshield buzzes, attempting to return your brain to normal function.")
+ if(phase == 4)
+ mental_capacity += 5
+
+ //phase specific events
+ switch(phase)
+ if(-1)//fully removed
+ SEND_SIGNAL(M, COMSIG_CLEAR_MOOD_EVENT, "enthrall")
+ log_game("FERMICHEM: MKULTRA: Status REMOVED from [owner] ckey: [owner.key] with a master of [master] ckey: [enthrallID].")
+ owner.remove_status_effect(src)
+ return
+ if(0)// sleeper agent
+ if (cooldown > 0)
+ cooldown -= 1
+ return
+ if(1)//Initial enthrallment
+ if (enthrallTally > 125)
+ phase += 1
+ mental_capacity -= resistanceTally//leftover resistance per step is taken away from mental_capacity.
+ resistanceTally /= 2
+ enthrallTally = 0
+ SSblackbox.record_feedback("tally", "fermi_chem", 1, "Enthralled to state 2")
+ if(owner.client?.prefs.lewdchem)
+ to_chat(owner, "Your conciousness slips, as you sink deeper into trance and servitude.")
+ else
+ to_chat(owner, "Your conciousness slips, as you feel more drawn to following [master].")
+
+ else if (resistanceTally > 125)
+ phase = -1
+ to_chat(owner, "You break free of the influence in your mind, your thoughts suddenly turning lucid!")
+ if(DistApart < 10)
+ to_chat(master, "[(master.client?.prefs.lewdchem?"Your pet":"Your thrall")] seems to have broken free of your enthrallment!")
+ SSblackbox.record_feedback("tally", "fermi_chem", 1, "Thralls broken free")
+ owner.remove_status_effect(src) //If resisted in phase 1, effect is removed.
+ if(prob(10))
+ if(owner.client?.prefs.lewdchem)
+ to_chat(owner, "[pick("It feels so good to listen to [master].", "You can't keep your eyes off [master].", "[master]'s voice is making you feel so sleepy.", "You feel so comfortable with [master]", "[master] is so dominant, it feels right to obey them.")].")
+ if (2) //partially enthralled
+ if(enthrallTally > 200)
+ phase += 1
+ mental_capacity -= resistanceTally//leftover resistance per step is taken away from mental_capacity.
+ enthrallTally = 0
+ resistanceTally /= 2
+ if(owner.client?.prefs.lewdchem)
+ to_chat(owner, "Your mind gives, eagerly obeying and serving [master].")
+ to_chat(owner, "You are now fully enthralled to [master], and eager to follow their commands. However you find that in your intoxicated state you are unable to resort to violence. Equally you are unable to commit suicide, even if ordered to, as you cannot serve your [enthrallGender] in death. ")//If people start using this as an excuse to be violent I'll just make them all pacifists so it's not OP.
+ else
+ to_chat(owner, "You are unable to put up a resistance any longer, and now are under the influence of [master]. However you find that in your intoxicated state you are unable to resort to violence. Equally you are unable to commit suicide, even if ordered to, as you cannot follow [master] in death. ")
+ to_chat(master, "Your [(master.client?.prefs.lewdchem?"pet":"follower")] [owner] appears to have fully fallen under your sway.")
+ log_game("FERMICHEM: MKULTRA: Status on [owner] ckey: [owner.key] has been fully entrhalled (state 3) with a master of [master] ckey: [enthrallID].")
+ SSblackbox.record_feedback("tally", "fermi_chem", 1, "thralls fully enthralled.")
+ else if (resistanceTally > 200)
+ enthrallTally *= 0.5
+ phase -= 1
+ resistanceTally = 0
+ resistGrowth = 0
+ to_chat(owner, "You manage to shake some of the effects from your addled mind, however you can still feel yourself drawn towards [master].")
+ if(prob(10))
+ if(owner.client?.prefs.lewdchem)
+ to_chat(owner, "[pick("It feels so good to listen to [enthrallGender].", "You can't keep your eyes off [enthrallGender].", "[enthrallGender]'s voice is making you feel so sleepy.", "You feel so comfortable with [enthrallGender]", "[enthrallGender] is so dominant, it feels right to obey them.")].")
+ if (3)//fully entranced
+ if ((resistanceTally >= 200 && withdrawalTick >= 150) || (HAS_TRAIT(M, TRAIT_MINDSHIELD) && (resistanceTally >= 100)))
+ enthrallTally = 0
+ phase -= 1
+ resistanceTally = 0
+ resistGrowth = 0
+ to_chat(owner, "The separation from [(owner.client?.prefs.lewdchem?"your [enthrallGender]":"[master]")] sparks a small flame of resistance in yourself, as your mind slowly starts to return to normal.")
+ REMOVE_TRAIT(owner, TRAIT_PACIFISM, "MKUltra")
+ if(prob(1))
+ if(owner.client?.prefs.lewdchem && !customEcho)
+ to_chat(owner, "[pick("I belong to [enthrallGender].", "[enthrallGender] knows whats best for me.", "Obedence is pleasure.", "I exist to serve [enthrallGender].", "[enthrallGender] is so dominant, it feels right to obey them.")].")
+ if (4) //mindbroken
+ if (mental_capacity >= 499 && (owner.getOrganLoss(ORGAN_SLOT_BRAIN) <=0 || HAS_TRAIT(M, TRAIT_MINDSHIELD)) && !owner.reagents.has_reagent("MKUltra"))
+ phase = 2
+ mental_capacity = 500
+ customTriggers = list()
+ to_chat(owner, "Your mind starts to heal, fixing the damage caused by the massive amounts of chem injected into your system earlier, returning clarity to your mind. Though, you still feel drawn towards [master]'s words...'")
+ M.slurring = 0
+ M.confused = 0
+ resistGrowth = 0
+ else
+ if (cooldown > 0)
+ cooldown -= (0.8 + (mental_capacity/500))
+ cooldownMsg = FALSE
+ else if (cooldownMsg == FALSE)
+ if(DistApart < 10)
+ if(master.client?.prefs.lewdchem)
+ to_chat(master, "Your pet [owner] appears to have finished internalising your last command.")
+ cooldownMsg = TRUE
+ else
+ to_chat(master, "Your thrall [owner] appears to have finished internalising your last command.")
+ cooldownMsg = TRUE
+ if(get_dist(master, owner) > 10)
+ if(prob(10))
+ to_chat(owner, "You feel [(owner.client?.prefs.lewdchem?"a deep NEED to return to your [enthrallGender]":"like you have to return to [master]")].")
+ M.throw_at(get_step_towards(master,owner), 5, 1)
+ return//If you break the mind of someone, you can't use status effects on them.
+
+
+ //distance calculations
+ DistApart = get_dist(master, owner)
+ switch(DistApart)
+ if(0 to 8)//If the enchanter is within range, increase enthrallTally, remove withdrawal subproc and undo withdrawal effects.
+ if(phase <= 2)
+ enthrallTally += distancelist[get_dist(master, owner)+1]
+ if(withdrawalTick > 0)
+ withdrawalTick -= 1
+ //calming effects
+ M.hallucination = max(0, M.hallucination - 5)
+ M.stuttering = max(0, M.stuttering - 5)
+ M.jitteriness = max(0, M.jitteriness - 5)
+ if(owner.getOrganLoss(ORGAN_SLOT_BRAIN) >=20)
+ owner.adjustOrganLoss(ORGAN_SLOT_BRAIN, -0.2)
+ if(withdrawal == TRUE)
+ REMOVE_TRAIT(owner, TRAIT_PACIFISM, "MKUltra")
+ SEND_SIGNAL(M, COMSIG_CLEAR_MOOD_EVENT, "EnthMissing1")
+ SEND_SIGNAL(M, COMSIG_CLEAR_MOOD_EVENT, "EnthMissing2")
+ SEND_SIGNAL(M, COMSIG_CLEAR_MOOD_EVENT, "EnthMissing3")
+ SEND_SIGNAL(M, COMSIG_CLEAR_MOOD_EVENT, "EnthMissing4")
+ withdrawal = FALSE
+ if(9 to INFINITY)//If they're not nearby, enable withdrawl effects.
+ withdrawal = TRUE
+
+ //Withdrawal subproc:
+ if (withdrawal == TRUE)//Your minions are really REALLY needy.
+ switch(withdrawalTick)//denial
+ if(5)//To reduce spam
+ to_chat(owner, "You are unable to complete [(owner.client?.prefs.lewdchem?"your [enthrallGender]":"[master]")]'s orders without their presence, and any commands and objectives given to you prior are not in effect until you are back with them.")
+ ADD_TRAIT(owner, TRAIT_PACIFISM, "MKUltra") //IMPORTANT
+ if(10 to 35)//Gives wiggle room, so you're not SUPER needy
+ if(prob(5))
+ to_chat(owner, "You're starting to miss [(owner.client?.prefs.lewdchem?"your [enthrallGender]":"[master]")].")
+ if(prob(5))
+ owner.adjustOrganLoss(ORGAN_SLOT_BRAIN, 0.1)
+ to_chat(owner, "[(owner.client?.prefs.lewdchem?"[enthrallGender]":"[master]")] will surely be back soon") //denial
+ if(36)
+ var/message = "[(owner.client?.prefs.lewdchem?"I feel empty when [enthrallGender]'s not around..":"I miss [master]'s presence")]"
+ SEND_SIGNAL(M, COMSIG_ADD_MOOD_EVENT, "EnthMissing1", /datum/mood_event/enthrallmissing1, message)
+ if(37 to 65)//barganing
+ if(prob(10))
+ to_chat(owner, "They are coming back, right...?")
+ owner.adjustOrganLoss(ORGAN_SLOT_BRAIN, 0.5)
+ if(prob(10))
+ if(owner.client?.prefs.lewdchem)
+ to_chat(owner, "I just need to be a good pet for [enthrallGender], they'll surely return if I'm a good pet.")
+ owner.adjustOrganLoss(ORGAN_SLOT_BRAIN, -1.5)
+ if(66)
+ SEND_SIGNAL(M, COMSIG_CLEAR_MOOD_EVENT, "EnthMissing1")
+ var/message = "[(owner.client?.prefs.lewdchem?"I feel so lost in this complicated world without [enthrallGender]..":"I have to return to [master]!")]"
+ to_chat(owner, "You start to feel really angry about how you're not with [(owner.client?.prefs.lewdchem?"your [enthrallGender]":"[master]")]!")
+ SEND_SIGNAL(M, COMSIG_ADD_MOOD_EVENT, "EnthMissing2", /datum/mood_event/enthrallmissing2, message)
+ owner.stuttering += 50
+ owner.jitteriness += 250
+ if(67 to 89) //anger
+ if(prob(10))
+ addtimer(CALLBACK(M, /mob/verb/a_intent_change, INTENT_HARM), 2)
+ addtimer(CALLBACK(M, /mob/proc/click_random_mob), 2)
+ if(owner.client?.prefs.lewdchem)
+ to_chat(owner, "You are overwhelmed with anger at the lack of [enthrallGender]'s presence and suddenly lash out!")
+ else
+ to_chat(owner, "You are overwhelmed with anger and suddenly lash out!")
+ if(90)
+ SEND_SIGNAL(M, COMSIG_CLEAR_MOOD_EVENT, "EnthMissing2")
+ var/message = "[(owner.client?.prefs.lewdchem?"Where are you [enthrallGender]??!":"I need to find [master]!")]"
+ SEND_SIGNAL(M, COMSIG_ADD_MOOD_EVENT, "EnthMissing3", /datum/mood_event/enthrallmissing3, message)
+ if(owner.client?.prefs.lewdchem)
+ to_chat(owner, "You need to find your [enthrallGender] at all costs, you can't hold yourself back anymore!")
+ else
+ to_chat(owner, "You need to find [master] at all costs, you can't hold yourself back anymore!")
+ if(91 to 100)//depression
+ if(prob(10))
+ M.gain_trauma_type(BRAIN_TRAUMA_MILD)
+ owner.stuttering += 35
+ owner.jitteriness += 35
+ else if(prob(25))
+ M.hallucination += 10
+ if(101)
+ SEND_SIGNAL(M, COMSIG_CLEAR_MOOD_EVENT, "EnthMissing3")
+ var/message = "[(owner.client?.prefs.lewdchem?"I'm all alone, It's so hard to continute without [enthrallGender]...":"I really need to find [master]!!!")]"
+ SEND_SIGNAL(M, COMSIG_ADD_MOOD_EVENT, "EnthMissing4", /datum/mood_event/enthrallmissing4, message)
+ to_chat(owner, "You can hardly find the strength to continue without [(owner.client?.prefs.lewdchem?"your [enthrallGender]":"[master]")].")
+ M.gain_trauma_type(BRAIN_TRAUMA_SEVERE)
+ if(102 to 140) //depression 2, revengeance
+ if(prob(20))
+ owner.Stun(50)
+ owner.emote("cry")//does this exist?
+ if(owner.client?.prefs.lewdchem)
+ to_chat(owner, "You're unable to hold back your tears, suddenly sobbing as the desire to see your [enthrallGender] oncemore overwhelms you.")
+ else
+ to_chat(owner, "You are overwheled with withdrawl from [master].")
+ owner.adjustOrganLoss(ORGAN_SLOT_BRAIN, 1)
+ owner.stuttering += 35
+ owner.jitteriness += 35
+ if(prob(10))//2% chance
+ switch(rand(1,5))//Now let's see what hopefully-not-important part of the brain we cut off
+ if(1 to 3)
+ M.gain_trauma_type(BRAIN_TRAUMA_MILD)
+ if(4)
+ M.gain_trauma_type(BRAIN_TRAUMA_SEVERE)
+ if(5)//0.4% chance
+ M.gain_trauma_type(BRAIN_TRAUMA_SPECIAL)
+ if(prob(5))
+ deltaResist += 5
+ if(140 to INFINITY) //acceptance
+ if(prob(15))
+ deltaResist += 5
+ owner.adjustOrganLoss(ORGAN_SLOT_BRAIN, -1)
+ if(prob(20))
+ if(owner.client?.prefs.lewdchem)
+ to_chat(owner, "Maybe you'll be okay without your [enthrallGender].")
+ else
+ to_chat(owner, "You feel your mental functions slowly begin to return.")
+ if(prob(5))
+ owner.adjustOrganLoss(ORGAN_SLOT_BRAIN, 1)
+ M.hallucination += 30
+
+ withdrawalTick += 0.5//Enough to leave you with a major brain trauma, but not kill you.
+
+ //Status subproc - statuses given to you from your Master
+ //currently 3 statuses; antiresist -if you press resist, increases your enthrallment instead, HEAL - which slowly heals the pet, CHARGE - which breifly increases speed, PACIFY - makes pet a pacifist, ANTIRESIST - frustrates resist presses.
+ if (status)
+
+ if(status == "Antiresist")
+ if (statusStrength < 0)
+ status = null
+ to_chat(owner, "Your mind feels able to resist oncemore.")
+ else
+ statusStrength -= 1
+
+ else if(status == "heal")
+ if (statusStrength < 0)
+ status = null
+ to_chat(owner, "You finish licking your wounds.")
+ else
+ statusStrength -= 1
+ owner.heal_overall_damage(1, 1, 0, FALSE, FALSE)
+ cooldown += 1 //Cooldown doesn't process till status is done
+
+ else if(status == "charge")
+ ADD_TRAIT(owner, TRAIT_GOTTAGOFAST, "MKUltra")
+ status = "charged"
+ if(master.client?.prefs.lewdchem)
+ to_chat(owner, "Your [enthrallGender]'s order fills you with a burst of speed!")
+ else
+ to_chat(owner, "[master]'s command fills you with a burst of speed!")
+
+ else if (status == "charged")
+ if (statusStrength < 0)
+ status = null
+ REMOVE_TRAIT(owner, TRAIT_GOTTAGOFAST, "MKUltra")
+ owner.Knockdown(50)
+ to_chat(owner, "Your body gives out as the adrenaline in your system runs out.")
+ else
+ statusStrength -= 1
+ cooldown += 1 //Cooldown doesn't process till status is done
+
+ else if (status == "pacify")
+ ADD_TRAIT(owner, TRAIT_PACIFISM, "MKUltraStatus")
+ status = null
+
+ //Truth serum?
+ //adrenals?
+
+ //customEcho
+ if(customEcho && withdrawal == FALSE && owner.client?.prefs.lewdchem)
+ if(prob(2))
+ if(!customSpan) //just in case!
+ customSpan = "notice"
+ to_chat(owner, "[customEcho].")
+
+ //final tidying
+ resistanceTally += deltaResist
+ deltaResist = 0
+ if(cTriggered >= 0)
+ cTriggered -= 1
+ if (cooldown > 0)
+ cooldown -= (0.8 + (mental_capacity/500))
+ cooldownMsg = FALSE
+ else if (cooldownMsg == FALSE)
+ if(DistApart < 10)
+ if(master.client?.prefs.lewdchem)
+ to_chat(master, "Your pet [owner] appears to have finished internalising your last command.")
+ else
+ to_chat(master, "Your thrall [owner] appears to have finished internalising your last command.")
+ cooldownMsg = TRUE
+ cooldown = 0
+ if (tranceTime > 0 && tranceTime != 51) //custom trances only last 50 ticks.
+ tranceTime -= 1
+ else if (tranceTime == 0) //remove trance after.
+ M.cure_trauma_type(/datum/brain_trauma/hypnosis, TRAUMA_RESILIENCE_SURGERY)
+ M.remove_status_effect(/datum/status_effect/trance)
+ tranceTime = 51
+ //..()
+
+//Remove all stuff
+/datum/status_effect/chem/enthrall/on_remove()
+ var/mob/living/carbon/M = owner
+ M.mind.remove_antag_datum(/datum/antagonist/brainwashed)
+ SEND_SIGNAL(M, COMSIG_CLEAR_MOOD_EVENT, "enthrall")
+ SEND_SIGNAL(M, COMSIG_CLEAR_MOOD_EVENT, "enthrallpraise")
+ SEND_SIGNAL(M, COMSIG_CLEAR_MOOD_EVENT, "enthrallscold")
+ SEND_SIGNAL(M, COMSIG_CLEAR_MOOD_EVENT, "EnthMissing1")
+ SEND_SIGNAL(M, COMSIG_CLEAR_MOOD_EVENT, "EnthMissing2")
+ SEND_SIGNAL(M, COMSIG_CLEAR_MOOD_EVENT, "EnthMissing3")
+ SEND_SIGNAL(M, COMSIG_CLEAR_MOOD_EVENT, "EnthMissing4")
+ qdel(redirect_component.resolve())
+ redirect_component = null
+ UnregisterSignal(owner, COMSIG_MOVABLE_HEAR)
+ REMOVE_TRAIT(owner, TRAIT_PACIFISM, "MKUltra")
+ to_chat(owner, "You're now free of [master]'s influence, and fully independent!'")
+ UnregisterSignal(owner, COMSIG_GLOB_LIVING_SAY_SPECIAL)
+
+
+/datum/status_effect/chem/enthrall/proc/owner_hear(var/hearer, message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, message_mode)
+ if(owner.client?.prefs.lewdchem == FALSE)
+ return
+ if (cTriggered > 0)
+ return
+ var/mob/living/carbon/C = owner
+ raw_message = lowertext(raw_message)
+ for (var/trigger in customTriggers)
+ var/cached_trigger = lowertext(trigger)
+ if (findtext(raw_message, cached_trigger))//if trigger1 is the message
+ cTriggered = 5 //Stops triggerparties and as a result, stops servercrashes.
+ log_game("FERMICHEM: MKULTRA: [owner] ckey: [owner.key] has been triggered with [cached_trigger] from [speaker] saying: \"[message]\". (their master being [master] ckey: [enthrallID].)")
+
+ //Speak (Forces player to talk)
+ if (lowertext(customTriggers[trigger][1]) == "speak")//trigger2
+ var/saytext = "Your mouth moves on it's own before you can even catch it."
+ if(HAS_TRAIT(C, TRAIT_NYMPHO))
+ saytext += " You find yourself fully believing in the validity of what you just said and don't think to question it."
+ addtimer(CALLBACK(GLOBAL_PROC, .proc/to_chat, C, "[saytext]"), 5)
+ addtimer(CALLBACK(C, /atom/movable/proc/say, "[customTriggers[trigger][2]]"), 5)
+ //(C.say(customTriggers[trigger][2]))//trigger3
+ log_game("FERMICHEM: MKULTRA: [owner] ckey: [owner.key] has been forced to say: \"[customTriggers[trigger][2]]\" from previous trigger.")
+
+
+ //Echo (repeats message!) allows customisation, but won't display var calls! Defaults to hypnophrase.
+ else if (lowertext(customTriggers[trigger][1]) == "echo")//trigger2
+ addtimer(CALLBACK(GLOBAL_PROC, .proc/to_chat, C, "[customTriggers[trigger][2]]"), 5)
+ //(to_chat(owner, "[customTriggers[trigger][2]]"))//trigger3
+
+ //Shocking truth!
+ else if (lowertext(customTriggers[trigger]) == "shock")
+ if (C.canbearoused && C.client?.prefs.lewdchem)
+ C.adjustArousalLoss(5)
+ C.jitteriness += 100
+ C.stuttering += 25
+ C.Knockdown(60)
+ C.Stun(60)
+ to_chat(owner, "Your muscles seize up, then start spasming wildy!")
+
+ //wah intensifies wah-rks
+ else if (lowertext(customTriggers[trigger]) == "cum")//aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ if (HAS_TRAIT(C, TRAIT_NYMPHO) && C.client?.prefs.lewdchem)
+ if (C.getArousalLoss() > 80)
+ C.mob_climax(forced_climax=TRUE)
+ C.SetStun(10)//We got your stun effects in somewhere, Kev.
+ else
+ C.adjustArousalLoss(10)
+ to_chat(C, "You feel a surge of arousal!")
+ else
+ C.throw_at(get_step_towards(speaker,C), 3, 1) //cut this if it's too hard to get working
+
+ //kneel (knockdown)
+ else if (lowertext(customTriggers[trigger]) == "kneel")//as close to kneeling as you can get, I suppose.
+ to_chat(owner, "You drop to the ground unsurreptitiously.")
+ C.lay_down()
+
+ //strip (some) clothes
+ else if (lowertext(customTriggers[trigger]) == "strip")//This wasn't meant to just be a lewd thing oops.
+ var/mob/living/carbon/human/o = owner
+ var/items = o.get_contents()
+ for(var/obj/item/W in items)
+ if(W == o.w_uniform || W == o.wear_suit)
+ o.dropItemToGround(W, TRUE)
+ to_chat(owner,"You feel compelled to strip your clothes.")
+
+ //trance
+ else if (lowertext(customTriggers[trigger]) == "trance")//Maaaybe too strong. Weakened it, only lasts 50 ticks.
+ var/mob/living/carbon/human/o = owner
+ o.apply_status_effect(/datum/status_effect/trance, 200, TRUE)
+ tranceTime = 50
+ log_game("FERMICHEM: MKULTRA: [owner] ckey: [owner.key] has been tranced from previous trigger.")
+
+ return
+
+/datum/status_effect/chem/enthrall/proc/owner_resist()
+ var/mob/living/carbon/M = owner
+ to_chat(owner, "You attempt to fight against [master]'s influence!")
+
+ //Able to resist checks
+ if (status == "Sleeper" || phase == 0)
+ return
+ else if (phase == 4)
+ if(owner.client?.prefs.lewdchem)
+ to_chat(owner, "Your mind is too far gone to even entertain the thought of resisting. Unless you can fix the brain damage, you won't be able to break free of your [enthrallGender]'s control.")
+ else
+ to_chat(owner, "Your brain is too overwhelmed with from the high volume of chemicals in your system, rendering you unable to resist, unless you can fix the brain damage.")
+ return
+ else if (phase == 3 && withdrawal == FALSE)
+ if(owner.client?.prefs.lewdchem)
+ to_chat(owner, "The presence of your [enthrallGender] fully captures the horizon of your mind, removing any thoughts of resistance. If you get split up from them, then you might be able to entertain the idea of resisting.")
+ else
+ to_chat(owner, "You are unable to resist [master] in your current state. If you get split up from them, then you might be able to resist.")
+ return
+ else if (status == "Antiresist")//If ordered to not resist; resisting while ordered to not makes it last longer, and increases the rate in which you are enthralled.
+ if (statusStrength > 0)
+ if(owner.client?.prefs.lewdchem)
+ to_chat(owner, "The order from your [enthrallGender] to give in is conflicting with your attempt to resist, drawing you deeper into trance! You'll have to wait a bit before attemping again, lest your attempts become frustrated again.")
+ else
+ to_chat(owner, "The order from your [master] to give in is conflicting with your attempt to resist. You'll have to wait a bit before attemping again, lest your attempts become frustrated again.")
+ statusStrength += 1
+ enthrallTally += 1
+ return
+ else
+ status = null
+
+ //base resistance
+ if (deltaResist != 0)//So you can't spam it, you get one deltaResistance per tick.
+ deltaResist += 0.1 //Though I commend your spamming efforts.
+ return
+ else
+ deltaResist = 1.8 + resistGrowth
+ resistGrowth += 0.05
+
+ //distance modifer
+ switch(DistApart)
+ if(0)
+ deltaResist *= 0.8
+ if(1 to 8)//If they're far away, increase resistance.
+ deltaResist *= (1+(DistApart/10))
+ if(9 to INFINITY)//If
+ deltaResist *= 2
+
+
+ if(prob(5))
+ M.emote("me",1,"squints, shaking their head for a moment.")//shows that you're trying to resist sometimes
+ deltaResist *= 1.5
+ //nymphomania
+ if (M.canbearoused && HAS_TRAIT(M, TRAIT_NYMPHO))//I'm okay with this being removed.
+ deltaResist*= 0.5-(((2/200)*M.arousalloss)/1)//more aroused you are, the weaker resistance you can give, the less you are, the more you gain. (+/- 0.5)
+
+ //chemical resistance, brain and annaphros are the key to undoing, but the subject has to to be willing to resist.
+ if (owner.reagents.has_reagent("mannitol"))
+ deltaResist *= 1.25
+ if (owner.reagents.has_reagent("neurine"))
+ deltaResist *= 1.5
+ if (!HAS_TRAIT(owner, TRAIT_CROCRIN_IMMUNE) && M.canbearoused)
+ if (owner.reagents.has_reagent("anaphro"))
+ deltaResist *= 1.5
+ if (owner.reagents.has_reagent("anaphro+"))
+ deltaResist *= 2
+ if (owner.reagents.has_reagent("aphro"))
+ deltaResist *= 0.75
+ if (owner.reagents.has_reagent("aphro+"))
+ deltaResist *= 0.5
+
+ //Antag resistance
+ //cultists are already brainwashed by their god
+ if(iscultist(owner))
+ deltaResist *= 1.3
+ else if (is_servant_of_ratvar(owner))
+ deltaResist *= 1.3
+ //antags should be able to resist, so they can do their other objectives. This chem does frustrate them, but they've all the tools to break free when an oportunity presents itself.
+ else if (owner.mind.assigned_role in GLOB.antagonists)
+ deltaResist *= 1.2
+
+ //role resistance
+ //Chaplains are already brainwashed by their god
+ if(owner.mind.assigned_role == "Chaplain")
+ deltaResist *= 1.2
+ //Command staff has authority,
+ if(owner.mind.assigned_role in GLOB.command_positions)
+ deltaResist *= 1.1
+ //Chemists should be familiar with drug effects
+ if(owner.mind.assigned_role == "Chemist")
+ deltaResist *= 1.2
+
+ //Happiness resistance
+ //Your Thralls are like pets, you need to keep them happy.
+ if(owner.nutrition < 300)
+ deltaResist += (300-owner.nutrition)/6
+ if(owner.health < 100)//Harming your thrall will make them rebel harder.
+ deltaResist *= ((120-owner.health)/100)+1
+ //if(owner.mood.mood) //datum/component/mood TO ADD in FERMICHEM 2
+ //Add cold/hot, oxygen, sanity, happiness? (happiness might be moot, since the mood effects are so strong)
+ //Mental health could play a role too in the other direction
+
+ //If you've a collar, you get a sense of pride
+ if(istype(M.wear_neck, /obj/item/clothing/neck/petcollar))
+ deltaResist *= 0.5
+ if(HAS_TRAIT(M, TRAIT_MINDSHIELD))
+ deltaResist += 5//even faster!
+
+ return
diff --git a/modular_citadel/code/datums/traits/negative.dm b/modular_citadel/code/datums/traits/negative.dm
new file mode 100644
index 0000000000..c0cbe57b5a
--- /dev/null
+++ b/modular_citadel/code/datums/traits/negative.dm
@@ -0,0 +1 @@
+// Citadel-specific Negative Traits
diff --git a/modular_citadel/code/datums/traits/neutral.dm b/modular_citadel/code/datums/traits/neutral.dm
index 05aeb27361..197c9b94e1 100644
--- a/modular_citadel/code/datums/traits/neutral.dm
+++ b/modular_citadel/code/datums/traits/neutral.dm
@@ -31,3 +31,13 @@
mob_trait = TRAIT_MASO
gain_text = "You desire to be hurt."
lose_text = "Pain has become less exciting for you."
+
+/datum/quirk/pharmacokinesis //Prevents unwanted organ additions.
+ name = "Acute hepatic pharmacokinesis"
+ desc = "You've a rare genetic disorder that causes Incubus draft and Sucubus milk to be absorbed by your liver instead."
+ value = 0
+ mob_trait = TRAIT_PHARMA
+ lose_text = "Your liver feels different."
+ var/active = FALSE
+ var/power = 0
+ var/cachedmoveCalc = 1
diff --git a/modular_citadel/code/game/gamemodes/gangs/dominator.dm b/modular_citadel/code/game/gamemodes/gangs/dominator.dm
index 0d89c78010..4946cad510 100644
--- a/modular_citadel/code/game/gamemodes/gangs/dominator.dm
+++ b/modular_citadel/code/game/gamemodes/gangs/dominator.dm
@@ -1,6 +1,7 @@
#define DOM_BLOCKED_SPAM_CAP 6
//32 instead of 40 for safety reasons. How many turfs aren't walls around dominator for it to work
-#define DOM_REQUIRED_TURFS 32
+//Update ppl somehow fuckup at 32, now we are down to 25. I hope to god they don't try harder to wall it.
+#define DOM_REQUIRED_TURFS 25
#define DOM_HULK_HITS_REQUIRED 10
/obj/machinery/dominator
@@ -85,6 +86,9 @@
if(gang && gang.domination_time != NOT_DOMINATING)
var/time_remaining = gang.domination_time_remaining()
if(time_remaining > 0)
+ if(!is_station_level(z))
+ explosion(src, 5, 10, 20, 30) //you now get a nice explosion if this moves off station.
+ qdel(src) //to make sure it doesn't continue to exist.
if(excessive_walls_check())
gang.domination_time += 20
if(spam_prevention < DOM_BLOCKED_SPAM_CAP)
@@ -108,6 +112,7 @@
else
Cinematic(CINEMATIC_MALF,world) //Here is the gang victory trigger on the dominator ending.
gang.winner = TRUE
+ SSticker.news_report = GANG_VICTORY
SSticker.force_ending = TRUE
if(!.)
@@ -163,7 +168,7 @@
examine(user)
return
- if(tempgang.domination_time != NOT_DOMINATING)
+ if(tempgang.domination_time != NOT_DOMINATING)
to_chat(user, "Error: Hostile Takeover is already in progress.")
return
diff --git a/modular_citadel/code/game/gamemodes/gangs/gang.dm b/modular_citadel/code/game/gamemodes/gangs/gang.dm
index 00ce3f81a9..fa25701ac4 100644
--- a/modular_citadel/code/game/gamemodes/gangs/gang.dm
+++ b/modular_citadel/code/game/gamemodes/gangs/gang.dm
@@ -343,11 +343,12 @@
return "