A biological research lab within the HD-10180 system has suffered from a complete containment failure. The SYN-C Brutus is to deliver a nuclear payload via strike team. Everything inside and outside the facility is to be killed on sight, including any research staff. Nuclear authentication codes have been sent via red phone, as have other detailed orders.
"
else
- bans = json_decode(response["body"])
+ bans = json_decode(response.body)
//Ignore bans from non-whitelisted sources, if a whitelist exists
var/list/valid_sources
diff --git a/code/modules/admin/verbs/adminevents.dm b/code/modules/admin/verbs/adminevents.dm
index 858e984a21263..fbd9f5c75d5df 100644
--- a/code/modules/admin/verbs/adminevents.dm
+++ b/code/modules/admin/verbs/adminevents.dm
@@ -8,6 +8,8 @@ ADMIN_VERB_AND_CONTEXT_MENU(cmd_admin_subtle_message, R_ADMIN, "Subtle Message",
message_admins("[key_name_admin(user)] decided not to answer [ADMIN_LOOKUPFLW(target)]'s prayer")
return
+ msg = user.reformat_narration(msg)
+
target.balloon_alert(target, "you hear a voice")
to_chat(target, "You hear a voice in your head... [msg]", confidential = TRUE)
@@ -53,6 +55,8 @@ ADMIN_VERB_AND_CONTEXT_MENU(cmd_admin_headset_message, R_ADMIN, "Headset Message
message_admins("[key_name_admin(src)] decided not to answer [key_name_admin(target)]'s [sender] request.")
return
+ input = reformat_narration(input)
+
log_directed_talk(mob, target, input, LOG_ADMIN, "reply")
message_admins("[key_name_admin(src)] replied to [key_name_admin(target)]'s [sender] message with: \"[input]\"")
target.balloon_alert(target, "you hear a voice")
@@ -64,6 +68,7 @@ ADMIN_VERB(cmd_admin_world_narrate, R_ADMIN, "Global Narrate", "Send a direct na
var/msg = input(user, "Message:", "Enter the text you wish to appear to everyone:") as text|null
if (!msg)
return
+ msg = user.reformat_narration(msg)
to_chat(world, "[msg]", confidential = TRUE)
log_admin("GlobalNarrate: [key_name(user)] : [msg]")
message_admins(span_adminnotice("[key_name_admin(user)] Sent a global narrate"))
@@ -76,6 +81,7 @@ ADMIN_VERB_AND_CONTEXT_MENU(cmd_admin_local_narrate, R_ADMIN, "Local Narrate", A
var/msg = input(user, "Message:", "Enter the text you wish to appear to everyone within view:") as text|null
if (!msg)
return
+ msg = user.reformat_narration(msg)
for(var/mob/M in view(range, locale))
to_chat(M, msg, confidential = TRUE)
@@ -89,6 +95,8 @@ ADMIN_VERB_AND_CONTEXT_MENU(cmd_admin_direct_narrate, R_ADMIN, "Direct Narrate",
if( !msg )
return
+ msg = user.reformat_narration(msg)
+
to_chat(target, msg, confidential = TRUE)
log_admin("DirectNarrate: [key_name(user)] to ([key_name(target)]): [msg]")
msg = span_adminnotice(" DirectNarrate: [key_name_admin(user)] to ([key_name_admin(target)]): [msg] ")
@@ -278,3 +286,14 @@ ADMIN_VERB(command_report_footnote, R_ADMIN, "Command Report Footnote", "Adds a
ADMIN_VERB(delay_command_report, R_FUN, "Delay Command Report", "Prevents the roundstart command report from being sent; or forces it to send it delayed.", ADMIN_CATEGORY_EVENTS)
GLOB.communications_controller.block_command_report = !GLOB.communications_controller.block_command_report
message_admins("[key_name_admin(user)] has [(GLOB.communications_controller.block_command_report ? "delayed" : "sent")] the roundstart command report.")
+
+///Reformats a narration message. First provides a prompt asking if the user wants to reformat their message, then allows them to pick from a list of spans to use.
+/client/proc/reformat_narration(input)
+ if(tgui_alert(mob, "Set a custom text format?", "Make it snazzy!", list("Yes", "No")) == "Yes")
+ var/text_span = tgui_input_list(mob, "Select a span!", "Immersion! Yeah!", GLOB.spanname_to_formatting)
+ if(isnull(text_span)) //In case the user just quit the prompt.
+ return text_span
+ text_span = GLOB.spanname_to_formatting[text_span]
+ input = "" + input + ""
+
+ return input
diff --git a/code/modules/admin/verbs/anonymousnames.dm b/code/modules/admin/verbs/anonymousnames.dm
index 86ccf7008ce91..ad38615d8bed6 100644
--- a/code/modules/admin/verbs/anonymousnames.dm
+++ b/code/modules/admin/verbs/anonymousnames.dm
@@ -94,7 +94,7 @@ GLOBAL_DATUM(current_anonymous_theme, /datum/anonymous_theme)
return
var/mob/living/carbon/human/human_mob = player
var/original_name = player.real_name //id will not be changed if you do not do this
- randomize_human(player) //do this first so the special name can be given
+ randomize_human_normie(player) //do this first so the special name can be given
player.fully_replace_character_name(original_name, anonymous_name(player))
if(extras_enabled)
player_extras(player)
diff --git a/code/modules/admin/verbs/mapping.dm b/code/modules/admin/verbs/mapping.dm
index 436e484cb430e..3717affc51c34 100644
--- a/code/modules/admin/verbs/mapping.dm
+++ b/code/modules/admin/verbs/mapping.dm
@@ -208,7 +208,7 @@ ADMIN_VERB(create_mapping_job_icons, R_DEBUG, "Generate job landmarks icons", "G
else
for(var/obj/item/I in D)
qdel(I)
- randomize_human(D)
+ randomize_human_normie(D)
D.dress_up_as_job(
equipping = JB,
visual_only = TRUE,
diff --git a/code/modules/admin/view_variables/debug_variables.dm b/code/modules/admin/view_variables/debug_variables.dm
index a4035acd01421..d9a1b90b0af29 100644
--- a/code/modules/admin/view_variables/debug_variables.dm
+++ b/code/modules/admin/view_variables/debug_variables.dm
@@ -3,11 +3,12 @@
/proc/debug_variable(name, value, level, datum/owner, sanitize = TRUE, display_flags = NONE) //if D is a list, name will be index, and value will be assoc value.
if(owner)
if(islist(owner))
+ var/list/list_owner = owner
var/index = name
if (value)
- name = owner[name] //name is really the index until this line
+ name = list_owner[name] //name is really the index until this line
else
- value = owner[name]
+ value = list_owner[name]
. = "
([VV_HREF_TARGET_1V(owner, VV_HK_BASIC_EDIT, "E", name)]) ([VV_HREF_TARGET_1V(owner, VV_HK_BASIC_CHANGE, "C", name)]) ([VV_HREF_TARGET_1V(owner, VV_HK_BASIC_MASSEDIT, "M", name)]) "
diff --git a/code/modules/antagonists/_common/antag_datum.dm b/code/modules/antagonists/_common/antag_datum.dm
index d1a2a52fa1b73..3a2ed4281ca77 100644
--- a/code/modules/antagonists/_common/antag_datum.dm
+++ b/code/modules/antagonists/_common/antag_datum.dm
@@ -525,6 +525,7 @@ GLOBAL_LIST_EMPTY(antagonists)
"antag_team_hud_[REF(src)]",
hud_image_on(target),
antag_to_check || type,
+ get_team() && WEAKREF(get_team()),
))
// Add HUDs that they couldn't see before
diff --git a/code/modules/antagonists/_common/antag_hud.dm b/code/modules/antagonists/_common/antag_hud.dm
index 228bfc354dff0..863d52ef5ffe4 100644
--- a/code/modules/antagonists/_common/antag_hud.dm
+++ b/code/modules/antagonists/_common/antag_hud.dm
@@ -4,9 +4,12 @@ GLOBAL_LIST_EMPTY_TYPED(has_antagonist_huds, /datum/atom_hud/alternate_appearanc
/// An alternate appearance that will only show if you have the antag datum
/datum/atom_hud/alternate_appearance/basic/has_antagonist
var/antag_datum_type
+ /// Optionally, a weakref to antag team
+ var/datum/weakref/team_ref
-/datum/atom_hud/alternate_appearance/basic/has_antagonist/New(key, image/I, antag_datum_type)
+/datum/atom_hud/alternate_appearance/basic/has_antagonist/New(key, image/I, antag_datum_type, datum/weakref/team)
src.antag_datum_type = antag_datum_type
+ team_ref = team
GLOB.has_antagonist_huds += src
return ..(key, I, NONE)
@@ -15,6 +18,9 @@ GLOBAL_LIST_EMPTY_TYPED(has_antagonist_huds, /datum/atom_hud/alternate_appearanc
return ..()
/datum/atom_hud/alternate_appearance/basic/has_antagonist/mobShouldSee(mob/M)
+ var/datum/team/antag_team = team_ref?.resolve()
+ if(!isnull(antag_team))
+ return !!(M.mind in antag_team.members)
return !!M.mind?.has_antag_datum(antag_datum_type)
/// An alternate appearance that will show all the antagonists this mob has
diff --git a/code/modules/antagonists/abductor/abductee/abductee.dm b/code/modules/antagonists/abductor/abductee/abductee.dm
index f1e657a558ea1..fa529a6504415 100644
--- a/code/modules/antagonists/abductor/abductee/abductee.dm
+++ b/code/modules/antagonists/abductor/abductee/abductee.dm
@@ -6,6 +6,7 @@
*/
/datum/antagonist/abductee
name = "\improper Abductee"
+ stinger_sound = 'sound/ambience/antag/abductee.ogg'
roundend_category = "abductees"
antagpanel_category = ANTAG_GROUP_ABDUCTORS
antag_hud_name = "abductee"
@@ -18,6 +19,7 @@
to_chat(owner, span_warning("Your mind snaps!"))
to_chat(owner, "[span_warning("You can't remember how you got here...")]")
owner.announce_objectives()
+ play_stinger()
/datum/antagonist/abductee/proc/give_objective()
var/objtype = (prob(75) ? /datum/objective/abductee/random : pick(subtypesof(/datum/objective/abductee/) - /datum/objective/abductee/random))
diff --git a/code/modules/antagonists/brainwashing/brainwashing.dm b/code/modules/antagonists/brainwashing/brainwashing.dm
index 524bfb04362db..57707688f4daf 100644
--- a/code/modules/antagonists/brainwashing/brainwashing.dm
+++ b/code/modules/antagonists/brainwashing/brainwashing.dm
@@ -30,6 +30,7 @@
/datum/antagonist/brainwashed
name = "\improper Brainwashed Victim"
job_rank = ROLE_BRAINWASHED
+ stinger_sound = 'sound/ambience/antag/brainwashed.ogg'
roundend_category = "brainwashed victims"
show_in_antagpanel = TRUE
antag_hud_name = "brainwashed"
diff --git a/code/modules/antagonists/brother/brother.dm b/code/modules/antagonists/brother/brother.dm
index b62b18a02aedf..13707df2c052f 100644
--- a/code/modules/antagonists/brother/brother.dm
+++ b/code/modules/antagonists/brother/brother.dm
@@ -75,9 +75,14 @@
flashed.balloon_alert(source, "unconscious!")
return
+#ifdef TESTING
+ if (isnull(flashed.mind))
+ flashed.mind_initialize()
+#else
if (isnull(flashed.mind) || !GET_CLIENT(flashed))
flashed.balloon_alert(source, "[flashed.p_their()] mind is vacant!")
return
+#endif
for(var/datum/objective/brother_objective as anything in source.mind.get_all_objectives())
// If the objective has a target, are we flashing them?
@@ -183,6 +188,11 @@
message_admins("[key_name_admin(admin)] made [key_name_admin(new_owner)] into a blood brother.")
log_admin("[key_name(admin)] made [key_name(new_owner)] into a blood brother.")
+/datum/antagonist/brother/apply_innate_effects(mob/living/mob_override)
+ . = ..()
+ var/mob/living/the_mob = owner.current || mob_override
+ add_team_hud(the_mob)
+
/datum/antagonist/brother/ui_static_data(mob/user)
var/list/data = list()
data["antag_name"] = name
@@ -219,8 +229,13 @@
/// Adds a new brother to the team
/datum/team/brother_team/proc/add_brother(mob/living/new_brother, source)
+#ifndef TESTING
if (isnull(new_brother) || isnull(new_brother.mind) || !GET_CLIENT(new_brother) || new_brother.mind.has_antag_datum(/datum/antagonist/brother))
return FALSE
+#else
+ if (isnull(new_brother) || new_brother.mind.has_antag_datum(/datum/antagonist/brother))
+ return FALSE
+#endif
set_brothers_left(brothers_left - 1)
for (var/datum/mind/brother_mind as anything in members)
diff --git a/code/modules/antagonists/changeling/powers/mutations.dm b/code/modules/antagonists/changeling/powers/mutations.dm
index de010b07cba33..9929266f4dbf4 100644
--- a/code/modules/antagonists/changeling/powers/mutations.dm
+++ b/code/modules/antagonists/changeling/powers/mutations.dm
@@ -341,7 +341,7 @@
damage = 0
damage_type = BRUTE
range = 8
- hitsound = 'sound/weapons/thudswoosh.ogg'
+ hitsound = 'sound/weapons/shove.ogg'
var/chain
var/obj/item/ammo_casing/magic/tentacle/source //the item that shot it
///Click params that were used to fire the tentacle shot
diff --git a/code/modules/antagonists/clown_ops/clown_weapons.dm b/code/modules/antagonists/clown_ops/clown_weapons.dm
index 1c55e5416e247..130b6c9af5f1b 100644
--- a/code/modules/antagonists/clown_ops/clown_weapons.dm
+++ b/code/modules/antagonists/clown_ops/clown_weapons.dm
@@ -89,7 +89,7 @@
force = 0
throwforce = 0
hitsound = null
- embedding = null
+ embed_type = null
light_color = COLOR_YELLOW
sword_color_icon = "bananium"
active_heat = 0
diff --git a/code/modules/antagonists/cult/cult_items.dm b/code/modules/antagonists/cult/cult_items.dm
index 19d30ba09f419..6c4f1b2a8b527 100644
--- a/code/modules/antagonists/cult/cult_items.dm
+++ b/code/modules/antagonists/cult/cult_items.dm
@@ -332,6 +332,7 @@ Striking a noncultist, however, will tear their flesh."}
else
to_chat(user, span_warning("The bola seems to take on a life of its own!"))
ensnare(user)
+ user.update_held_items()
#undef CULT_BOLA_PICKUP_STUN
@@ -633,6 +634,10 @@ Striking a noncultist, however, will tear their flesh."}
icon_state = "blindfold"
inhand_icon_state = "blindfold"
flash_protect = FLASH_PROTECTION_WELDER
+ actions_types = null
+ color_cutoffs = list(40, 0, 0) //red
+ glass_colour_type = null
+ forced_glass_color = FALSE
/obj/item/clothing/glasses/hud/health/night/cultblind/equipped(mob/living/user, slot)
..()
diff --git a/code/modules/antagonists/fugitive/hunters/hunter.dm b/code/modules/antagonists/fugitive/hunters/hunter.dm
index b75cba69e528a..ba26645364712 100644
--- a/code/modules/antagonists/fugitive/hunters/hunter.dm
+++ b/code/modules/antagonists/fugitive/hunters/hunter.dm
@@ -42,6 +42,10 @@
to_chat(owner, span_danger("GOOD EVENING, WE ARE PSYKER HUNTE- NO, PSYKER SHIKARIS!"))
to_chat(owner, span_danger("A brainling hit us up on the holopad with an offer we could NOT pass up. We kidnap some fools for them, and in exchange we get a LIFETIME SUPPLY OF GORE."))
to_chat(owner, span_danger("Our gore supply has been running thin as of late -- How could we say no? The binge MUST go on!"))
+ if(HUNTER_PACK_MI13)
+ to_chat(owner, span_danger("Agents, we have detected a wanted fugitive in Nanotrasen controlled space."))
+ to_chat(owner, span_danger("Your mission is simple. Infiltrate the facility and extract the target, dead or alive."))
+ to_chat(owner, span_danger("This is a stealth infiltration mission in hostile enemy territory. Be wary, and avoid being caught if possible."))
to_chat(owner, span_boldannounce("You are not an antagonist in that you may kill whomever you please, but you can do anything to ensure the capture of the fugitives, even if that means going through the station."))
owner.announce_objectives()
diff --git a/code/modules/antagonists/fugitive/hunters/hunter_outfits.dm b/code/modules/antagonists/fugitive/hunters/hunter_outfits.dm
index 7df6818cdc44a..5491251d1aa53 100644
--- a/code/modules/antagonists/fugitive/hunters/hunter_outfits.dm
+++ b/code/modules/antagonists/fugitive/hunters/hunter_outfits.dm
@@ -216,6 +216,56 @@
id_trim = /datum/id_trim/bounty_hunter/psykers/seer
+/datum/outfit/mi13_hunter
+ name = "\improper MI13 Fugitive Retrieval Agent"
+ uniform = /obj/item/clothing/under/syndicate/sniper
+ back = /obj/item/storage/backpack/satchel/leather
+ ears = /obj/item/radio/headset/syndicate
+ glasses = /obj/item/clothing/glasses/sunglasses
+ gloves = /obj/item/clothing/gloves/combat
+ shoes = /obj/item/clothing/shoes/laceup
+ belt = /obj/item/restraints/handcuffs/cable/zipties
+ l_pocket = /obj/item/gun/ballistic/automatic/pistol
+ r_pocket = /obj/item/suppressor
+ id = /obj/item/card/id/advanced/chameleon/black
+ box = /obj/item/storage/box/survival/syndie
+ implants = list(/obj/item/implant/explosive)
+
+/datum/outfit/mi13_hunter/pre_equip(mob/living/carbon/human/agent, visualsOnly = FALSE)
+ backpack_contents = list()
+ backpack_contents += pick_weight(list(/obj/item/ammo_box/magazine/m9mm = 80,
+ /obj/item/ammo_box/magazine/m9mm/hp = 10,
+ /obj/item/ammo_box/magazine/m9mm/ap = 5,
+ /obj/item/ammo_box/magazine/m9mm/fire = 5,
+ ))
+ backpack_contents += pick_weight(list(
+ /obj/item/pen/edagger = 40,
+ /obj/item/knife/combat = 30,
+ /obj/item/assembly/flash = 30,
+ ))
+ backpack_contents += pick_weight(list(
+ /obj/item/grenade/c4 = 20,
+ /obj/item/implanter/freedom = 20,
+ /obj/item/clothing/mask/chameleon = 20,
+ /obj/item/language_manual/codespeak_manual/unlimited = 10,
+ /obj/item/storage/mail_counterfeit_device = 10,
+ /obj/item/traitor_machine_trapper = 10,
+ /obj/item/gun/ballistic/automatic/pistol/clandestine/fisher = 10,
+ ))
+
+/datum/outfit/mi13_hunter/post_equip(mob/living/carbon/human/agent, visualsOnly = FALSE)
+ if(visualsOnly)
+ return
+ var/obj/item/card/id/wearing = agent.wear_id
+ wearing.registered_name = agent.real_name
+ wearing.update_label()
+
+/datum/outfit/mi13_hunter/chef
+ name = "\improper MI13 Fugitive Retrieval Agent - Chef Disguise"
+ head = /obj/item/clothing/head/utility/chefhat
+ suit = /obj/item/clothing/suit/apron/chef
+ mask = /obj/item/clothing/mask/fakemoustache
+
//ids and ert code
/obj/item/card/id/advanced/bountyhunter
diff --git a/code/modules/antagonists/heretic/heretic_antag.dm b/code/modules/antagonists/heretic/heretic_antag.dm
index 488af0eef28dc..d2db4cb65d596 100644
--- a/code/modules/antagonists/heretic/heretic_antag.dm
+++ b/code/modules/antagonists/heretic/heretic_antag.dm
@@ -60,17 +60,17 @@
/// Wether we are allowed to ascend
var/feast_of_owls = FALSE
/// Static list of what each path converts to in the UI (colors are TGUI colors)
- var/static/list/path_to_ui_color = list(
- PATH_START = "grey",
- PATH_SIDE = "green",
- PATH_RUST = "brown",
- PATH_FLESH = "red",
- PATH_ASH = "white",
- PATH_VOID = "blue",
- PATH_BLADE = "label", // my favorite color is label
- PATH_COSMIC = "purple",
- PATH_LOCK = "yellow",
- PATH_MOON = "blue",
+ var/static/list/path_to_ui_bgr = list(
+ PATH_START = "node_side",
+ PATH_SIDE = "node_side",
+ PATH_RUST = "node_rust",
+ PATH_FLESH = "node_flesh",
+ PATH_ASH = "node_ash",
+ PATH_VOID = "node_void",
+ PATH_BLADE = "node_blade",
+ PATH_COSMIC = "node_cosmos",
+ PATH_LOCK = "node_lock",
+ PATH_MOON = "node_moon",
)
var/static/list/path_to_rune_color = list(
@@ -98,6 +98,82 @@
LAZYNULL(sac_targets)
return ..()
+/datum/antagonist/heretic/proc/get_icon_of_knowledge(datum/heretic_knowledge/knowledge)
+ //basic icon parameters
+ var/icon_path = 'icons/mob/actions/actions_ecult.dmi'
+ var/icon_state = "eye"
+ var/icon_frame = knowledge.research_tree_icon_frame
+ var/icon_dir = knowledge.research_tree_icon_dir
+ //can't imagine why you would want this one, so it can't be overridden by the knowledge
+ var/icon_moving = 0
+
+ //item transmutation knowledge does not generate its own icon due to implementation difficulties, the icons have to be specified in the override vars
+
+ //if the knowledge has a special icon, use that
+ if(!isnull(knowledge.research_tree_icon_path))
+ icon_path = knowledge.research_tree_icon_path
+ icon_state = knowledge.research_tree_icon_state
+
+ //if the knowledge is a spell, use the spell's button
+ else if(ispath(knowledge,/datum/heretic_knowledge/spell))
+ var/datum/heretic_knowledge/spell/spell_knowledge = knowledge
+ var/datum/action/cooldown/spell/result_spell = spell_knowledge.spell_to_add
+ icon_path = result_spell.button_icon
+ icon_state = result_spell.button_icon_state
+
+ //if the knowledge is a summon, use the mob sprite
+ else if(ispath(knowledge,/datum/heretic_knowledge/summon))
+ var/datum/heretic_knowledge/summon/summon_knowledge = knowledge
+ var/mob/living/result_mob = summon_knowledge.mob_to_summon
+ icon_path = result_mob.icon
+ icon_state = result_mob.icon_state
+
+ //if the knowledge is an eldritch mark, use the mark sprite
+ else if(ispath(knowledge,/datum/heretic_knowledge/mark))
+ var/datum/heretic_knowledge/mark/mark_knowledge = knowledge
+ var/datum/status_effect/eldritch/mark_effect = mark_knowledge.mark_type
+ icon_path = mark_effect.effect_icon
+ icon_state = mark_effect.effect_icon_state
+
+ //if the knowledge is an ascension, use the achievement sprite
+ else if(ispath(knowledge,/datum/heretic_knowledge/ultimate))
+ var/datum/heretic_knowledge/ultimate/ascension_knowledge = knowledge
+ var/datum/award/achievement/misc/achievement = ascension_knowledge.ascension_achievement
+ if(!isnull(achievement))
+ icon_path = achievement.icon
+ icon_state = achievement.icon_state
+
+ var/list/result_parameters = list()
+ result_parameters["icon"] = icon_path
+ result_parameters["state"] = icon_state
+ result_parameters["frame"] = icon_frame
+ result_parameters["dir"] = icon_dir
+ result_parameters["moving"] = icon_moving
+ return result_parameters
+
+/datum/antagonist/heretic/proc/get_knowledge_data(datum/heretic_knowledge/knowledge, done)
+
+ var/list/knowledge_data = list()
+
+ knowledge_data["path"] = knowledge
+ knowledge_data["icon_params"] = get_icon_of_knowledge(knowledge)
+ knowledge_data["name"] = initial(knowledge.name)
+ knowledge_data["gainFlavor"] = initial(knowledge.gain_text)
+ knowledge_data["cost"] = initial(knowledge.cost)
+ knowledge_data["disabled"] = (!done) && (initial(knowledge.cost) > knowledge_points)
+ knowledge_data["bgr"] = (path_to_ui_bgr[initial(knowledge.route)] || "side")
+ knowledge_data["finished"] = done
+ knowledge_data["ascension"] = ispath(knowledge,/datum/heretic_knowledge/ultimate)
+
+ //description of a knowledge might change, make sure we are not shown the initial() value in that case
+ if(done)
+ var/datum/heretic_knowledge/knowledge_instance = researched_knowledge[knowledge]
+ knowledge_data["desc"] = knowledge_instance.desc
+ else
+ knowledge_data["desc"] = initial(knowledge.desc)
+
+ return knowledge_data
+
/datum/antagonist/heretic/ui_data(mob/user)
var/list/data = list()
@@ -105,26 +181,32 @@
data["total_sacrifices"] = total_sacrifices
data["ascended"] = ascended
+ var/list/tiers = list()
+
// This should be cached in some way, but the fact that final knowledge
// has to update its disabled state based on whether all objectives are complete,
// makes this very difficult. I'll figure it out one day maybe
+ for(var/datum/heretic_knowledge/knowledge as anything in researched_knowledge)
+ var/list/knowledge_data = get_knowledge_data(knowledge,TRUE)
+
+ while(initial(knowledge.depth) > tiers.len)
+ tiers += list(list("nodes"=list()))
+
+ tiers[initial(knowledge.depth)]["nodes"] += list(knowledge_data)
+
for(var/datum/heretic_knowledge/knowledge as anything in get_researchable_knowledge())
- var/list/knowledge_data = list()
- knowledge_data["path"] = knowledge
- knowledge_data["name"] = initial(knowledge.name)
- knowledge_data["desc"] = initial(knowledge.desc)
- knowledge_data["gainFlavor"] = initial(knowledge.gain_text)
- knowledge_data["cost"] = initial(knowledge.cost)
- knowledge_data["disabled"] = initial(knowledge.cost) > knowledge_points
+ var/list/knowledge_data = get_knowledge_data(knowledge,FALSE)
// Final knowledge can't be learned until all objectives are complete.
if(ispath(knowledge, /datum/heretic_knowledge/ultimate))
- knowledge_data["disabled"] = !can_ascend()
+ knowledge_data["disabled"] ||= !can_ascend()
- knowledge_data["hereticPath"] = initial(knowledge.route)
- knowledge_data["color"] = path_to_ui_color[initial(knowledge.route)] || "grey"
+ while(initial(knowledge.depth) > tiers.len)
+ tiers += list(list("nodes"=list()))
- data["learnableKnowledge"] += list(knowledge_data)
+ tiers[initial(knowledge.depth)]["nodes"] += list(knowledge_data)
+
+ data["knowledge_tiers"] = tiers
return data
@@ -134,18 +216,6 @@
data["objectives"] = get_objectives()
data["can_change_objective"] = can_assign_self_objectives
- for(var/path in researched_knowledge)
- var/list/knowledge_data = list()
- var/datum/heretic_knowledge/found_knowledge = researched_knowledge[path]
- knowledge_data["name"] = found_knowledge.name
- knowledge_data["desc"] = found_knowledge.desc
- knowledge_data["gainFlavor"] = found_knowledge.gain_text
- knowledge_data["cost"] = found_knowledge.cost
- knowledge_data["hereticPath"] = found_knowledge.route
- knowledge_data["color"] = path_to_ui_color[found_knowledge.route] || "grey"
-
- data["learnedKnowledge"] += list(knowledge_data)
-
return data
/datum/antagonist/heretic/ui_act(action, params)
diff --git a/code/modules/antagonists/heretic/heretic_knowledge.dm b/code/modules/antagonists/heretic/heretic_knowledge.dm
index 4c83958d7f7ab..83c883b71616f 100644
--- a/code/modules/antagonists/heretic/heretic_knowledge.dm
+++ b/code/modules/antagonists/heretic/heretic_knowledge.dm
@@ -38,6 +38,14 @@
var/priority = 0
/// What path is this on. If set to "null", assumed to be unreachable (or abstract).
var/route
+ /// In case we want to override the default UI icon getter and plug in our own icon instead.
+ /// if research_tree_icon_path is not null, research_tree_icon_state must also be specified or things may break
+ var/research_tree_icon_path
+ var/research_tree_icon_state
+ var/research_tree_icon_frame = 1
+ var/research_tree_icon_dir = SOUTH
+ /// Level of knowledge tree where this knowledge should be in the UI
+ var/depth = 1
///Determines what kind of monster ghosts will ignore from here on out. Defaults to POLL_IGNORE_HERETIC_MONSTER, but we define other types of monsters for more granularity.
var/poll_ignore_define = POLL_IGNORE_HERETIC_MONSTER
@@ -263,6 +271,7 @@
limit = 2
cost = 1
priority = MAX_KNOWLEDGE_PRIORITY - 5
+ depth = 2
/datum/heretic_knowledge/limited_amount/starting/New()
. = ..()
@@ -287,6 +296,7 @@
abstract_parent_type = /datum/heretic_knowledge/mark
mutually_exclusive = TRUE
cost = 2
+ depth = 5
/// The status effect typepath we apply on people on mansus grasp.
var/datum/status_effect/eldritch/mark_type
@@ -352,6 +362,7 @@
abstract_parent_type = /datum/heretic_knowledge/blade_upgrade
mutually_exclusive = TRUE
cost = 2
+ depth = 9
/datum/heretic_knowledge/blade_upgrade/on_gain(mob/user, datum/antagonist/heretic/our_heretic)
RegisterSignal(user, COMSIG_HERETIC_BLADE_ATTACK, PROC_REF(on_eldritch_blade))
@@ -592,6 +603,9 @@
mutually_exclusive = TRUE
cost = 1
priority = MAX_KNOWLEDGE_PRIORITY - 10 // A pretty important midgame ritual.
+ depth = 6
+ research_tree_icon_path = 'icons/obj/antags/eldritch.dmi'
+ research_tree_icon_state = "book_open"
/// Whether we've done the ritual. Only doable once.
var/was_completed = FALSE
@@ -685,6 +699,9 @@
cost = 2
priority = MAX_KNOWLEDGE_PRIORITY + 1 // Yes, the final ritual should be ABOVE the max priority.
required_atoms = list(/mob/living/carbon/human = 3)
+ depth = 11
+ //use this to store the achievement typepath
+ var/datum/award/achievement/misc/ascension_achievement
/datum/heretic_knowledge/ultimate/on_research(mob/user, datum/antagonist/heretic/our_heretic)
. = ..()
@@ -745,6 +762,8 @@
source = user,
header = "Еретик вознесся!",
)
+ if(!isnull(ascension_achievement))
+ user.client?.give_award(ascension_achievement, user)
heretic_datum.increase_rust_strength()
return TRUE
diff --git a/code/modules/antagonists/heretic/items/eldritch_flask.dm b/code/modules/antagonists/heretic/items/eldritch_flask.dm
index 9fdf8a571158c..fcafba914938b 100644
--- a/code/modules/antagonists/heretic/items/eldritch_flask.dm
+++ b/code/modules/antagonists/heretic/items/eldritch_flask.dm
@@ -4,5 +4,5 @@
name = "flask of eldritch essence"
desc = "Токсичный для недалеких умов, но освежающий для тех, кто имеет знания о потустороннем."
icon = 'icons/obj/antags/eldritch.dmi'
- icon_state = "eldrich_flask"
+ icon_state = "eldritch_flask"
list_reagents = list(/datum/reagent/eldritch = 50)
diff --git a/code/modules/antagonists/heretic/items/keyring.dm b/code/modules/antagonists/heretic/items/keyring.dm
index e495ffd184b4d..eebdc9507d791 100644
--- a/code/modules/antagonists/heretic/items/keyring.dm
+++ b/code/modules/antagonists/heretic/items/keyring.dm
@@ -185,7 +185,7 @@
if(reference_resolved)
make_portal(user, reference_resolved, target)
- to_chat(user, span_notice("Используя [src], вы соединяете [link] с [target]."))
+ to_chat(user, span_notice("Используя [src], вы соединяете [reference_resolved] с [target]."))
link = null
balloon_alert(user, "соединено 2/2")
else
diff --git a/code/modules/antagonists/heretic/knowledge/ash_lore.dm b/code/modules/antagonists/heretic/knowledge/ash_lore.dm
index b5e532bca9092..6d2f231d31ddf 100644
--- a/code/modules/antagonists/heretic/knowledge/ash_lore.dm
+++ b/code/modules/antagonists/heretic/knowledge/ash_lore.dm
@@ -1,5 +1,7 @@
/**
* # The path of Ash.
+ * Spell names are in this language: OLD NORDIC
+ * Both are related: Nordic Mythology-Yggdrassil-Ash Tree Genus-Ash
*
* Goes as follows:
*
@@ -39,6 +41,8 @@
)
result_atoms = list(/obj/item/melee/sickly_blade/ash)
route = PATH_ASH
+ research_tree_icon_path = 'icons/obj/weapons/khopesh.dmi'
+ research_tree_icon_state = "ash_blade"
/datum/heretic_knowledge/ashen_grasp
name = "Grasp of Ash"
@@ -48,6 +52,9 @@
next_knowledge = list(/datum/heretic_knowledge/spell/ash_passage)
cost = 1
route = PATH_ASH
+ depth = 3
+ research_tree_icon_path = 'icons/ui_icons/antags/heretic/knowledge.dmi'
+ research_tree_icon_state = "grasp_ash"
/datum/heretic_knowledge/ashen_grasp/on_gain(mob/user, datum/antagonist/heretic/our_heretic)
RegisterSignal(user, COMSIG_HERETIC_MANSUS_GRASP_ATTACK, PROC_REF(on_mansus_grasp))
@@ -80,6 +87,7 @@
spell_to_add = /datum/action/cooldown/spell/jaunt/ethereal_jaunt/ash
cost = 1
route = PATH_ASH
+ depth = 4
/datum/heretic_knowledge/mark/ash_mark
name = "Mark of Ash"
@@ -119,6 +127,8 @@
spell_to_add = /datum/action/cooldown/spell/charged/beam/fire_blast
cost = 1
route = PATH_ASH
+ depth = 7
+ research_tree_icon_frame = 7
/datum/heretic_knowledge/mad_mask
@@ -142,6 +152,9 @@
result_atoms = list(/obj/item/clothing/mask/madness_mask)
cost = 1
route = PATH_ASH
+ research_tree_icon_path = 'icons/obj/clothing/masks.dmi'
+ research_tree_icon_state = "mad_mask"
+ depth = 8
/datum/heretic_knowledge/blade_upgrade/ash
name = "Fiery Blade"
@@ -150,6 +163,8 @@
Его город, люди, за которыми он поклялся наблюдать... и он наблюдал, пока все они сгорали дотла."
next_knowledge = list(/datum/heretic_knowledge/spell/flame_birth)
route = PATH_ASH
+ research_tree_icon_path = 'icons/ui_icons/antags/heretic/knowledge.dmi'
+ research_tree_icon_state = "blade_upgrade_ash"
/datum/heretic_knowledge/blade_upgrade/ash/do_melee_effects(mob/living/source, mob/living/target, obj/item/melee/sickly_blade/blade)
if(source == target)
@@ -173,6 +188,8 @@
spell_to_add = /datum/action/cooldown/spell/aoe/fiery_rebirth
cost = 1
route = PATH_ASH
+ depth = 10
+ research_tree_icon_frame = 5
/datum/heretic_knowledge/ultimate/ash_final
name = "Ashlord's Rite"
@@ -187,6 +204,7 @@
ибо он принес человечеству обряд! Его взгляд продолжается, и теперь я един с пламенем, \
УЗРИТЕ МОЕ ВОЗНЕСЕНИЕ, ПЕПЕЛЬНЫЙ ФОНАРЬ ВОСПЛАМЕНИТСЯ ВНОВЬ!"
route = PATH_ASH
+ ascension_achievement = /datum/award/achievement/misc/ash_ascension
/// A static list of all traits we apply on ascension.
var/static/list/traits_to_apply = list(
TRAIT_BOMBIMMUNE,
@@ -215,7 +233,7 @@
text = "[generate_heretic_text()] Бойтесь пламени, ибо Пепельный лорд, [user.real_name], вознесся! Пламя поглотит всех! [generate_heretic_text()]",
title = "[generate_heretic_text()]",
sound = 'sound/ambience/antag/heretic/ascend_ash.ogg',
- color_override = "pink",
+ color_override = "white",
)
var/datum/action/cooldown/spell/fire_sworn/circle_spell = new(user.mind)
@@ -233,6 +251,5 @@
var/datum/action/cooldown/spell/aoe/fiery_rebirth/fiery_rebirth = locate() in user.actions
fiery_rebirth?.cooldown_time *= 0.16
- user.client?.give_award(/datum/award/achievement/misc/ash_ascension, user)
if(length(traits_to_apply))
user.add_traits(traits_to_apply, MAGIC_TRAIT)
diff --git a/code/modules/antagonists/heretic/knowledge/blade_lore.dm b/code/modules/antagonists/heretic/knowledge/blade_lore.dm
index 704bc702a4505..d263980169b2c 100644
--- a/code/modules/antagonists/heretic/knowledge/blade_lore.dm
+++ b/code/modules/antagonists/heretic/knowledge/blade_lore.dm
@@ -1,5 +1,7 @@
/**
* # The path of Blades. Stab stab.
+ * Spell names are in this language: ARAMAIC
+ * Both are related: Aramaic-Damascus-Blade
*
* Goes as follows:
*
@@ -43,6 +45,8 @@
result_atoms = list(/obj/item/melee/sickly_blade/dark)
limit = 5 // It's the blade path, it's a given
route = PATH_BLADE
+ research_tree_icon_path = 'icons/obj/weapons/khopesh.dmi'
+ research_tree_icon_state = "dark_blade"
/datum/heretic_knowledge/blade_grasp
name = "Grasp of the Blade"
@@ -52,6 +56,9 @@
next_knowledge = list(/datum/heretic_knowledge/blade_dance)
cost = 1
route = PATH_BLADE
+ depth = 3
+ research_tree_icon_path = 'icons/ui_icons/antags/heretic/knowledge.dmi'
+ research_tree_icon_state = "grasp_blade"
/datum/heretic_knowledge/blade_grasp/on_gain(mob/user, datum/antagonist/heretic/our_heretic)
RegisterSignal(user, COMSIG_HERETIC_MANSUS_GRASP_ATTACK, PROC_REF(on_mansus_grasp))
@@ -111,6 +118,9 @@
)
cost = 1
route = PATH_BLADE
+ depth = 4
+ research_tree_icon_path = 'icons/mob/actions/actions_ecult.dmi'
+ research_tree_icon_state = "shatter"
/// Whether the counter-attack is ready or not.
/// Used instead of cooldowns, so we can give feedback when it's ready again
var/riposte_ready = TRUE
@@ -231,6 +241,7 @@
spell_to_add = /datum/action/cooldown/spell/realignment
cost = 1
route = PATH_BLADE
+ depth = 7
/// The amount of blood flow reduced per level of severity of gained bleeding wounds for Stance of the Torn Champion.
#define BLOOD_FLOW_PER_SEVEIRTY -1
@@ -251,6 +262,10 @@
)
cost = 1
route = PATH_BLADE
+ depth = 8
+ research_tree_icon_path = 'icons/effects/blood.dmi'
+ research_tree_icon_state = "suitblood"
+ research_tree_icon_dir = SOUTH
/// Whether we're currently in duelist stance, gaining certain buffs (low health)
var/in_duelist_stance = FALSE
@@ -310,6 +325,8 @@
шквал клинков, но ни один из них не попал в цель, ибо чемпион был неукротим."
next_knowledge = list(/datum/heretic_knowledge/spell/furious_steel)
route = PATH_BLADE
+ research_tree_icon_path = 'icons/ui_icons/antags/heretic/knowledge.dmi'
+ research_tree_icon_state = "blade_upgrade_blade"
/// How much force do we apply to the offhand?
var/offand_force_decrement = 0
/// How much force was the last weapon we offhanded with? If it's different, we need to re-calculate the decrement
@@ -380,6 +397,7 @@
spell_to_add = /datum/action/cooldown/spell/pointed/projectile/furious_steel
cost = 1
route = PATH_BLADE
+ depth = 10
/datum/heretic_knowledge/ultimate/blade_final
name = "Maelstrom of Silver"
@@ -393,6 +411,7 @@
gain_text = "Разорванный чемпион освобожден! Я стану воссоединенным клинком, и с моими более великими амбициями, \
НЕ НЕТ РАВНЫХ! БУРЯ ИЗ СТАЛИ И СЕРЕБРА НАДВИГАЕТСЯ НА НАС! УЗРИТЕ МОЕ ВОЗНЕСЕНИЕ!"
route = PATH_BLADE
+ ascension_achievement = /datum/award/achievement/misc/blade_ascension
/datum/heretic_knowledge/ultimate/blade_final/is_valid_sacrifice(mob/living/carbon/human/sacrifice)
. = ..()
@@ -409,7 +428,6 @@
sound = 'sound/ambience/antag/heretic/ascend_blade.ogg',
color_override = "pink",
)
- user.client?.give_award(/datum/award/achievement/misc/blade_ascension, user)
ADD_TRAIT(user, TRAIT_NEVER_WOUNDED, name)
RegisterSignal(user, COMSIG_HERETIC_BLADE_ATTACK, PROC_REF(on_eldritch_blade))
user.apply_status_effect(/datum/status_effect/protective_blades/recharging, null, 8, 30, 0.25 SECONDS, 1 MINUTES)
diff --git a/code/modules/antagonists/heretic/knowledge/cosmic_lore.dm b/code/modules/antagonists/heretic/knowledge/cosmic_lore.dm
index 60112bb9878a9..670fb8fac6fd8 100644
--- a/code/modules/antagonists/heretic/knowledge/cosmic_lore.dm
+++ b/code/modules/antagonists/heretic/knowledge/cosmic_lore.dm
@@ -1,5 +1,7 @@
/**
* # The path of Cosmos.
+ * Spell names are in this language: SUMERIAN
+ * Both are related: Sumerian-Original-Primordial-Cosmic
*
* Goes as follows:
*
@@ -38,6 +40,8 @@
)
result_atoms = list(/obj/item/melee/sickly_blade/cosmic)
route = PATH_COSMIC
+ research_tree_icon_path = 'icons/obj/weapons/khopesh.dmi'
+ research_tree_icon_state = "cosmic_blade"
/datum/heretic_knowledge/cosmic_grasp
name = "Grasp of Cosmos"
@@ -48,6 +52,9 @@
next_knowledge = list(/datum/heretic_knowledge/spell/cosmic_runes)
cost = 1
route = PATH_COSMIC
+ depth = 3
+ research_tree_icon_path = 'icons/ui_icons/antags/heretic/knowledge.dmi'
+ research_tree_icon_state = "grasp_cosmos"
/datum/heretic_knowledge/cosmic_grasp/on_gain(mob/user, datum/antagonist/heretic/our_heretic)
RegisterSignal(user, COMSIG_HERETIC_MANSUS_GRASP_ATTACK, PROC_REF(on_mansus_grasp))
@@ -78,6 +85,7 @@
spell_to_add = /datum/action/cooldown/spell/cosmic_rune
cost = 1
route = PATH_COSMIC
+ depth = 4
/datum/heretic_knowledge/mark/cosmic_mark
name = "Mark of Cosmos"
@@ -107,6 +115,7 @@
spell_to_add = /datum/action/cooldown/spell/touch/star_touch
cost = 1
route = PATH_COSMIC
+ depth = 7
/datum/heretic_knowledge/spell/star_blast
name = "Star Blast"
@@ -123,6 +132,7 @@
spell_to_add = /datum/action/cooldown/spell/pointed/projectile/star_blast
cost = 1
route = PATH_COSMIC
+ depth = 8
/datum/heretic_knowledge/blade_upgrade/cosmic
name = "Cosmic Blade"
@@ -135,6 +145,8 @@
Клинки теперь сверкали раздробленной силой. Я упал на землю и зарыдал у ног Зверя."
next_knowledge = list(/datum/heretic_knowledge/spell/cosmic_expansion)
route = PATH_COSMIC
+ research_tree_icon_path = 'icons/ui_icons/antags/heretic/knowledge.dmi'
+ research_tree_icon_state = "blade_upgrade_cosmos"
/// Storage for the second target.
var/datum/weakref/second_target
/// Storage for the third target.
@@ -233,6 +245,7 @@
spell_to_add = /datum/action/cooldown/spell/conjure/cosmic_expansion
cost = 1
route = PATH_COSMIC
+ depth = 10
/datum/heretic_knowledge/ultimate/cosmic_final
name = "Creators's Gift"
@@ -250,6 +263,7 @@
Я закрыл глаза, прижавшись головой к их телу. Я был в безопасности. \
УЗРИТЕ МОЕ ВОЗНЕСЕНИЕ!"
route = PATH_COSMIC
+ ascension_achievement = /datum/award/achievement/misc/cosmic_ascension
/// A static list of command we can use with our mob.
var/static/list/star_gazer_commands = list(
/datum/pet_command/idle,
@@ -271,7 +285,7 @@
text = "[generate_heretic_text()] Звездочет прибыл на станцию, [user.real_name] вознесся! Эта станция - владения Космоса! [generate_heretic_text()]",
title = "[generate_heretic_text()]",
sound = 'sound/ambience/antag/heretic/ascend_cosmic.ogg',
- color_override = "pink",
+ color_override = "purple",
)
var/mob/living/basic/heretic_summon/star_gazer/star_gazer_mob = new /mob/living/basic/heretic_summon/star_gazer(loc)
star_gazer_mob.maxHealth = INFINITY
@@ -296,5 +310,3 @@
var/datum/action/cooldown/spell/conjure/cosmic_expansion/cosmic_expansion_spell = locate() in user.actions
cosmic_expansion_spell?.ascended = TRUE
-
- user.client?.give_award(/datum/award/achievement/misc/cosmic_ascension, user)
diff --git a/code/modules/antagonists/heretic/knowledge/flesh_lore.dm b/code/modules/antagonists/heretic/knowledge/flesh_lore.dm
index 5f986db82fa95..5547bb974758c 100644
--- a/code/modules/antagonists/heretic/knowledge/flesh_lore.dm
+++ b/code/modules/antagonists/heretic/knowledge/flesh_lore.dm
@@ -5,6 +5,8 @@
/**
* # The path of Flesh.
+ * Spell names are in this language: LATIN
+ * Both are related: Latin-Rome-Hedonism-Flesh
*
* Goes as follows:
*
@@ -44,6 +46,8 @@
result_atoms = list(/obj/item/melee/sickly_blade/flesh)
limit = 3 // Bumped up so they can arm up their ghouls too.
route = PATH_FLESH
+ research_tree_icon_path = 'icons/obj/weapons/khopesh.dmi'
+ research_tree_icon_state = "flesh_blade"
/datum/heretic_knowledge/limited_amount/starting/base_flesh/on_research(mob/user, datum/antagonist/heretic/our_heretic)
. = ..()
@@ -64,6 +68,9 @@
limit = 1
cost = 1
route = PATH_FLESH
+ depth = 3
+ research_tree_icon_path = 'icons/ui_icons/antags/heretic/knowledge.dmi'
+ research_tree_icon_state = "grasp_flesh"
/datum/heretic_knowledge/limited_amount/flesh_grasp/on_gain(mob/user, datum/antagonist/heretic/our_heretic)
RegisterSignal(user, COMSIG_HERETIC_MANSUS_GRASP_ATTACK, PROC_REF(on_mansus_grasp))
@@ -137,6 +144,10 @@
limit = 2
cost = 1
route = PATH_FLESH
+ research_tree_icon_path = 'icons/ui_icons/antags/heretic/knowledge.dmi'
+ research_tree_icon_state = "ghoul_voiceless"
+
+ depth = 4
/datum/heretic_knowledge/limited_amount/flesh_ghoul/recipe_snowflake_check(mob/living/user, list/atoms, list/selected_atoms, turf/loc)
. = ..()
@@ -227,6 +238,7 @@
spell_to_add = /datum/action/cooldown/spell/touch/flesh_surgery
cost = 1
route = PATH_FLESH
+ depth = 7
/datum/heretic_knowledge/summon/raw_prophet
name = "Raw Ritual"
@@ -250,6 +262,7 @@
cost = 1
route = PATH_FLESH
poll_ignore_define = POLL_IGNORE_RAW_PROPHET
+ depth = 8
/datum/heretic_knowledge/blade_upgrade/flesh
name = "Bleeding Steel"
@@ -258,6 +271,8 @@
Наконец-то я начал понимать. А потом с небес хлынул кровавый дождь."
next_knowledge = list(/datum/heretic_knowledge/summon/stalker)
route = PATH_FLESH
+ research_tree_icon_path = 'icons/ui_icons/antags/heretic/knowledge.dmi'
+ research_tree_icon_state = "blade_upgrade_flesh"
///What type of wound do we apply on hit
var/wound_type = /datum/wound/slash/flesh/severe
@@ -292,6 +307,7 @@
cost = 1
route = PATH_FLESH
poll_ignore_define = POLL_IGNORE_STALKER
+ depth = 10
/datum/heretic_knowledge/ultimate/flesh_final
name = "Priest's Final Hymn"
@@ -308,6 +324,7 @@
Реальность согнется перед ВЛАДЫКОЙ НОЧИ или будет разрушена! УЗРИТЕ МОЕ ВОЗНЕСЕНИЕ!"
required_atoms = list(/mob/living/carbon/human = 4)
route = PATH_FLESH
+ ascension_achievement = /datum/award/achievement/misc/flesh_ascension
/datum/heretic_knowledge/ultimate/flesh_final/on_finished_recipe(mob/living/user, list/selected_atoms, turf/loc)
. = ..()
@@ -315,13 +332,12 @@
text = "[generate_heretic_text()] Вечно закручивающийся вихрь. Реальность развернулась. С ВЫТЯНУТЫМИ РУКАМИ, ВЛАСТЕЛИН НОЧИ, [user.real_name] вознесся! Бойтесь вечно изгибающейся руки! [generate_heretic_text()]",
title = "[generate_heretic_text()]",
sound = 'sound/ambience/antag/heretic/ascend_flesh.ogg',
- color_override = "pink",
+ color_override = "red",
)
var/datum/action/cooldown/spell/shapeshift/shed_human_form/worm_spell = new(user.mind)
worm_spell.Grant(user)
- user.client?.give_award(/datum/award/achievement/misc/flesh_ascension, user)
var/datum/antagonist/heretic/heretic_datum = IS_HERETIC(user)
var/datum/heretic_knowledge/limited_amount/flesh_grasp/grasp_ghoul = heretic_datum.get_knowledge(/datum/heretic_knowledge/limited_amount/flesh_grasp)
diff --git a/code/modules/antagonists/heretic/knowledge/general_side.dm b/code/modules/antagonists/heretic/knowledge/general_side.dm
index d5e76c5bd608f..f6c9abb855447 100644
--- a/code/modules/antagonists/heretic/knowledge/general_side.dm
+++ b/code/modules/antagonists/heretic/knowledge/general_side.dm
@@ -12,6 +12,9 @@
)
cost = 1
route = PATH_SIDE
+ depth = 8
+ research_tree_icon_path = 'icons/mob/actions/actions_animal.dmi'
+ research_tree_icon_state = "gaze"
/datum/heretic_knowledge/reroll_targets/recipe_snowflake_check(mob/living/user, list/atoms, list/selected_atoms, turf/loc)
diff --git a/code/modules/antagonists/heretic/knowledge/lock_lore.dm b/code/modules/antagonists/heretic/knowledge/lock_lore.dm
index bf38cefaae0c4..5c41a793355da 100644
--- a/code/modules/antagonists/heretic/knowledge/lock_lore.dm
+++ b/code/modules/antagonists/heretic/knowledge/lock_lore.dm
@@ -1,5 +1,7 @@
/**
* # The path of Lock.
+ * Spell names are in this language: EGYPTIAN
+ * Both are related: Egyptian-Mysteries-Secrets-Lock
*
* Goes as follows:
*
@@ -39,6 +41,8 @@
result_atoms = list(/obj/item/melee/sickly_blade/lock)
limit = 2
route = PATH_LOCK
+ research_tree_icon_path = 'icons/obj/weapons/khopesh.dmi'
+ research_tree_icon_state = "key_blade"
/datum/heretic_knowledge/lock_grasp
name = "Grasp of Lock"
@@ -49,6 +53,9 @@
next_knowledge = list(/datum/heretic_knowledge/key_ring)
cost = 1
route = PATH_LOCK
+ depth = 3
+ research_tree_icon_path = 'icons/ui_icons/antags/heretic/knowledge.dmi'
+ research_tree_icon_state = "grasp_lock"
/datum/heretic_knowledge/lock_grasp/on_gain(mob/user, datum/antagonist/heretic/our_heretic)
RegisterSignal(user, COMSIG_HERETIC_MANSUS_GRASP_ATTACK_SECONDARY, PROC_REF(on_secondary_mansus_grasp))
@@ -111,6 +118,9 @@
)
cost = 1
route = PATH_LOCK
+ research_tree_icon_path = 'icons/obj/card.dmi'
+ research_tree_icon_state = "card_gold"
+ depth = 4
/datum/heretic_knowledge/mark/lock_mark
name = "Mark of Lock"
@@ -140,6 +150,9 @@
next_knowledge = list(/datum/heretic_knowledge/spell/burglar_finesse)
cost = 1
route = PATH_LOCK
+ research_tree_icon_path = 'icons/obj/service/library.dmi'
+ research_tree_icon_state = "heretichandbook"
+ depth = 7
/datum/heretic_knowledge/spell/burglar_finesse
name = "Burglar's Finesse"
@@ -156,6 +169,7 @@
spell_to_add = /datum/action/cooldown/spell/pointed/burglar_finesse
cost = 1
route = PATH_LOCK
+ depth = 8
/datum/heretic_knowledge/blade_upgrade/flesh/lock //basically a chance-based weeping avulsion version of the former
name = "Opening Blade"
@@ -164,6 +178,8 @@
next_knowledge = list(/datum/heretic_knowledge/spell/caretaker_refuge)
route = PATH_LOCK
wound_type = /datum/wound/slash/flesh/critical
+ research_tree_icon_path = 'icons/ui_icons/antags/heretic/knowledge.dmi'
+ research_tree_icon_state = "blade_upgrade_lock"
var/chance = 35
/datum/heretic_knowledge/blade_upgrade/flesh/lock/do_melee_effects(mob/living/source, mob/living/target, obj/item/melee/sickly_blade/blade)
@@ -183,6 +199,7 @@
route = PATH_LOCK
spell_to_add = /datum/action/cooldown/spell/caretaker
cost = 1
+ depth = 10
/datum/heretic_knowledge/ultimate/lock_final
name = "Unlock the Labyrinth"
@@ -199,6 +216,7 @@
Лабиринт теперь не будет Заперт, свобода будет нашей! УЗРИТЕ НАС!"
required_atoms = list(/mob/living/carbon/human = 3)
route = PATH_LOCK
+ ascension_achievement = /datum/award/achievement/misc/lock_ascension
/datum/heretic_knowledge/ultimate/lock_final/recipe_snowflake_check(mob/living/user, list/atoms, list/selected_atoms, turf/loc)
. = ..()
@@ -225,15 +243,13 @@
text = "Пространственная аномалия Дельта-класса обнар[generate_heretic_text()] Реальность разрушена, разорвана. Врата открыты, двери открыты, [user.real_name] вознесся! Бойтесь нашествия! [generate_heretic_text()]",
title = "[generate_heretic_text()]",
sound = 'sound/ambience/antag/heretic/ascend_knock.ogg',
- color_override = "pink",
+ color_override = "yellow",
)
- user.client?.give_award(/datum/award/achievement/misc/lock_ascension, user)
// buffs
var/datum/action/cooldown/spell/shapeshift/eldritch/ascension/transform_spell = new(user.mind)
transform_spell.Grant(user)
- user.client?.give_award(/datum/award/achievement/misc/lock_ascension, user)
var/datum/antagonist/heretic/heretic_datum = IS_HERETIC(user)
var/datum/heretic_knowledge/blade_upgrade/flesh/lock/blade_upgrade = heretic_datum.get_knowledge(/datum/heretic_knowledge/blade_upgrade/flesh/lock)
blade_upgrade.chance += 30
diff --git a/code/modules/antagonists/heretic/knowledge/moon_lore.dm b/code/modules/antagonists/heretic/knowledge/moon_lore.dm
index 8dd32c201dc45..511ac0f0cafbd 100644
--- a/code/modules/antagonists/heretic/knowledge/moon_lore.dm
+++ b/code/modules/antagonists/heretic/knowledge/moon_lore.dm
@@ -1,5 +1,7 @@
/**
* # The path of Moon.
+ * Spell names are in this language: ANCIENT HEBREW
+ * Both are related: Ancient Hebrew-Moon Mysticism-Moon
*
* Goes as follows:
*
@@ -39,6 +41,8 @@
)
result_atoms = list(/obj/item/melee/sickly_blade/moon)
route = PATH_MOON
+ research_tree_icon_path = 'icons/obj/weapons/khopesh.dmi'
+ research_tree_icon_state = "moon_blade"
/datum/heretic_knowledge/base_moon/on_gain(mob/user, datum/antagonist/heretic/our_heretic)
add_traits(user ,TRAIT_EMPATH, REF(src))
@@ -51,6 +55,9 @@
next_knowledge = list(/datum/heretic_knowledge/spell/moon_smile)
cost = 1
route = PATH_MOON
+ depth = 3
+ research_tree_icon_path = 'icons/ui_icons/antags/heretic/knowledge.dmi'
+ research_tree_icon_state = "grasp_moon"
/datum/heretic_knowledge/moon_grasp/on_gain(mob/user, datum/antagonist/heretic/our_heretic)
RegisterSignal(user, COMSIG_HERETIC_MANSUS_GRASP_ATTACK, PROC_REF(on_mansus_grasp))
@@ -82,6 +89,7 @@
spell_to_add = /datum/action/cooldown/spell/pointed/moon_smile
cost = 1
route = PATH_MOON
+ depth = 4
/datum/heretic_knowledge/mark/moon_mark
name = "Mark of Moon"
@@ -107,6 +115,7 @@
spell_to_add = /datum/action/cooldown/spell/pointed/projectile/moon_parade
cost = 1
route = PATH_MOON
+ depth = 7
/datum/heretic_knowledge/moon_amulet
@@ -130,6 +139,10 @@
result_atoms = list(/obj/item/clothing/neck/heretic_focus/moon_amulet)
cost = 1
route = PATH_MOON
+ depth = 8
+ research_tree_icon_path = 'icons/obj/antags/eldritch.dmi'
+ research_tree_icon_state = "moon_amulette"
+ research_tree_icon_frame = 9
/datum/heretic_knowledge/blade_upgrade/moon
name = "Moonlight Blade"
@@ -137,6 +150,8 @@
gain_text = "Его остроумие было острым, как клинок, оно прорезало ложь, чтобы принести нам радость."
next_knowledge = list(/datum/heretic_knowledge/spell/moon_ringleader)
route = PATH_MOON
+ research_tree_icon_path = 'icons/ui_icons/antags/heretic/knowledge.dmi'
+ research_tree_icon_state = "blade_upgrade_moon"
/datum/heretic_knowledge/blade_upgrade/moon/do_melee_effects(mob/living/source, mob/living/target, obj/item/melee/sickly_blade/blade)
if(source == target)
@@ -164,6 +179,8 @@
spell_to_add = /datum/action/cooldown/spell/aoe/moon_ringleader
cost = 1
route = PATH_MOON
+ depth = 10
+ research_tree_icon_frame = 5
/datum/heretic_knowledge/ultimate/moon_final
name = "The Last Act"
@@ -176,6 +193,7 @@
туда, откуда Шпрехшталмейстер начал парад, и я продолжу его до самой кончины солнца \
УЗРИТЕ МОЕ ВОЗНЕСЕНИЕ, ЛУНА УЛЫБНЕТСЯ РАЗ И НАВСЕГДА!"
route = PATH_MOON
+ ascension_achievement = /datum/award/achievement/misc/moon_ascension
/datum/heretic_knowledge/ultimate/moon_final/is_valid_sacrifice(mob/living/sacrifice)
@@ -193,10 +211,9 @@
Правда наконец поглотит ложь! [generate_heretic_text()]",
title = "[generate_heretic_text()]",
sound = 'sound/ambience/antag/heretic/ascend_moon.ogg',
- color_override = "pink",
+ color_override = "blue",
)
- user.client?.give_award(/datum/award/achievement/misc/moon_ascension, user)
ADD_TRAIT(user, TRAIT_MADNESS_IMMUNE, REF(src))
user.mind.add_antag_datum(/datum/antagonist/lunatic/master)
RegisterSignal(user, COMSIG_LIVING_LIFE, PROC_REF(on_life))
diff --git a/code/modules/antagonists/heretic/knowledge/rust_lore.dm b/code/modules/antagonists/heretic/knowledge/rust_lore.dm
index a40a9c8717a6d..0fe648d8ecbb4 100644
--- a/code/modules/antagonists/heretic/knowledge/rust_lore.dm
+++ b/code/modules/antagonists/heretic/knowledge/rust_lore.dm
@@ -1,5 +1,6 @@
/**
* # The path of Rust.
+ * Spell names are in this language: OLD SLAVIC
*
* Goes as follows:
*
@@ -42,6 +43,8 @@
)
result_atoms = list(/obj/item/melee/sickly_blade/rust)
route = PATH_RUST
+ research_tree_icon_path = 'icons/obj/weapons/khopesh.dmi'
+ research_tree_icon_state = "rust_blade"
/datum/heretic_knowledge/rust_fist
name = "Grasp of Rust"
@@ -52,6 +55,9 @@
next_knowledge = list(/datum/heretic_knowledge/rust_regen)
cost = 1
route = PATH_RUST
+ depth = 3
+ research_tree_icon_path = 'icons/ui_icons/antags/heretic/knowledge.dmi'
+ research_tree_icon_state = "grasp_rust"
/datum/heretic_knowledge/rust_fist/on_gain(mob/user, datum/antagonist/heretic/our_heretic)
RegisterSignal(user, COMSIG_HERETIC_MANSUS_GRASP_ATTACK, PROC_REF(on_mansus_grasp))
@@ -93,6 +99,9 @@
)
cost = 1
route = PATH_RUST
+ research_tree_icon_path = 'icons/effects/eldritch.dmi'
+ research_tree_icon_state = "cloud_swirl"
+ depth = 4
/datum/heretic_knowledge/rust_regen/on_gain(mob/user, datum/antagonist/heretic/our_heretic)
user.AddElement(/datum/element/leeching_walk)
@@ -128,6 +137,7 @@
spell_to_add = /datum/action/cooldown/spell/pointed/rust_construction
cost = 1
route = PATH_RUST
+ depth = 7
/datum/heretic_knowledge/spell/area_conversion
name = "Aggressive Spread"
@@ -145,6 +155,8 @@
spell_to_add = /datum/action/cooldown/spell/aoe/rust_conversion
cost = 1
route = PATH_RUST
+ depth = 8
+ research_tree_icon_frame = 5
/datum/heretic_knowledge/spell/area_conversion/on_gain(mob/user, datum/antagonist/heretic/our_heretic)
. = ..()
@@ -157,6 +169,8 @@
Тяжелая ржавчина утяжеляет клинок. Вы пристально вглядываетесь в него. Ржавые холмы зовут тебя."
next_knowledge = list(/datum/heretic_knowledge/spell/entropic_plume)
route = PATH_RUST
+ research_tree_icon_path = 'icons/ui_icons/antags/heretic/knowledge.dmi'
+ research_tree_icon_state = "blade_upgrade_rust"
/datum/heretic_knowledge/blade_upgrade/rust/on_gain(mob/user, datum/antagonist/heretic/our_heretic)
. = ..()
@@ -181,6 +195,7 @@
spell_to_add = /datum/action/cooldown/spell/cone/staggered/entropic_plume
cost = 1
route = PATH_RUST
+ depth = 10
/datum/heretic_knowledge/spell/entropic_plume/on_gain(mob/user)
. = ..()
@@ -197,6 +212,7 @@
gain_text = "Чемпион ржавчины. Разлагатель стали. Бойся темноты, ибо пришел ПОВЕЛИТЕЛЬ РЖАВЧИНЫ! \
Работа Кузнеца продолжается! Ржавые холмы, УСЛЫШЬТЕ МОЕ ИМЯ! УЗРИТЕ МОЕ ВОЗНЕСЕНИЕ!"
route = PATH_RUST
+ ascension_achievement = /datum/award/achievement/misc/rust_ascension
/// If TRUE, then immunities are currently active.
var/immunities_active = FALSE
/// A typepath to an area that we must finish the ritual in.
@@ -242,7 +258,7 @@
text = "[generate_heretic_text()] Бойтесь разложения, ведь Повелитель ржавчины, [user.real_name] вознесся! Никто не избежит коррозии! [generate_heretic_text()]",
title = "[generate_heretic_text()]",
sound = 'sound/ambience/antag/heretic/ascend_rust.ogg',
- color_override = "pink",
+ color_override = "brown",
)
trigger(loc)
RegisterSignal(user, COMSIG_MOVABLE_MOVED, PROC_REF(on_move))
diff --git a/code/modules/antagonists/heretic/knowledge/sacrifice_knowledge/sacrifice_knowledge.dm b/code/modules/antagonists/heretic/knowledge/sacrifice_knowledge/sacrifice_knowledge.dm
index 9fd24f0c1e584..25656f377a68b 100644
--- a/code/modules/antagonists/heretic/knowledge/sacrifice_knowledge/sacrifice_knowledge.dm
+++ b/code/modules/antagonists/heretic/knowledge/sacrifice_knowledge/sacrifice_knowledge.dm
@@ -16,6 +16,9 @@
cost = 0
priority = MAX_KNOWLEDGE_PRIORITY // Should be at the top
route = PATH_START
+ research_tree_icon_path = 'icons/effects/eldritch.dmi'
+ research_tree_icon_state = "eye_close"
+ research_tree_icon_frame = 1
/// How many targets do we generate?
var/num_targets_to_generate = 5
/// Whether we've generated a heretic sacrifice z-level yet, from any heretic.
diff --git a/code/modules/antagonists/heretic/knowledge/side_ash_moon.dm b/code/modules/antagonists/heretic/knowledge/side_ash_moon.dm
index 52f96a1337409..c3fc2783bb89f 100644
--- a/code/modules/antagonists/heretic/knowledge/side_ash_moon.dm
+++ b/code/modules/antagonists/heretic/knowledge/side_ash_moon.dm
@@ -16,6 +16,9 @@
result_atoms = list(/obj/item/clothing/neck/eldritch_amulet)
cost = 1
route = PATH_SIDE
+ research_tree_icon_path = 'icons/obj/antags/eldritch.dmi'
+ research_tree_icon_state = "eye_medalion"
+ depth = 4
/datum/heretic_knowledge/curse/paralysis
name = "Curse of Paralysis"
@@ -37,6 +40,9 @@
curse_color = "#f19a9a"
cost = 1
route = PATH_SIDE
+ research_tree_icon_path = 'icons/ui_icons/antags/heretic/knowledge.dmi'
+ research_tree_icon_state = "curse_paralysis"
+ depth = 8
/datum/heretic_knowledge/curse/paralysis/curse(mob/living/carbon/human/chosen_mob, boosted = FALSE)
if(chosen_mob.usable_legs <= 0) // What're you gonna do, curse someone who already can't walk?
@@ -75,6 +81,7 @@
cost = 1
route = PATH_SIDE
poll_ignore_define = POLL_IGNORE_ASH_SPIRIT
+ depth = 10
/datum/heretic_knowledge/summon/ashy/cleanup_atoms(list/selected_atoms)
var/obj/item/bodypart/head/ritual_head = locate() in selected_atoms
diff --git a/code/modules/antagonists/heretic/knowledge/side_blade_rust.dm b/code/modules/antagonists/heretic/knowledge/side_blade_rust.dm
index 2a94e648c54bb..aa250ce435e3d 100644
--- a/code/modules/antagonists/heretic/knowledge/side_blade_rust.dm
+++ b/code/modules/antagonists/heretic/knowledge/side_blade_rust.dm
@@ -16,6 +16,10 @@
result_atoms = list(/obj/item/clothing/suit/hooded/cultrobes/eldritch)
cost = 1
route = PATH_SIDE
+ research_tree_icon_path = 'icons/obj/clothing/suits/armor.dmi'
+ research_tree_icon_state = "eldritch_armor"
+ research_tree_icon_frame = 12
+ depth = 4
/datum/heretic_knowledge/crucible
name = "Mawed Crucible"
@@ -34,6 +38,9 @@
result_atoms = list(/obj/structure/destructible/eldritch_crucible)
cost = 1
route = PATH_SIDE
+ research_tree_icon_path = 'icons/obj/antags/eldritch.dmi'
+ research_tree_icon_state = "crucible"
+ depth = 8
/datum/heretic_knowledge/rifle
name = "Lionhunter's Rifle"
@@ -59,9 +66,12 @@
result_atoms = list(/obj/item/gun/ballistic/rifle/lionhunter)
cost = 1
route = PATH_SIDE
+ depth = 8
+ research_tree_icon_path = 'icons/obj/weapons/guns/ballistic.dmi'
+ research_tree_icon_state = "goldrevolver"
/datum/heretic_knowledge/rifle_ammo
- name = "Lionhunter Rifle Ammunition (free)"
+ name = "Lionhunter Rifle Ammunition"
desc = "Позволяет трансмутировать 3 баллистические гильзы (использованные или неиспользованные) любого калибра, \
включая дробь, со шкурой любого животного, чтобы создать дополнительную обойму боеприпасов для винтовки Lionhunter."
gain_text = "К оружию прилагались три грубых железных шара, предназначенных для использования в качестве боеприпасов. \
@@ -74,6 +84,9 @@
result_atoms = list(/obj/item/ammo_box/strilka310/lionhunter)
cost = 0
route = PATH_SIDE
+ research_tree_icon_path = 'icons/obj/weapons/guns/ammo.dmi'
+ research_tree_icon_state = "310_strip"
+ depth = 8
/// A list of calibers that the ritual will deny. Only ballistic calibers are allowed.
var/static/list/caliber_blacklist = list(
CALIBER_LASER,
@@ -107,3 +120,4 @@
spell_to_add = /datum/action/cooldown/mob_cooldown/charge/rust
cost = 1
route = PATH_SIDE
+ depth = 10
diff --git a/code/modules/antagonists/heretic/knowledge/side_cosmos_ash.dm b/code/modules/antagonists/heretic/knowledge/side_cosmos_ash.dm
index 37758eb2b2239..5a8b0f852b4c4 100644
--- a/code/modules/antagonists/heretic/knowledge/side_cosmos_ash.dm
+++ b/code/modules/antagonists/heretic/knowledge/side_cosmos_ash.dm
@@ -19,6 +19,8 @@
cost = 1
route = PATH_SIDE
poll_ignore_define = POLL_IGNORE_FIRE_SHARK
+ depth = 4
+ research_tree_icon_dir = EAST
/datum/heretic_knowledge/spell/space_phase
name = "Space Phase"
@@ -32,6 +34,8 @@
spell_to_add = /datum/action/cooldown/spell/jaunt/space_crawl
cost = 1
route = PATH_SIDE
+ depth = 8
+ research_tree_icon_frame = 6
/datum/heretic_knowledge/eldritch_coin
name = "Eldritch Coin"
@@ -51,3 +55,6 @@
result_atoms = list(/obj/item/coin/eldritch)
cost = 1
route = PATH_SIDE
+ research_tree_icon_path = 'icons/obj/economy.dmi'
+ research_tree_icon_state = "coin_heretic"
+ depth = 10
diff --git a/code/modules/antagonists/heretic/knowledge/side_flesh_void.dm b/code/modules/antagonists/heretic/knowledge/side_flesh_void.dm
index c85ea8da6acba..839158c7f253c 100644
--- a/code/modules/antagonists/heretic/knowledge/side_flesh_void.dm
+++ b/code/modules/antagonists/heretic/knowledge/side_flesh_void.dm
@@ -19,6 +19,9 @@
result_atoms = list(/obj/item/clothing/suit/hooded/cultrobes/void)
cost = 1
route = PATH_SIDE
+ research_tree_icon_path = 'icons/obj/clothing/suits/armor.dmi'
+ research_tree_icon_state = "void_cloak"
+ depth = 4
/datum/heretic_knowledge/spell/blood_siphon
name = "Blood Siphon"
@@ -32,6 +35,7 @@
spell_to_add = /datum/action/cooldown/spell/pointed/blood_siphon
cost = 1
route = PATH_SIDE
+ depth = 8
/datum/heretic_knowledge/spell/cleave
name = "Blood Cleave"
@@ -46,3 +50,4 @@
spell_to_add = /datum/action/cooldown/spell/pointed/cleave
cost = 1
route = PATH_SIDE
+ depth = 10
diff --git a/code/modules/antagonists/heretic/knowledge/side_lock_flesh.dm b/code/modules/antagonists/heretic/knowledge/side_lock_flesh.dm
index 50f9867847d85..f20901387c19a 100644
--- a/code/modules/antagonists/heretic/knowledge/side_lock_flesh.dm
+++ b/code/modules/antagonists/heretic/knowledge/side_lock_flesh.dm
@@ -12,6 +12,7 @@
spell_to_add = /datum/action/cooldown/spell/aoe/wave_of_desperation
cost = 1
route = PATH_SIDE
+ depth = 8
/datum/heretic_knowledge/spell/apetra_vulnera
name = "Apetra Vulnera"
@@ -26,3 +27,4 @@
spell_to_add = /datum/action/cooldown/spell/pointed/apetra_vulnera
cost = 1
route = PATH_SIDE
+ depth = 10
diff --git a/code/modules/antagonists/heretic/knowledge/side_lock_moon.dm b/code/modules/antagonists/heretic/knowledge/side_lock_moon.dm
index 1b22cb7d4450a..cc421c717114e 100644
--- a/code/modules/antagonists/heretic/knowledge/side_lock_moon.dm
+++ b/code/modules/antagonists/heretic/knowledge/side_lock_moon.dm
@@ -13,6 +13,7 @@
spell_to_add = /datum/action/cooldown/spell/pointed/mind_gate
cost = 1
route = PATH_SIDE
+ depth = 4
/datum/heretic_knowledge/unfathomable_curio
name = "Unfathomable Curio"
@@ -33,6 +34,9 @@
result_atoms = list(/obj/item/storage/belt/unfathomable_curio)
cost = 1
route = PATH_SIDE
+ research_tree_icon_path = 'icons/obj/clothing/belts.dmi'
+ research_tree_icon_state = "unfathomable_curio"
+ depth = 8
/datum/heretic_knowledge/painting
name = "Unsealed Arts"
@@ -54,6 +58,9 @@
result_atoms = list(/obj/item/canvas)
cost = 1
route = PATH_SIDE
+ research_tree_icon_path = 'icons/obj/signs.dmi'
+ research_tree_icon_state = "eldritch_painting_weeping"
+ depth = 8
/datum/heretic_knowledge/painting/recipe_snowflake_check(mob/living/user, list/atoms, list/selected_atoms, turf/loc)
if(locate(/obj/item/organ/internal/eyes) in atoms)
diff --git a/code/modules/antagonists/heretic/knowledge/side_rust_cosmos.dm b/code/modules/antagonists/heretic/knowledge/side_rust_cosmos.dm
index 869a69926c8a2..331ff58696e12 100644
--- a/code/modules/antagonists/heretic/knowledge/side_rust_cosmos.dm
+++ b/code/modules/antagonists/heretic/knowledge/side_rust_cosmos.dm
@@ -17,6 +17,9 @@
result_atoms = list(/obj/item/reagent_containers/cup/beaker/eldritch)
cost = 1
route = PATH_SIDE
+ depth = 4
+ research_tree_icon_path = 'icons/obj/antags/eldritch.dmi'
+ research_tree_icon_state = "eldritch_flask"
/datum/heretic_knowledge/entropy_pulse
name = "Pulse of Entropy"
@@ -28,6 +31,10 @@
)
cost = 0
route = PATH_SIDE
+ research_tree_icon_path = 'icons/mob/actions/actions_ecult.dmi'
+ research_tree_icon_state = "corrode"
+ research_tree_icon_frame = 10
+ depth = 4
var/rusting_range = 8
/datum/heretic_knowledge/entropy_pulse/on_finished_recipe(mob/living/user, list/selected_atoms, turf/loc)
@@ -60,6 +67,9 @@
curse_color = "#c1ffc9"
cost = 1
route = PATH_SIDE
+ research_tree_icon_path = 'icons/ui_icons/antags/heretic/knowledge.dmi'
+ research_tree_icon_state = "curse_corrosion"
+ depth = 8
/datum/heretic_knowledge/curse/corrosion/curse(mob/living/carbon/human/chosen_mob, boosted = FALSE)
to_chat(chosen_mob, span_danger("Вы чувствуете себя очень плохо..."))
@@ -92,6 +102,7 @@
cost = 1
route = PATH_SIDE
poll_ignore_define = POLL_IGNORE_RUST_SPIRIT
+ depth = 8
/datum/heretic_knowledge/summon/rusty/cleanup_atoms(list/selected_atoms)
var/obj/item/bodypart/head/ritual_head = locate() in selected_atoms
diff --git a/code/modules/antagonists/heretic/knowledge/side_void_blade.dm b/code/modules/antagonists/heretic/knowledge/side_void_blade.dm
index f119c5d11b10c..dcd30cffc0a2e 100644
--- a/code/modules/antagonists/heretic/knowledge/side_void_blade.dm
+++ b/code/modules/antagonists/heretic/knowledge/side_void_blade.dm
@@ -23,6 +23,9 @@
limit = 1
cost = 1
route = PATH_SIDE
+ research_tree_icon_path = 'icons/ui_icons/antags/heretic/knowledge.dmi'
+ research_tree_icon_state = "ghoul_shattered"
+ depth = 4
/datum/heretic_knowledge/limited_amount/risen_corpse/recipe_snowflake_check(mob/living/user, list/atoms, list/selected_atoms, turf/loc)
. = ..()
@@ -139,6 +142,9 @@
result_atoms = list(/obj/item/melee/rune_carver)
cost = 1
route = PATH_SIDE
+ depth = 8
+ research_tree_icon_path = 'icons/obj/antags/eldritch.dmi'
+ research_tree_icon_state = "rune_carver"
/datum/heretic_knowledge/summon/maid_in_mirror
name = "Maid in the Mirror"
@@ -162,3 +168,4 @@
route = PATH_SIDE
mob_to_summon = /mob/living/basic/heretic_summon/maid_in_the_mirror
poll_ignore_define = POLL_IGNORE_MAID_IN_MIRROR
+ depth = 10
diff --git a/code/modules/antagonists/heretic/knowledge/starting_lore.dm b/code/modules/antagonists/heretic/knowledge/starting_lore.dm
index 351db763eac20..af7bc1c98ae56 100644
--- a/code/modules/antagonists/heretic/knowledge/starting_lore.dm
+++ b/code/modules/antagonists/heretic/knowledge/starting_lore.dm
@@ -1,4 +1,5 @@
// Heretic starting knowledge.
+// Default heretic language is Ancient Greek, because, uh, they're like ancient and shit.
/// Global list of all heretic knowledge that have route = PATH_START. List of PATHS.
GLOBAL_LIST_INIT(heretic_start_knowledge, initialize_starting_knowledge())
@@ -48,6 +49,9 @@ GLOBAL_LIST_INIT(heretic_start_knowledge, initialize_starting_knowledge())
cost = 0
priority = MAX_KNOWLEDGE_PRIORITY - 1 // Knowing how to remake your heart is important
route = PATH_START
+ research_tree_icon_path = 'icons/obj/antags/eldritch.dmi'
+ research_tree_icon_state = "living_heart"
+ research_tree_icon_frame = 1
/// The typepath of the organ type required for our heart.
var/required_organ_type = /obj/item/organ/internal/heart
@@ -204,6 +208,8 @@ GLOBAL_LIST_INIT(heretic_start_knowledge, initialize_starting_knowledge())
cost = 0
priority = MAX_KNOWLEDGE_PRIORITY - 2 // Not as important as making a heart or sacrificing, but important enough.
route = PATH_START
+ research_tree_icon_path = 'icons/obj/clothing/neck.dmi'
+ research_tree_icon_state = "eldritch_necklace"
/datum/heretic_knowledge/spell/cloak_of_shadows
name = "Cloak of Shadow"
@@ -238,6 +244,8 @@ GLOBAL_LIST_INIT(heretic_start_knowledge, initialize_starting_knowledge())
route = PATH_START
priority = MAX_KNOWLEDGE_PRIORITY - 3 // Least priority out of the starting knowledges, as it's an optional boon.
var/static/list/non_mob_bindings = typecacheof(list(/obj/item/stack/sheet/leather, /obj/item/stack/sheet/animalhide))
+ research_tree_icon_path = 'icons/obj/antags/eldritch.dmi'
+ research_tree_icon_state = "book"
/datum/heretic_knowledge/codex_cicatrix/parse_required_item(atom/item_path, number_of_things)
if(item_path == /obj/item/pen)
@@ -300,21 +308,30 @@ GLOBAL_LIST_INIT(heretic_start_knowledge, initialize_starting_knowledge())
gain_text = "Under the soft glow of unreason there is a beast that stalks the night. I shall bring it forth and let it enter my presence. It will feast upon my amibitions and leave knowledge in its wake."
route = PATH_START
required_atoms = list()
+ research_tree_icon_path = 'icons/mob/actions/actions_animal.dmi'
+ research_tree_icon_state = "god_transmit"
/datum/heretic_knowledge/feast_of_owls/can_be_invoked(datum/antagonist/heretic/invoker)
return !invoker.feast_of_owls
/datum/heretic_knowledge/feast_of_owls/on_finished_recipe(mob/living/user, list/selected_atoms, turf/loc)
+ //amount of research points granted
+ var/reward = 5
var/alert = tgui_alert(user,"Do you really want to forsake your ascension? This action cannot be reverted.", "Feast of Owls", list("Yes I'm sure", "No"), 30 SECONDS)
if( alert != "Yes I'm sure")
return FALSE
- user.set_temp_blindness(5 SECONDS)
- user.AdjustParalyzed(5 SECONDS)
+ user.set_temp_blindness(reward SECONDS)
+ user.AdjustParalyzed(reward SECONDS)
+ user.playsound_local(get_turf(user), 'sound/ambience/antag/heretic/heretic_gain_intense.ogg', 100, FALSE, pressure_affected = FALSE, use_reverb = FALSE)
var/datum/antagonist/heretic/heretic_datum = IS_HERETIC(user)
- for(var/i in 0 to 4)
+ for(var/i in 1 to reward)
user.emote("scream")
playsound(loc, 'sound/items/eatfood.ogg', 100, TRUE)
heretic_datum.knowledge_points++
+ to_chat(user, span_danger("You feel something invisible tearing away at your very essence!"))
+ user.do_jitter_animation()
sleep(1 SECONDS)
- to_chat(user,span_danger("You feel different..."))
heretic_datum.feast_of_owls = TRUE
+ to_chat(user, span_danger(span_big("Your ambition is ravaged, but something powerful remains in its wake...")))
+ var/drain_message = pick(strings(HERETIC_INFLUENCE_FILE, "drain_message"))
+ to_chat(user, span_hypnophrase(span_big("[drain_message]")))
diff --git a/code/modules/antagonists/heretic/knowledge/void_lore.dm b/code/modules/antagonists/heretic/knowledge/void_lore.dm
index 56134db680163..0d8dbbec36f3d 100644
--- a/code/modules/antagonists/heretic/knowledge/void_lore.dm
+++ b/code/modules/antagonists/heretic/knowledge/void_lore.dm
@@ -1,5 +1,7 @@
/**
* # The path of VOID.
+ * Spell names are in this language: PALI
+ * Both are related: Pali-Buddhism-Nothingness-Void
*
* Goes as follows:
*
@@ -37,6 +39,8 @@
required_atoms = list(/obj/item/knife = 1)
result_atoms = list(/obj/item/melee/sickly_blade/void)
route = PATH_VOID
+ research_tree_icon_path = 'icons/obj/weapons/khopesh.dmi'
+ research_tree_icon_state = "void_blade"
/datum/heretic_knowledge/limited_amount/starting/base_void/recipe_snowflake_check(mob/living/user, list/atoms, list/selected_atoms, turf/loc)
if(!isopenturf(loc))
@@ -58,6 +62,9 @@
next_knowledge = list(/datum/heretic_knowledge/cold_snap)
cost = 1
route = PATH_VOID
+ depth = 3
+ research_tree_icon_path = 'icons/ui_icons/antags/heretic/knowledge.dmi'
+ research_tree_icon_state = "grasp_void"
/datum/heretic_knowledge/void_grasp/on_gain(mob/user, datum/antagonist/heretic/our_heretic)
RegisterSignal(user, COMSIG_HERETIC_MANSUS_GRASP_ATTACK, PROC_REF(on_mansus_grasp))
@@ -88,6 +95,9 @@
)
cost = 1
route = PATH_VOID
+ research_tree_icon_path = 'icons/effects/effects.dmi'
+ research_tree_icon_state = "the_freezer"
+ depth = 4
/datum/heretic_knowledge/cold_snap/on_gain(mob/user, datum/antagonist/heretic/our_heretic)
user.add_traits(list(TRAIT_NOBREATH, TRAIT_RESISTCOLD), type)
@@ -119,6 +129,7 @@
spell_to_add = /datum/action/cooldown/spell/cone/staggered/cone_of_cold/void
cost = 1
route = PATH_VOID
+ depth = 7
/datum/heretic_knowledge/spell/void_phase
name = "Void Phase"
@@ -135,6 +146,8 @@
spell_to_add = /datum/action/cooldown/spell/pointed/void_phase
cost = 1
route = PATH_VOID
+ depth = 8
+ research_tree_icon_frame = 7
/datum/heretic_knowledge/blade_upgrade/void
name = "Seeking Blade"
@@ -142,6 +155,8 @@
gain_text = "Мимолетные воспоминания, мимолетные ноги. Я отмечаю свой путь застывшей кровью на снегу. Покрытый и забытый."
next_knowledge = list(/datum/heretic_knowledge/spell/void_pull)
route = PATH_VOID
+ research_tree_icon_path = 'icons/ui_icons/antags/heretic/knowledge.dmi'
+ research_tree_icon_state = "blade_upgrade_void"
/datum/heretic_knowledge/blade_upgrade/void/do_ranged_effects(mob/living/user, mob/living/target, obj/item/melee/sickly_blade/blade)
if(!target.has_status_effect(/datum/status_effect/eldritch))
@@ -168,6 +183,8 @@
spell_to_add = /datum/action/cooldown/spell/aoe/void_pull
cost = 1
route = PATH_VOID
+ depth = 10
+ research_tree_icon_frame = 6
/datum/heretic_knowledge/ultimate/void_final
name = "Waltz at the End of Time"
@@ -180,6 +197,7 @@
Аристократ стоит передо мной, призывая. Мы будем играть вальс под шепот умирающей реальности, \
пока мир разрушается на наших глазах. Пустота вернет все в ничто, УЗРИТЕ МОЕ ВОЗНЕСЕНИЕ!"
route = PATH_VOID
+ ascension_achievement = /datum/award/achievement/misc/void_ascension
///soundloop for the void theme
var/datum/looping_sound/void_loop/sound_loop
///Reference to the ongoing voidstrom that surrounds the heretic
@@ -203,9 +221,8 @@
text = "[generate_heretic_text()] Дворянин пустоты [user.real_name] прибыл, танцуя Вальс, уничтожающий миры! [generate_heretic_text()]",
title = "[generate_heretic_text()]",
sound = 'sound/ambience/antag/heretic/ascend_void.ogg',
- color_override = "pink",
+ color_override = "blue",
)
- user.client?.give_award(/datum/award/achievement/misc/void_ascension, user)
ADD_TRAIT(user, TRAIT_RESISTLOWPRESSURE, MAGIC_TRAIT)
// Let's get this show on the road!
diff --git a/code/modules/antagonists/heretic/magic/aggressive_spread.dm b/code/modules/antagonists/heretic/magic/aggressive_spread.dm
index bd77b7804dba5..c7eb726b459d4 100644
--- a/code/modules/antagonists/heretic/magic/aggressive_spread.dm
+++ b/code/modules/antagonists/heretic/magic/aggressive_spread.dm
@@ -10,8 +10,8 @@
school = SCHOOL_FORBIDDEN
cooldown_time = 30 SECONDS
- invocation = "A'GRSV SPR'D"
- invocation_type = INVOCATION_WHISPER
+ invocation = "Agresiv'noe rasprostra-neniye!"
+ invocation_type = INVOCATION_SHOUT
spell_requirements = NONE
aoe_radius = 2
diff --git a/code/modules/antagonists/heretic/magic/apetravulnera.dm b/code/modules/antagonists/heretic/magic/apetravulnera.dm
index b64d906c53332..8a8f2f931d85d 100644
--- a/code/modules/antagonists/heretic/magic/apetravulnera.dm
+++ b/code/modules/antagonists/heretic/magic/apetravulnera.dm
@@ -10,8 +10,8 @@
school = SCHOOL_FORBIDDEN
cooldown_time = 45 SECONDS
- invocation = "AP'TRA VULN'RA!"
- invocation_type = INVOCATION_WHISPER
+ invocation = "Shea' shen-eh!"
+ invocation_type = INVOCATION_SHOUT
spell_requirements = NONE
cast_range = 4
diff --git a/code/modules/antagonists/heretic/magic/ash_ascension.dm b/code/modules/antagonists/heretic/magic/ash_ascension.dm
index 0a71f025ebbd6..bc7b67a0ce121 100644
--- a/code/modules/antagonists/heretic/magic/ash_ascension.dm
+++ b/code/modules/antagonists/heretic/magic/ash_ascension.dm
@@ -10,7 +10,7 @@
school = SCHOOL_FORBIDDEN
cooldown_time = 70 SECONDS
- invocation = "FL'MS"
+ invocation = "EID'R-ELDR!!!"
invocation_type = INVOCATION_WHISPER
spell_requirements = NONE
@@ -72,8 +72,8 @@
school = SCHOOL_FORBIDDEN
cooldown_time = 30 SECONDS
- invocation = "C'SC'DE"
- invocation_type = INVOCATION_WHISPER
+ invocation = "ILLA-LASARA'FOSS!!!"
+ invocation_type = INVOCATION_SHOUT
spell_requirements = NONE
/// The radius the flames will go around the caster.
@@ -112,7 +112,7 @@
school = SCHOOL_FORBIDDEN
cooldown_time = 300
- invocation = "F'RE"
+ invocation = "Eld'sky!"
invocation_type = INVOCATION_WHISPER
spell_requirements = NONE
diff --git a/code/modules/antagonists/heretic/magic/ash_jaunt.dm b/code/modules/antagonists/heretic/magic/ash_jaunt.dm
index b831665a84bae..7e6e03d392e66 100644
--- a/code/modules/antagonists/heretic/magic/ash_jaunt.dm
+++ b/code/modules/antagonists/heretic/magic/ash_jaunt.dm
@@ -10,7 +10,7 @@
school = SCHOOL_FORBIDDEN
cooldown_time = 15 SECONDS
- invocation = "ASH'N P'SSG'"
+ invocation = "Askgraar' goetur!"
invocation_type = INVOCATION_WHISPER
spell_requirements = NONE
diff --git a/code/modules/antagonists/heretic/magic/blood_cleave.dm b/code/modules/antagonists/heretic/magic/blood_cleave.dm
index 41c415843b791..672dd23a98f71 100644
--- a/code/modules/antagonists/heretic/magic/blood_cleave.dm
+++ b/code/modules/antagonists/heretic/magic/blood_cleave.dm
@@ -10,7 +10,7 @@
school = SCHOOL_FORBIDDEN
cooldown_time = 45 SECONDS
- invocation = "CL'VE!"
+ invocation = "Fer're!"
invocation_type = INVOCATION_WHISPER
spell_requirements = NONE
diff --git a/code/modules/antagonists/heretic/magic/blood_siphon.dm b/code/modules/antagonists/heretic/magic/blood_siphon.dm
index 7b269730ec05f..ef0f485815a06 100644
--- a/code/modules/antagonists/heretic/magic/blood_siphon.dm
+++ b/code/modules/antagonists/heretic/magic/blood_siphon.dm
@@ -11,7 +11,7 @@
school = SCHOOL_FORBIDDEN
cooldown_time = 15 SECONDS
- invocation = "FL'MS O'ET'RN'ITY."
+ invocation = "Sanguis suctio!"
invocation_type = INVOCATION_WHISPER
spell_requirements = NONE
diff --git a/code/modules/antagonists/heretic/magic/burglar_finesse.dm b/code/modules/antagonists/heretic/magic/burglar_finesse.dm
index 74c753dc3b9a7..93bd5e354623e 100644
--- a/code/modules/antagonists/heretic/magic/burglar_finesse.dm
+++ b/code/modules/antagonists/heretic/magic/burglar_finesse.dm
@@ -9,7 +9,7 @@
school = SCHOOL_FORBIDDEN
cooldown_time = 40 SECONDS
- invocation = "Y'O'K!"
+ invocation = "Khenem"
invocation_type = INVOCATION_WHISPER
spell_requirements = NONE
diff --git a/code/modules/antagonists/heretic/magic/cosmic_expansion.dm b/code/modules/antagonists/heretic/magic/cosmic_expansion.dm
index f025e40ce6743..cb3a8db081c03 100644
--- a/code/modules/antagonists/heretic/magic/cosmic_expansion.dm
+++ b/code/modules/antagonists/heretic/magic/cosmic_expansion.dm
@@ -11,7 +11,7 @@
school = SCHOOL_FORBIDDEN
cooldown_time = 45 SECONDS
- invocation = "C'SM'S 'XP'ND"
+ invocation = "An'gar baltil!"
invocation_type = INVOCATION_SHOUT
spell_requirements = NONE
diff --git a/code/modules/antagonists/heretic/magic/cosmic_runes.dm b/code/modules/antagonists/heretic/magic/cosmic_runes.dm
index bf1d64c2ed13d..513d7d43af346 100644
--- a/code/modules/antagonists/heretic/magic/cosmic_runes.dm
+++ b/code/modules/antagonists/heretic/magic/cosmic_runes.dm
@@ -11,7 +11,7 @@
school = SCHOOL_FORBIDDEN
cooldown_time = 15 SECONDS
- invocation = "ST'R R'N'"
+ invocation = "Is'zara-runen"
invocation_type = INVOCATION_WHISPER
spell_requirements = NONE
diff --git a/code/modules/antagonists/heretic/magic/eldritch_blind.dm b/code/modules/antagonists/heretic/magic/eldritch_blind.dm
index 8df20503821b0..413ff4fe67810 100644
--- a/code/modules/antagonists/heretic/magic/eldritch_blind.dm
+++ b/code/modules/antagonists/heretic/magic/eldritch_blind.dm
@@ -5,7 +5,7 @@
overlay_icon_state = "bg_heretic_border"
school = SCHOOL_FORBIDDEN
- invocation = "E'E'S"
+ invocation = "Caecus"
spell_requirements = NONE
cast_range = 10
diff --git a/code/modules/antagonists/heretic/magic/eldritch_emplosion.dm b/code/modules/antagonists/heretic/magic/eldritch_emplosion.dm
index b2f7a9886e49f..931642f96b1a9 100644
--- a/code/modules/antagonists/heretic/magic/eldritch_emplosion.dm
+++ b/code/modules/antagonists/heretic/magic/eldritch_emplosion.dm
@@ -8,7 +8,7 @@
school = SCHOOL_FORBIDDEN
cooldown_time = 30 SECONDS
- invocation = "E'P"
+ invocation = "Pulsus Energiae"
invocation_type = INVOCATION_WHISPER
spell_requirements = NONE
diff --git a/code/modules/antagonists/heretic/magic/eldritch_shapeshift.dm b/code/modules/antagonists/heretic/magic/eldritch_shapeshift.dm
index f17e10458122d..a3fbd64728869 100644
--- a/code/modules/antagonists/heretic/magic/eldritch_shapeshift.dm
+++ b/code/modules/antagonists/heretic/magic/eldritch_shapeshift.dm
@@ -7,7 +7,7 @@
overlay_icon_state = "bg_heretic_border"
school = SCHOOL_FORBIDDEN
- invocation = "SH'PE"
+ invocation = "Forma"
invocation_type = INVOCATION_WHISPER
spell_requirements = NONE
diff --git a/code/modules/antagonists/heretic/magic/fire_blast.dm b/code/modules/antagonists/heretic/magic/fire_blast.dm
index 3c57b8f6de5b8..98fe5bbdb3ea6 100644
--- a/code/modules/antagonists/heretic/magic/fire_blast.dm
+++ b/code/modules/antagonists/heretic/magic/fire_blast.dm
@@ -12,7 +12,7 @@
school = SCHOOL_FORBIDDEN
cooldown_time = 45 SECONDS
- invocation = "V'LC'N!"
+ invocation = "Eld'fjall!"
invocation_type = INVOCATION_SHOUT
spell_requirements = NONE
channel_time = 5 SECONDS
diff --git a/code/modules/antagonists/heretic/magic/flesh_ascension.dm b/code/modules/antagonists/heretic/magic/flesh_ascension.dm
index e287738fa2839..08e2f144066e9 100644
--- a/code/modules/antagonists/heretic/magic/flesh_ascension.dm
+++ b/code/modules/antagonists/heretic/magic/flesh_ascension.dm
@@ -9,7 +9,7 @@
school = SCHOOL_FORBIDDEN
- invocation = "РЕАЛЬНОСТЬ РАЗРУШЬСЯ!"
+ invocation = "REALITAS EXSERPAT!!"
invocation_type = INVOCATION_SHOUT
spell_requirements = NONE
diff --git a/code/modules/antagonists/heretic/magic/flesh_surgery.dm b/code/modules/antagonists/heretic/magic/flesh_surgery.dm
index 26ad0a7886585..9b4c0f5279dd0 100644
--- a/code/modules/antagonists/heretic/magic/flesh_surgery.dm
+++ b/code/modules/antagonists/heretic/magic/flesh_surgery.dm
@@ -11,8 +11,8 @@
school = SCHOOL_FORBIDDEN
cooldown_time = 20 SECONDS
- invocation = "CL'M M'N!" // "CLAIM MINE", but also almost "KALI MA"
- invocation_type = INVOCATION_SHOUT
+ invocation = "Carnis chirurgia"
+ invocation_type = INVOCATION_WHISPER
spell_requirements = NONE
hand_path = /obj/item/melee/touch_attack/flesh_surgery
diff --git a/code/modules/antagonists/heretic/magic/furious_steel.dm b/code/modules/antagonists/heretic/magic/furious_steel.dm
index 31af20d9126e9..64b15d65f9570 100644
--- a/code/modules/antagonists/heretic/magic/furious_steel.dm
+++ b/code/modules/antagonists/heretic/magic/furious_steel.dm
@@ -11,7 +11,7 @@
school = SCHOOL_FORBIDDEN
cooldown_time = 60 SECONDS
- invocation = "F'LSH'NG S'LV'R!"
+ invocation = "Ham'sana-qasep!"
invocation_type = INVOCATION_SHOUT
spell_requirements = NONE
diff --git a/code/modules/antagonists/heretic/magic/manse_link.dm b/code/modules/antagonists/heretic/magic/manse_link.dm
index d9fe06f3fb4d0..fddb253dbbb42 100644
--- a/code/modules/antagonists/heretic/magic/manse_link.dm
+++ b/code/modules/antagonists/heretic/magic/manse_link.dm
@@ -11,7 +11,7 @@
school = SCHOOL_FORBIDDEN
cooldown_time = 20 SECONDS
- invocation = "PI'RC' TH' M'ND."
+ invocation = "Diaperaste' to-myalo!"
invocation_type = INVOCATION_SHOUT
spell_requirements = NONE
antimagic_flags = MAGIC_RESISTANCE|MAGIC_RESISTANCE_MIND
diff --git a/code/modules/antagonists/heretic/magic/mansus_grasp.dm b/code/modules/antagonists/heretic/magic/mansus_grasp.dm
index 0105eee1de668..c464506cb3314 100644
--- a/code/modules/antagonists/heretic/magic/mansus_grasp.dm
+++ b/code/modules/antagonists/heretic/magic/mansus_grasp.dm
@@ -10,7 +10,7 @@
school = SCHOOL_EVOCATION
cooldown_time = 10 SECONDS
- invocation = "R'CH T'H TR'TH!"
+ invocation = "Ad verum per aspera!"
invocation_type = INVOCATION_SHOUT
// Mimes can cast it. Chaplains can cast it. Anyone can cast it, so long as they have a hand.
spell_requirements = SPELL_CASTABLE_WITHOUT_INVOCATION
@@ -66,6 +66,7 @@
carbon_hit.adjust_timed_status_effect(4 SECONDS, /datum/status_effect/speech/slurring/heretic)
carbon_hit.AdjustKnockdown(5 SECONDS)
carbon_hit.adjustStaminaLoss(80)
+ carbon_hit.apply_status_effect(/datum/status_effect/next_shove_stuns)
return TRUE
diff --git a/code/modules/antagonists/heretic/magic/mind_gate.dm b/code/modules/antagonists/heretic/magic/mind_gate.dm
index 2db7c989ff5d5..205e54d779bbe 100644
--- a/code/modules/antagonists/heretic/magic/mind_gate.dm
+++ b/code/modules/antagonists/heretic/magic/mind_gate.dm
@@ -11,7 +11,7 @@
school = SCHOOL_FORBIDDEN
cooldown_time = 20 SECONDS
- invocation = "Op' 'oY 'Mi'd"
+ invocation = "Sha'ar ha-da'at"
invocation_type = INVOCATION_WHISPER
spell_requirements = NONE
cast_range = 6
diff --git a/code/modules/antagonists/heretic/magic/moon_parade.dm b/code/modules/antagonists/heretic/magic/moon_parade.dm
index b645f65bd4880..e0247bf9f47f0 100644
--- a/code/modules/antagonists/heretic/magic/moon_parade.dm
+++ b/code/modules/antagonists/heretic/magic/moon_parade.dm
@@ -11,7 +11,7 @@
school = SCHOOL_FORBIDDEN
cooldown_time = 30 SECONDS
- invocation = "L'N'R P'RAD"
+ invocation = "Tsiyun' levani!"
invocation_type = INVOCATION_SHOUT
spell_requirements = NONE
diff --git a/code/modules/antagonists/heretic/magic/moon_ringleader.dm b/code/modules/antagonists/heretic/magic/moon_ringleader.dm
index 7697a8774f132..77c75ff0c9e80 100644
--- a/code/modules/antagonists/heretic/magic/moon_ringleader.dm
+++ b/code/modules/antagonists/heretic/magic/moon_ringleader.dm
@@ -12,7 +12,7 @@
school = SCHOOL_FORBIDDEN
cooldown_time = 1 MINUTES
- invocation = "R''S 'E"
+ invocation = "Manahel-qomem!"
invocation_type = INVOCATION_SHOUT
spell_requirements = NONE
diff --git a/code/modules/antagonists/heretic/magic/moon_smile.dm b/code/modules/antagonists/heretic/magic/moon_smile.dm
index e980612c367f9..4db700e7e478b 100644
--- a/code/modules/antagonists/heretic/magic/moon_smile.dm
+++ b/code/modules/antagonists/heretic/magic/moon_smile.dm
@@ -12,7 +12,7 @@
school = SCHOOL_FORBIDDEN
cooldown_time = 20 SECONDS
- invocation = "Mo'N S'M'LE"
+ invocation = "Hiyuk-levana!"
invocation_type = INVOCATION_SHOUT
spell_requirements = NONE
cast_range = 6
diff --git a/code/modules/antagonists/heretic/magic/nightwatcher_rebirth.dm b/code/modules/antagonists/heretic/magic/nightwatcher_rebirth.dm
index a4df7d1da68a0..bac0fde3606d3 100644
--- a/code/modules/antagonists/heretic/magic/nightwatcher_rebirth.dm
+++ b/code/modules/antagonists/heretic/magic/nightwatcher_rebirth.dm
@@ -11,7 +11,7 @@
school = SCHOOL_FORBIDDEN
cooldown_time = 1 MINUTES
- invocation = "GL'RY T' TH' N'GHT'W'TCH'ER"
+ invocation = "Dyrth-a Vaktry'ggjandi"
invocation_type = INVOCATION_WHISPER
spell_requirements = SPELL_REQUIRES_HUMAN
diff --git a/code/modules/antagonists/heretic/magic/realignment.dm b/code/modules/antagonists/heretic/magic/realignment.dm
index 5c02d8ac312ce..5afabd2c8b7c3 100644
--- a/code/modules/antagonists/heretic/magic/realignment.dm
+++ b/code/modules/antagonists/heretic/magic/realignment.dm
@@ -14,8 +14,8 @@
cooldown_reduction_per_rank = -6 SECONDS // we're not a wizard spell but we use the levelling mechanic
spell_max_level = 10 // we can get up to / over a minute duration cd time
- invocation = "R'S'T."
- invocation_type = INVOCATION_SHOUT
+ invocation = "Rasut"
+ invocation_type = INVOCATION_WHISPER
spell_requirements = NONE
/datum/action/cooldown/spell/realignment/is_valid_target(atom/cast_on)
diff --git a/code/modules/antagonists/heretic/magic/rust_wave.dm b/code/modules/antagonists/heretic/magic/rust_wave.dm
index 2a316e98d9730..714244f645f8d 100644
--- a/code/modules/antagonists/heretic/magic/rust_wave.dm
+++ b/code/modules/antagonists/heretic/magic/rust_wave.dm
@@ -13,8 +13,8 @@
school = SCHOOL_FORBIDDEN
cooldown_time = 30 SECONDS
- invocation = "'NTR'P'C PL'M'"
- invocation_type = INVOCATION_WHISPER
+ invocation = "Entro'pichniy-plim!"
+ invocation_type = INVOCATION_SHOUT
spell_requirements = NONE
cone_levels = 5
@@ -78,8 +78,8 @@
school = SCHOOL_FORBIDDEN
cooldown_time = 35 SECONDS
- invocation = "SPR'D TH' WO'D"
- invocation_type = INVOCATION_WHISPER
+ invocation = "Diffunde' verbum!"
+ invocation_type = INVOCATION_SHOUT
spell_requirements = NONE
projectile_type = /obj/projectile/magic/aoe/rust_wave
diff --git a/code/modules/antagonists/heretic/magic/star_blast.dm b/code/modules/antagonists/heretic/magic/star_blast.dm
index 607ce3f52d84e..b98cdfc842cd7 100644
--- a/code/modules/antagonists/heretic/magic/star_blast.dm
+++ b/code/modules/antagonists/heretic/magic/star_blast.dm
@@ -10,7 +10,7 @@
school = SCHOOL_FORBIDDEN
cooldown_time = 20 SECONDS
- invocation = "R'T'T' ST'R!"
+ invocation = "Pi-rig is'zara!"
invocation_type = INVOCATION_SHOUT
spell_requirements = NONE
diff --git a/code/modules/antagonists/heretic/magic/star_touch.dm b/code/modules/antagonists/heretic/magic/star_touch.dm
index 1558c8a622975..58d52cbaf48c6 100644
--- a/code/modules/antagonists/heretic/magic/star_touch.dm
+++ b/code/modules/antagonists/heretic/magic/star_touch.dm
@@ -13,7 +13,7 @@
sound = 'sound/items/welder.ogg'
school = SCHOOL_FORBIDDEN
cooldown_time = 15 SECONDS
- invocation = "ST'R 'N'RG'!"
+ invocation = "An'gar sig!"
invocation_type = INVOCATION_SHOUT
spell_requirements = NONE
antimagic_flags = MAGIC_RESISTANCE
diff --git a/code/modules/antagonists/heretic/magic/void_cold_cone.dm b/code/modules/antagonists/heretic/magic/void_cold_cone.dm
index d29f29d3d35f4..0daf3577f1aee 100644
--- a/code/modules/antagonists/heretic/magic/void_cold_cone.dm
+++ b/code/modules/antagonists/heretic/magic/void_cold_cone.dm
@@ -11,7 +11,7 @@
school = SCHOOL_FORBIDDEN
cooldown_time = 30 SECONDS
- invocation = "FR'ZE!"
+ invocation = "Sunya'kop!"
invocation_type = INVOCATION_SHOUT
spell_requirements = NONE
diff --git a/code/modules/antagonists/heretic/magic/void_phase.dm b/code/modules/antagonists/heretic/magic/void_phase.dm
index 7588c82ceb925..39059d5d712b1 100644
--- a/code/modules/antagonists/heretic/magic/void_phase.dm
+++ b/code/modules/antagonists/heretic/magic/void_phase.dm
@@ -12,8 +12,8 @@
school = SCHOOL_FORBIDDEN
cooldown_time = 30 SECONDS
- invocation = "RE'L'TY PH'S'E."
- invocation_type = INVOCATION_WHISPER
+ invocation = "Sunya'sthiti!"
+ invocation_type = INVOCATION_SHOUT
spell_requirements = NONE
cast_range = 9
diff --git a/code/modules/antagonists/heretic/magic/void_pull.dm b/code/modules/antagonists/heretic/magic/void_pull.dm
index 233e78e1560f5..3d4409e8d49ee 100644
--- a/code/modules/antagonists/heretic/magic/void_pull.dm
+++ b/code/modules/antagonists/heretic/magic/void_pull.dm
@@ -11,8 +11,8 @@
school = SCHOOL_FORBIDDEN
cooldown_time = 40 SECONDS
- invocation = "BR'NG F'RTH TH'M T' M'."
- invocation_type = INVOCATION_WHISPER
+ invocation = "Sunya'apamkti!"
+ invocation_type = INVOCATION_SHOUT
spell_requirements = NONE
aoe_radius = 7
diff --git a/code/modules/antagonists/heretic/magic/wave_of_desperation.dm b/code/modules/antagonists/heretic/magic/wave_of_desperation.dm
index bfe0fc1d203e4..87caca7496fd2 100644
--- a/code/modules/antagonists/heretic/magic/wave_of_desperation.dm
+++ b/code/modules/antagonists/heretic/magic/wave_of_desperation.dm
@@ -11,8 +11,8 @@
school = SCHOOL_FORBIDDEN
cooldown_time = 5 MINUTES
- invocation = "F'K 'FF."
- invocation_type = INVOCATION_WHISPER
+ invocation = "Kher' Sekh-em waaef'k!"
+ invocation_type = INVOCATION_SHOUT
spell_requirements = NONE
aoe_radius = 3
diff --git a/code/modules/antagonists/heretic/structures/carving_knife.dm b/code/modules/antagonists/heretic/structures/carving_knife.dm
index 2d21e3858acc1..a0525c7489226 100644
--- a/code/modules/antagonists/heretic/structures/carving_knife.dm
+++ b/code/modules/antagonists/heretic/structures/carving_knife.dm
@@ -15,15 +15,7 @@
attack_verb_continuous = list("attacks", "slashes", "stabs", "slices", "tears", "lacerates", "rips", "dices", "rends")
attack_verb_simple = list("attack", "slash", "stab", "slice", "tear", "lacerate", "rip", "dice", "rend")
actions_types = list(/datum/action/item_action/rune_shatter)
- embedding = list(
- ignore_throwspeed_threshold = TRUE,
- embed_chance = 75,
- jostle_chance = 2,
- jostle_pain_mult = 5,
- pain_stam_pct = 0.4,
- pain_mult = 3,
- rip_time = 15,
- )
+ embed_type = /datum/embed_data/rune_carver
/// Whether we're currently drawing a rune
var/drawing = FALSE
@@ -34,6 +26,15 @@
/// Turfs that you cannot draw carvings on
var/static/list/blacklisted_turfs = typecacheof(list(/turf/open/space, /turf/open/openspace, /turf/open/lava))
+/datum/embed_data/rune_carver
+ ignore_throwspeed_threshold = TRUE
+ embed_chance = 75
+ jostle_chance = 2
+ jostle_pain_mult = 5
+ pain_stam_pct = 0.4
+ pain_mult = 3
+ rip_time = 15
+
/obj/item/melee/rune_carver/examine(mob/user)
. = ..()
if(!IS_HERETIC_OR_MONSTER(user) && !isobserver(user))
diff --git a/code/modules/antagonists/hypnotized/hypnotized.dm b/code/modules/antagonists/hypnotized/hypnotized.dm
index 4f1f49aa3be7c..fc1e5d7d5ad24 100644
--- a/code/modules/antagonists/hypnotized/hypnotized.dm
+++ b/code/modules/antagonists/hypnotized/hypnotized.dm
@@ -1,6 +1,7 @@
/// Antag datum associated with the hypnosis brain trauma, used for displaying objectives and antag hud
/datum/antagonist/hypnotized
name = "\improper Hypnotized Victim"
+ stinger_sound = 'sound/ambience/antag/hypnotized.ogg'
job_rank = ROLE_HYPNOTIZED
roundend_category = "hypnotized victims"
antag_hud_name = "brainwashed"
@@ -10,7 +11,6 @@
show_name_in_check_antagonists = TRUE
count_against_dynamic_roll_chance = FALSE
- silent = TRUE //not actually silent, because greet will be called by the trauma anyway.
/// Brain trauma associated with this antag datum
var/datum/brain_trauma/hypnosis/trauma
diff --git a/code/modules/antagonists/ninja/outfit.dm b/code/modules/antagonists/ninja/outfit.dm
index f91f660a3e8b9..924943dce777b 100644
--- a/code/modules/antagonists/ninja/outfit.dm
+++ b/code/modules/antagonists/ninja/outfit.dm
@@ -1,7 +1,7 @@
/datum/outfit/ninja
name = "Space Ninja"
uniform = /obj/item/clothing/under/syndicate/ninja
- glasses = /obj/item/clothing/glasses/night
+ glasses = /obj/item/clothing/glasses/night/colorless
mask = /obj/item/clothing/mask/gas/ninja
ears = /obj/item/radio/headset
shoes = /obj/item/clothing/shoes/jackboots
diff --git a/code/modules/antagonists/nukeop/datums/operative_lone.dm b/code/modules/antagonists/nukeop/datums/operative_lone.dm
index d0bc718a781b0..3993c2d49a7f6 100644
--- a/code/modules/antagonists/nukeop/datums/operative_lone.dm
+++ b/code/modules/antagonists/nukeop/datums/operative_lone.dm
@@ -2,7 +2,7 @@
name = "Lone Operative"
always_new_team = TRUE
send_to_spawnpoint = FALSE //Handled by event
- nukeop_outfit = /datum/outfit/syndicate/full
+ nukeop_outfit = /datum/outfit/syndicate/full/loneop
preview_outfit = /datum/outfit/nuclear_operative
preview_outfit_behind = null
nuke_icon_state = null
diff --git a/code/modules/antagonists/nukeop/outfits.dm b/code/modules/antagonists/nukeop/outfits.dm
index 5cd89e6c842a9..8c2820d11be68 100644
--- a/code/modules/antagonists/nukeop/outfits.dm
+++ b/code/modules/antagonists/nukeop/outfits.dm
@@ -67,7 +67,7 @@
/datum/outfit/syndicate/full
name = "Syndicate Operative - Full Kit"
- glasses = /obj/item/clothing/glasses/night
+ glasses = /obj/item/clothing/glasses/night/colorless
mask = /obj/item/clothing/mask/gas/syndicate
back = /obj/item/mod/control/pre_equipped/nuclear
r_pocket = /obj/item/tank/internals/emergency_oxygen/engi
@@ -80,6 +80,10 @@
/obj/item/ammo_box/magazine/m12g = 3,
)
+/datum/outfit/syndicate/full/loneop
+ name = "Syndicate Operative - Full Kit (Loneop)"
+ uplink_type = /obj/item/uplink/loneop
+
/datum/outfit/syndicate/full/plasmaman
name = "Syndicate Operative - Full Kit (Plasmaman)"
back = /obj/item/mod/control/pre_equipped/nuclear/plasmaman
@@ -91,6 +95,10 @@
backpack_contents += /obj/item/clothing/head/helmet/space/plasmaman/syndie
return ..()
+/datum/outfit/syndicate/full/plasmaman/loneop
+ name = "Syndicate Operative - Full Kit (Loneop Plasmaman)"
+ uplink_type = /obj/item/uplink/loneop
+
/datum/outfit/syndicate/reinforcement
name = "Syndicate Operative - Reinforcement"
tc = 0
diff --git a/code/modules/antagonists/pirate/pirate_outfits.dm b/code/modules/antagonists/pirate/pirate_outfits.dm
index aef7f1d9d4b92..72318fe4987ca 100644
--- a/code/modules/antagonists/pirate/pirate_outfits.dm
+++ b/code/modules/antagonists/pirate/pirate_outfits.dm
@@ -62,10 +62,12 @@
id_trim = /datum/id_trim/pirate/silverscale
uniform = /obj/item/clothing/under/syndicate/sniper
suit = /obj/item/clothing/suit/armor/vest/alt
+ back = /obj/item/storage/backpack/satchel
glasses = /obj/item/clothing/glasses/monocle
gloves = /obj/item/clothing/gloves/color/black
head = /obj/item/clothing/head/collectable/tophat
shoes = /obj/item/clothing/shoes/laceup
+ implants = list(/obj/item/implant/explosive)
/datum/outfit/pirate/silverscale/captain
name = "Silver Scale Captain"
diff --git a/code/modules/art/paintings.dm b/code/modules/art/paintings.dm
index f050528bd3bc7..0bb3bcfe8ead7 100644
--- a/code/modules/art/paintings.dm
+++ b/code/modules/art/paintings.dm
@@ -265,7 +265,7 @@
return
var/sniped_amount = painting_metadata.credit_value
var/offer_amount = tgui_input_number(user, "How much do you want to offer?", "Patronage Amount", (painting_metadata.credit_value + 1), account.account_balance, painting_metadata.credit_value)
- if(!offer_amount || QDELETED(user) || QDELETED(src) || !usr.can_perform_action(src, FORBID_TELEKINESIS_REACH))
+ if(!offer_amount || QDELETED(user) || QDELETED(src) || !istype(loc, /obj/structure/sign/painting) || !user.can_perform_action(loc, FORBID_TELEKINESIS_REACH))
return
if(sniped_amount != painting_metadata.credit_value)
return
diff --git a/code/modules/atmospherics/gasmixtures/gas_mixture.dm b/code/modules/atmospherics/gasmixtures/gas_mixture.dm
index cfb87ce7cb3fe..db2732be83fad 100644
--- a/code/modules/atmospherics/gasmixtures/gas_mixture.dm
+++ b/code/modules/atmospherics/gasmixtures/gas_mixture.dm
@@ -676,20 +676,20 @@ GLOBAL_LIST_INIT(gaslist_cache, init_gaslist_cache())
return FALSE
/// Pumps gas from src to output_air. Amount depends on target_pressure
-/datum/gas_mixture/proc/pump_gas_to(datum/gas_mixture/output_air, target_pressure, specific_gas = null)
- var/temperature_delta = abs(temperature - output_air.temperature)
+/datum/gas_mixture/proc/pump_gas_to(datum/gas_mixture/output_air, target_pressure, specific_gas = null, datum/gas_mixture/output_pipenet_air = null)
+ var/datum/gas_mixture/input_air = specific_gas ? remove_specific_ratio(specific_gas, 1) : src
+ var/temperature_delta = abs(input_air.temperature - output_air.temperature)
var/datum/gas_mixture/removed
- var/transfer_moles
+
+ var/transfer_moles_output = input_air.gas_pressure_calculate(output_air, target_pressure, temperature_delta <= 5)
+ var/transfer_moles_pipenet = output_pipenet_air?.volume ? input_air.gas_pressure_calculate(output_pipenet_air, target_pressure, temperature_delta <= 5) : 0
+ var/transfer_moles = max(transfer_moles_output, transfer_moles_pipenet)
if(specific_gas)
- // This is necessary because the specific heat capacity of a gas might be different from our gasmix.
- var/datum/gas_mixture/temporary = remove_specific_ratio(specific_gas, 1)
- transfer_moles = temporary.gas_pressure_calculate(output_air, target_pressure, temperature_delta <= 5)
- removed = temporary.remove_specific(specific_gas, transfer_moles)
- merge(temporary)
+ removed = input_air.remove_specific(specific_gas, transfer_moles)
+ merge(input_air) // Merge the remaining gas back to the input node
else
- transfer_moles = gas_pressure_calculate(output_air, target_pressure, temperature_delta <= 5)
- removed = remove(transfer_moles)
+ removed = input_air.remove(transfer_moles)
if(!removed)
return FALSE
@@ -698,18 +698,20 @@ GLOBAL_LIST_INIT(gaslist_cache, init_gaslist_cache())
return removed
/// Releases gas from src to output air. This means that it can not transfer air to gas mixture with higher pressure.
-/datum/gas_mixture/proc/release_gas_to(datum/gas_mixture/output_air, target_pressure, rate=1)
+/datum/gas_mixture/proc/release_gas_to(datum/gas_mixture/output_air, target_pressure, rate=1, datum/gas_mixture/output_pipenet_air = null)
var/output_starting_pressure = output_air.return_pressure()
var/input_starting_pressure = return_pressure()
//Need at least 10 KPa difference to overcome friction in the mechanism
- if(output_starting_pressure >= min(target_pressure,input_starting_pressure-10))
+ if(output_starting_pressure >= min(target_pressure, input_starting_pressure-10))
return FALSE
//Can not have a pressure delta that would cause output_pressure > input_pressure
target_pressure = output_starting_pressure + min(target_pressure - output_starting_pressure, (input_starting_pressure - output_starting_pressure)/2)
var/temperature_delta = abs(temperature - output_air.temperature)
- var/transfer_moles = gas_pressure_calculate(output_air, target_pressure, temperature_delta <= 5)
+ var/transfer_moles_output = gas_pressure_calculate(output_air, target_pressure, temperature_delta <= 5)
+ var/transfer_moles_pipenet = output_pipenet_air?.volume ? gas_pressure_calculate(output_pipenet_air, target_pressure, temperature_delta <= 5) : 0
+ var/transfer_moles = max(transfer_moles_output, transfer_moles_pipenet)
//Actually transfer the gas
var/datum/gas_mixture/removed = remove(transfer_moles * rate)
diff --git a/code/modules/atmospherics/machinery/air_alarm/_air_alarm.dm b/code/modules/atmospherics/machinery/air_alarm/_air_alarm.dm
index 97278dc6a623b..282c523445bbc 100644
--- a/code/modules/atmospherics/machinery/air_alarm/_air_alarm.dm
+++ b/code/modules/atmospherics/machinery/air_alarm/_air_alarm.dm
@@ -145,6 +145,8 @@ GLOBAL_LIST_EMPTY_TYPED(air_alarms, /obj/machinery/airalarm)
/obj/machinery/airalarm/proc/check_enviroment()
var/turf/our_turf = connected_sensor ? get_turf(connected_sensor) : get_turf(src)
var/datum/gas_mixture/environment = our_turf.return_air()
+ if(isnull(environment))
+ return
check_danger(our_turf, environment, environment.temperature)
/obj/machinery/airalarm/proc/get_enviroment()
@@ -554,6 +556,9 @@ GLOBAL_LIST_EMPTY_TYPED(air_alarms, /obj/machinery/airalarm)
if((machine_stat & (NOPOWER|BROKEN)) || shorted)
return
+ if(!environment)
+ return
+
var/old_danger = danger_level
danger_level = AIR_ALARM_ALERT_NONE
diff --git a/code/modules/atmospherics/machinery/atmosmachinery.dm b/code/modules/atmospherics/machinery/atmosmachinery.dm
index fa8b833234586..29ea43905b8b7 100644
--- a/code/modules/atmospherics/machinery/atmosmachinery.dm
+++ b/code/modules/atmospherics/machinery/atmosmachinery.dm
@@ -666,7 +666,7 @@
SET_PLANE_EXPLICIT(cap_overlay, initial(plane), our_turf)
cap_overlay.color = pipe_color
- cap_overlay.layer = layer
+ cap_overlay.layer = initial(layer)
cap_overlay.icon_state = "[bitfield]_[piping_layer]"
cap_overlay.forceMove(our_turf)
diff --git a/code/modules/atmospherics/machinery/components/binary_devices/passive_gate.dm b/code/modules/atmospherics/machinery/components/binary_devices/passive_gate.dm
index 572e910d3fe08..1d7657dd35276 100644
--- a/code/modules/atmospherics/machinery/components/binary_devices/passive_gate.dm
+++ b/code/modules/atmospherics/machinery/components/binary_devices/passive_gate.dm
@@ -59,9 +59,11 @@ Passive gate is similar to the regular pump except:
if(!on)
return
- var/datum/gas_mixture/air1 = airs[1]
- var/datum/gas_mixture/air2 = airs[2]
- if(air1.release_gas_to(air2, target_pressure))
+ var/datum/gas_mixture/input_air = airs[1]
+ var/datum/gas_mixture/output_air = airs[2]
+ var/datum/gas_mixture/output_pipenet_air = parents[2].air
+
+ if(input_air.release_gas_to(output_air, target_pressure, output_pipenet_air = output_pipenet_air))
update_parents()
/obj/machinery/atmospherics/components/binary/passive_gate/relaymove(mob/living/user, direction)
diff --git a/code/modules/atmospherics/machinery/components/binary_devices/pressure_valve.dm b/code/modules/atmospherics/machinery/components/binary_devices/pressure_valve.dm
index 399feff12146f..ff9cc36d7cd61 100644
--- a/code/modules/atmospherics/machinery/components/binary_devices/pressure_valve.dm
+++ b/code/modules/atmospherics/machinery/components/binary_devices/pressure_valve.dm
@@ -54,11 +54,12 @@
if(!on || !is_operational)
return
- var/datum/gas_mixture/air1 = airs[1]
- var/datum/gas_mixture/air2 = airs[2]
+ var/datum/gas_mixture/input_air = airs[1]
+ var/datum/gas_mixture/output_air = airs[2]
+ var/datum/gas_mixture/output_pipenet_air = parents[2].air
- if(air1.return_pressure() > target_pressure)
- if(air1.release_gas_to(air2, air1.return_pressure()))
+ if(input_air.return_pressure() > target_pressure)
+ if(input_air.release_gas_to(output_air, input_air.return_pressure(), output_pipenet_air = output_pipenet_air))
update_parents()
is_gas_flowing = TRUE
else
diff --git a/code/modules/atmospherics/machinery/components/binary_devices/pump.dm b/code/modules/atmospherics/machinery/components/binary_devices/pump.dm
index 63ba340f27ff4..1e68bf9ad691a 100644
--- a/code/modules/atmospherics/machinery/components/binary_devices/pump.dm
+++ b/code/modules/atmospherics/machinery/components/binary_devices/pump.dm
@@ -61,10 +61,11 @@
if(!on || !is_operational)
return
- var/datum/gas_mixture/air1 = airs[1]
- var/datum/gas_mixture/air2 = airs[2]
+ var/datum/gas_mixture/input_air = airs[1]
+ var/datum/gas_mixture/output_air = airs[2]
+ var/datum/gas_mixture/output_pipenet_air = parents[2].air
- if(air1.pump_gas_to(air2, target_pressure))
+ if(input_air.pump_gas_to(output_air, target_pressure, output_pipenet_air = output_pipenet_air))
update_parents()
/obj/machinery/atmospherics/components/binary/pump/ui_interact(mob/user, datum/tgui/ui)
diff --git a/code/modules/atmospherics/machinery/components/binary_devices/temperature_gate.dm b/code/modules/atmospherics/machinery/components/binary_devices/temperature_gate.dm
index b5a3740245a31..879f6b7fab8d3 100644
--- a/code/modules/atmospherics/machinery/components/binary_devices/temperature_gate.dm
+++ b/code/modules/atmospherics/machinery/components/binary_devices/temperature_gate.dm
@@ -68,19 +68,20 @@
if(!on || !is_operational)
return
- var/datum/gas_mixture/air1 = airs[1]
- var/datum/gas_mixture/air2 = airs[2]
+ var/datum/gas_mixture/input_air = airs[1]
+ var/datum/gas_mixture/output_air = airs[2]
+ var/datum/gas_mixture/output_pipenet_air = parents[2].air
if(!inverted)
- if(air1.temperature < target_temperature)
- if(air1.release_gas_to(air2, air1.return_pressure()))
+ if(input_air.temperature < target_temperature)
+ if(input_air.release_gas_to(output_air, input_air.return_pressure(), output_pipenet_air = output_pipenet_air))
update_parents()
is_gas_flowing = TRUE
else
is_gas_flowing = FALSE
else
- if(air1.temperature > target_temperature)
- if(air1.release_gas_to(air2, air1.return_pressure()))
+ if(input_air.temperature > target_temperature)
+ if(input_air.release_gas_to(output_air, input_air.return_pressure(), output_pipenet_air = output_pipenet_air))
update_parents()
is_gas_flowing = TRUE
else
diff --git a/code/modules/atmospherics/machinery/components/components_base.dm b/code/modules/atmospherics/machinery/components/components_base.dm
index b4e5d88d62c71..cd4e00b3f33a6 100644
--- a/code/modules/atmospherics/machinery/components/components_base.dm
+++ b/code/modules/atmospherics/machinery/components/components_base.dm
@@ -62,6 +62,7 @@
color = null
SET_PLANE_IMPLICIT(src, showpipe ? GAME_PLANE : FLOOR_PLANE)
+ // Layer is handled in update_layer()
if(!showpipe)
return ..()
@@ -327,7 +328,7 @@
connect_nodes()
/obj/machinery/atmospherics/components/update_layer()
- layer = initial(layer) + (piping_layer - PIPING_LAYER_DEFAULT) * PIPING_LAYER_LCHANGE + (GLOB.pipe_colors_ordered[pipe_color] * 0.001)
+ layer = (showpipe ? initial(layer) : ABOVE_OPEN_TURF_LAYER) + (piping_layer - PIPING_LAYER_DEFAULT) * PIPING_LAYER_LCHANGE + (GLOB.pipe_colors_ordered[pipe_color] * 0.001)
/**
* Handles air relocation to the pipenet/environment
diff --git a/code/modules/atmospherics/machinery/components/gas_recipe_machines/crystallizer_items.dm b/code/modules/atmospherics/machinery/components/gas_recipe_machines/crystallizer_items.dm
index 6e0490e25efb3..95b548998a194 100644
--- a/code/modules/atmospherics/machinery/components/gas_recipe_machines/crystallizer_items.dm
+++ b/code/modules/atmospherics/machinery/components/gas_recipe_machines/crystallizer_items.dm
@@ -16,7 +16,7 @@
if(atmos_device.nob_crystal_inserted)
to_chat(user, span_warning("[atmos_device] already has a hypernoblium crystal inserted in it!"))
return ITEM_INTERACT_BLOCKING
- atmos_device.nob_crystal_inserted = TRUE
+ atmos_device.insert_nob_crystal()
to_chat(user, span_notice("You insert the [src] into [atmos_device]."))
if(istype(worn_item))
diff --git a/code/modules/atmospherics/machinery/components/tank.dm b/code/modules/atmospherics/machinery/components/tank.dm
index a9fcaf93ec680..ee1e61428d196 100644
--- a/code/modules/atmospherics/machinery/components/tank.dm
+++ b/code/modules/atmospherics/machinery/components/tank.dm
@@ -363,6 +363,18 @@
/obj/machinery/atmospherics/components/tank/air
name = "pressure tank (Air)"
+/obj/machinery/atmospherics/components/tank/air/layer1
+ piping_layer = 1
+
+/obj/machinery/atmospherics/components/tank/air/layer2
+ piping_layer = 2
+
+/obj/machinery/atmospherics/components/tank/air/layer4
+ piping_layer = 4
+
+/obj/machinery/atmospherics/components/tank/air/layer5
+ piping_layer = 5
+
/obj/machinery/atmospherics/components/tank/air/Initialize(mapload)
. = ..()
fill_to_pressure(/datum/gas/oxygen, safety_margin = (O2STANDARD * 0.5))
diff --git a/code/modules/atmospherics/machinery/components/trinary_devices/mixer.dm b/code/modules/atmospherics/machinery/components/trinary_devices/mixer.dm
index 12a3c7971601b..8f90986baaa46 100644
--- a/code/modules/atmospherics/machinery/components/trinary_devices/mixer.dm
+++ b/code/modules/atmospherics/machinery/components/trinary_devices/mixer.dm
@@ -250,6 +250,18 @@
icon_state = "mixer_on-0_f"
flipped = TRUE
+/obj/machinery/atmospherics/components/trinary/mixer/airmix/flipped/layer1
+ piping_layer = 1
+
+/obj/machinery/atmospherics/components/trinary/mixer/airmix/flipped/layer2
+ piping_layer = 2
+
+/obj/machinery/atmospherics/components/trinary/mixer/airmix/flipped/layer4
+ piping_layer = 4
+
+/obj/machinery/atmospherics/components/trinary/mixer/airmix/flipped/layer5
+ piping_layer = 5
+
/obj/machinery/atmospherics/components/trinary/mixer/airmix/flipped/inverse
node1_concentration = O2STANDARD
node2_concentration = N2STANDARD
diff --git a/code/modules/atmospherics/machinery/components/unary_devices/thermomachine.dm b/code/modules/atmospherics/machinery/components/unary_devices/thermomachine.dm
index 2fe1a8e430fa1..619bf100a170f 100644
--- a/code/modules/atmospherics/machinery/components/unary_devices/thermomachine.dm
+++ b/code/modules/atmospherics/machinery/components/unary_devices/thermomachine.dm
@@ -333,6 +333,18 @@
/obj/machinery/atmospherics/components/unary/thermomachine/freezer
+/obj/machinery/atmospherics/components/unary/thermomachine/freezer/layer1
+ piping_layer = 1
+
+/obj/machinery/atmospherics/components/unary/thermomachine/freezer/layer2
+ piping_layer = 2
+
+/obj/machinery/atmospherics/components/unary/thermomachine/freezer/layer4
+ piping_layer = 4
+
+/obj/machinery/atmospherics/components/unary/thermomachine/freezer/layer5
+ piping_layer = 5
+
/obj/machinery/atmospherics/components/unary/thermomachine/freezer/on
on = TRUE
icon_state = "thermo_base_1"
@@ -352,6 +364,18 @@
/obj/machinery/atmospherics/components/unary/thermomachine/heater
+/obj/machinery/atmospherics/components/unary/thermomachine/heater/layer1
+ piping_layer = 1
+
+/obj/machinery/atmospherics/components/unary/thermomachine/heater/layer2
+ piping_layer = 2
+
+/obj/machinery/atmospherics/components/unary/thermomachine/heater/layer4
+ piping_layer = 4
+
+/obj/machinery/atmospherics/components/unary/thermomachine/heater/layer5
+ piping_layer = 5
+
/obj/machinery/atmospherics/components/unary/thermomachine/heater/on
on = TRUE
icon_state = "thermo_base_1"
diff --git a/code/modules/atmospherics/machinery/pipes/bridge_pipe.dm b/code/modules/atmospherics/machinery/pipes/bridge_pipe.dm
index 472cb3ed2034a..4c1cd63016c92 100644
--- a/code/modules/atmospherics/machinery/pipes/bridge_pipe.dm
+++ b/code/modules/atmospherics/machinery/pipes/bridge_pipe.dm
@@ -5,6 +5,7 @@
name = "bridge pipe"
desc = "A one meter section of regular pipe used to connect pipenets over pipes."
+ layer = HIGH_PIPE_LAYER
dir = SOUTH
initialize_directions = NORTH | SOUTH
pipe_flags = PIPING_CARDINAL_AUTONORMALIZE | PIPING_BRIDGE
@@ -28,4 +29,5 @@
PIPING_LAYER_DOUBLE_SHIFT(center, piping_layer)
. += center
- layer = HIGH_PIPE_LAYER //to stay above all sorts of pipes
+/obj/machinery/atmospherics/pipe/bridge_pipe/update_layer()
+ layer = (HAS_TRAIT(src, TRAIT_UNDERFLOOR) ? ABOVE_OPEN_TURF_LAYER + 1 : initial(layer))
diff --git a/code/modules/atmospherics/machinery/pipes/layermanifold.dm b/code/modules/atmospherics/machinery/pipes/layermanifold.dm
index 730e193671dbe..df919cbeccb1c 100644
--- a/code/modules/atmospherics/machinery/pipes/layermanifold.dm
+++ b/code/modules/atmospherics/machinery/pipes/layermanifold.dm
@@ -41,7 +41,7 @@
nodes = list()
/obj/machinery/atmospherics/pipe/layer_manifold/update_layer()
- layer = initial(layer) + (PIPING_LAYER_MAX * PIPING_LAYER_LCHANGE) //This is above everything else.
+ layer = (HAS_TRAIT(src, TRAIT_UNDERFLOOR) ? ABOVE_OPEN_TURF_LAYER : initial(layer)) + (PIPING_LAYER_MAX * PIPING_LAYER_LCHANGE) //This is above everything else.
/obj/machinery/atmospherics/pipe/layer_manifold/update_overlays()
. = ..()
@@ -68,7 +68,8 @@
. += get_attached_image(get_dir(src, machine_check), machine_check.piping_layer, machine_check.pipe_color)
/obj/machinery/atmospherics/pipe/layer_manifold/proc/get_attached_image(p_dir, p_layer, p_color)
- var/mutable_appearance/muta = mutable_appearance('icons/obj/pipes_n_cables/layer_manifold_underlays.dmi', "intact_[p_dir]_[p_layer]", layer = layer - 0.01, appearance_flags = RESET_COLOR)
+ var/working_layer = FLOAT_LAYER - HAS_TRAIT(src, TRAIT_UNDERFLOOR) ? 1 : 0.01
+ var/mutable_appearance/muta = mutable_appearance('icons/obj/pipes_n_cables/layer_manifold_underlays.dmi', "intact_[p_dir]_[p_layer]", layer = working_layer, appearance_flags = RESET_COLOR)
muta.color = p_color
return muta
diff --git a/code/modules/atmospherics/machinery/pipes/mapping.dm b/code/modules/atmospherics/machinery/pipes/mapping.dm
index 3ba647ace8ab4..3615147e4d1bf 100644
--- a/code/modules/atmospherics/machinery/pipes/mapping.dm
+++ b/code/modules/atmospherics/machinery/pipes/mapping.dm
@@ -1,6 +1,22 @@
//Colored pipes, use these for mapping
+#define HELPER_PIPING_LAYER(Fulltype) \
+ ##Fulltype/layer1 { \
+ piping_layer = 1; \
+ } \
+ ##Fulltype/layer2 { \
+ piping_layer = 2; \
+ } \
+ ##Fulltype/layer4 { \
+ piping_layer = 4; \
+ } \
+ ##Fulltype/layer5 { \
+ piping_layer = 5; \
+ }
+
#define HELPER_PARTIAL(Fulltype, Iconbase, Color) \
+ HELPER_PIPING_LAYER(Fulltype/visible) \
+ HELPER_PIPING_LAYER(Fulltype/hidden) \
##Fulltype { \
pipe_color = Color; \
color = Color; \
@@ -10,38 +26,30 @@
layer = GAS_PIPE_VISIBLE_LAYER; \
} \
##Fulltype/visible/layer2 { \
- piping_layer = 2; \
icon_state = Iconbase + "-2"; \
} \
##Fulltype/visible/layer4 { \
- piping_layer = 4; \
icon_state = Iconbase + "-4"; \
} \
##Fulltype/visible/layer1 { \
- piping_layer = 1; \
icon_state = Iconbase + "-1"; \
} \
##Fulltype/visible/layer5 { \
- piping_layer = 5; \
icon_state = Iconbase + "-5"; \
} \
##Fulltype/hidden { \
hide = TRUE; \
} \
##Fulltype/hidden/layer2 { \
- piping_layer = 2; \
icon_state = Iconbase + "-2"; \
} \
##Fulltype/hidden/layer4 { \
- piping_layer = 4; \
icon_state = Iconbase + "-4"; \
} \
##Fulltype/hidden/layer1 { \
- piping_layer = 1; \
icon_state = Iconbase + "-1"; \
} \
##Fulltype/hidden/layer5 { \
- piping_layer = 5; \
icon_state = Iconbase + "-5"; \
}
@@ -89,3 +97,4 @@ HELPER_NAMED(supply, "air supply pipe", COLOR_BLUE)
#undef HELPER
#undef HELPER_PARTIAL_NAMED
#undef HELPER_PARTIAL
+#undef HELPER_PIPING_LAYER
diff --git a/code/modules/atmospherics/machinery/pipes/pipes.dm b/code/modules/atmospherics/machinery/pipes/pipes.dm
index 40258db903ff0..230edc9a897f0 100644
--- a/code/modules/atmospherics/machinery/pipes/pipes.dm
+++ b/code/modules/atmospherics/machinery/pipes/pipes.dm
@@ -139,4 +139,4 @@
current_node.update_icon()
/obj/machinery/atmospherics/pipe/update_layer()
- layer = initial(layer) + (piping_layer - PIPING_LAYER_DEFAULT) * PIPING_LAYER_LCHANGE + (GLOB.pipe_colors_ordered[pipe_color] * 0.0001)
+ layer = (HAS_TRAIT(src, TRAIT_UNDERFLOOR) ? ABOVE_OPEN_TURF_LAYER : initial(layer)) + (piping_layer - PIPING_LAYER_DEFAULT) * PIPING_LAYER_LCHANGE + (GLOB.pipe_colors_ordered[pipe_color] * 0.0001)
diff --git a/code/modules/atmospherics/machinery/portable/canister.dm b/code/modules/atmospherics/machinery/portable/canister.dm
index c3dae6241620d..af13be0bbbf9b 100644
--- a/code/modules/atmospherics/machinery/portable/canister.dm
+++ b/code/modules/atmospherics/machinery/portable/canister.dm
@@ -101,6 +101,8 @@
/obj/machinery/portable_atmospherics/canister/examine(user)
. = ..()
+ if(atom_integrity < max_integrity)
+ . += span_notice("Integrity compromised, repair hull with a welding tool.")
. += span_notice("A sticker on its side says MAX SAFE PRESSURE: [siunit_pressure(initial(pressure_limit), 0)]; MAX SAFE TEMPERATURE: [siunit(temp_limit, "K", 0)].")
. += span_notice("The hull is welded together and can be cut apart.")
if(internal_cell)
@@ -109,8 +111,6 @@
. += span_notice("Warning, no cell installed, use a screwdriver to open the hatch and insert one.")
if(panel_open)
. += span_notice("Hatch open, close it with a screwdriver.")
- if(integrity_failure)
- . += span_notice("Integrity compromised, repair hull with a welding tool.")
// Please keep the canister types sorted
// Basic canister per gas below here
diff --git a/code/modules/atmospherics/machinery/portable/pipe_scrubber.dm b/code/modules/atmospherics/machinery/portable/pipe_scrubber.dm
new file mode 100644
index 0000000000000..cde38f216ad70
--- /dev/null
+++ b/code/modules/atmospherics/machinery/portable/pipe_scrubber.dm
@@ -0,0 +1,167 @@
+/obj/machinery/portable_atmospherics/pipe_scrubber
+ name = "pipe scrubber"
+ desc = "A machine for cleaning out pipes of lingering gases. It is a huge tank with a pump attached to it."
+ icon_state = "pipe_scrubber"
+ density = TRUE
+ max_integrity = 250
+ volume = 200
+ ///The internal air tank obj of the mech
+ var/obj/machinery/portable_atmospherics/canister/internal_tank
+ ///Is the machine on?
+ var/on = FALSE
+ ///What direction is the machine pumping to (into scrubber or out to the port)?
+ var/direction = PUMP_IN
+ ///the rate the machine will scrub air
+ var/volume_rate = 1000
+ ///List of gases that can be scrubbed
+ var/list/scrubbing = list(
+ /datum/gas/plasma,
+ /datum/gas/carbon_dioxide,
+ /datum/gas/nitrous_oxide,
+ /datum/gas/bz,
+ /datum/gas/nitrium,
+ /datum/gas/tritium,
+ /datum/gas/hypernoblium,
+ /datum/gas/water_vapor,
+ /datum/gas/freon,
+ /datum/gas/hydrogen,
+ /datum/gas/healium,
+ /datum/gas/proto_nitrate,
+ /datum/gas/zauker,
+ /datum/gas/halon,
+ )
+
+/obj/machinery/portable_atmospherics/pipe_scrubber/Initialize(mapload)
+ . = ..()
+ internal_tank = new(src)
+ RegisterSignal(internal_tank, COMSIG_ATOM_BREAK, PROC_REF(deconstruct))
+ RegisterSignal(internal_tank, COMSIG_QDELETING, PROC_REF(deconstruct))
+
+/obj/machinery/portable_atmospherics/pipe_scrubber/atom_deconstruct(disassembled)
+ . = ..()
+ var/turf/my_turf = get_turf(src)
+ my_turf.assume_air(air_contents)
+ my_turf.assume_air(internal_tank.air_contents)
+ SSair.stop_processing_machine(internal_tank)
+ qdel(internal_tank)
+
+/obj/machinery/portable_atmospherics/pipe_scrubber/return_analyzable_air()
+ return list(
+ air_contents,
+ internal_tank.air_contents
+ )
+
+/obj/machinery/portable_atmospherics/pipe_scrubber/welder_act(mob/living/user, obj/item/tool)
+ internal_tank.welder_act(user, tool)
+ return ..()
+
+/obj/machinery/portable_atmospherics/pipe_scrubber/click_alt(mob/living/user)
+ return CLICK_ACTION_BLOCKING
+
+/obj/machinery/portable_atmospherics/pipe_scrubber/replace_tank(mob/living/user, close_valve, obj/item/tank/new_tank)
+ return FALSE
+
+/obj/machinery/portable_atmospherics/pipe_scrubber/update_icon_state()
+ icon_state = on ? "[initial(icon_state)]_active" : initial(icon_state)
+ return ..()
+
+/obj/machinery/portable_atmospherics/pipe_scrubber/process_atmos()
+ if(take_atmos_damage())
+ excited = TRUE
+ return ..()
+ if(!on)
+ return ..()
+ excited = TRUE
+ if(direction == PUMP_IN)
+ scrub(air_contents)
+ else
+ internal_tank.air_contents.pump_gas_to(air_contents, PUMP_MAX_PRESSURE)
+ return ..()
+
+/// Scrub gasses from own air_contents into internal_tank.air_contents
+/obj/machinery/portable_atmospherics/pipe_scrubber/proc/scrub()
+ if(internal_tank.air_contents.return_pressure() >= PUMP_MAX_PRESSURE)
+ return
+
+ var/transfer_moles = min(1, volume_rate / air_contents.volume) * air_contents.total_moles()
+
+ var/datum/gas_mixture/filtering = air_contents.remove(transfer_moles) // Remove part of the mixture to filter.
+ var/datum/gas_mixture/filtered = new
+ if(!filtering)
+ return
+
+ filtered.temperature = filtering.temperature
+ for(var/gas in filtering.gases & scrubbing)
+ filtered.add_gas(gas)
+ filtered.gases[gas][MOLES] = filtering.gases[gas][MOLES] // Shuffle the "bad" gasses to the filtered mixture.
+ filtering.gases[gas][MOLES] = 0
+ filtering.garbage_collect() // Now that the gasses are set to 0, clean up the mixture.
+
+ internal_tank.air_contents.merge(filtered) // Store filtered out gasses.
+ air_contents.merge(filtering) // Returned the cleaned gas.
+
+/obj/machinery/portable_atmospherics/pipe_scrubber/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "PipeScrubber", name)
+ ui.open()
+
+/obj/machinery/portable_atmospherics/pipe_scrubber/ui_data()
+ var/data = list()
+ data["on"] = on
+ data["direction"] = direction
+ data["connected"] = connected_port ? 1 : 0
+ data["pressureTank"] = round(internal_tank.air_contents.return_pressure() ? internal_tank.air_contents.return_pressure() : 0)
+ data["pressurePump"] = round(air_contents.return_pressure() ? air_contents.return_pressure() : 0)
+ data["hasHypernobCrystal"] = nob_crystal_inserted
+ data["reactionSuppressionEnabled"] = suppress_reactions
+
+ data["filterTypes"] = list()
+ for(var/gas_path in GLOB.meta_gas_info)
+ var/list/gas = GLOB.meta_gas_info[gas_path]
+ data["filterTypes"] += list(list("gasId" = gas[META_GAS_ID], "gasName" = gas[META_GAS_NAME], "enabled" = (gas_path in scrubbing)))
+
+ return data
+
+/obj/machinery/portable_atmospherics/pipe_scrubber/ui_static_data()
+ var/list/data = list()
+ data["pressureLimitPump"] = pressure_limit
+ data["pressureLimitTank"] = internal_tank.pressure_limit
+ return data
+
+/obj/machinery/portable_atmospherics/pipe_scrubber/ui_act(action, params)
+ . = ..()
+ if(.)
+ return
+ switch(action)
+ if("power")
+ on = !on
+ if(on)
+ SSair.start_processing_machine(src)
+ SSair.start_processing_machine(internal_tank)
+ . = TRUE
+ if("direction")
+ direction = !direction
+ . = TRUE
+ if("toggle_filter")
+ scrubbing ^= gas_id2path(params["val"])
+ . = TRUE
+ if("reaction_suppression")
+ if(!internal_tank.nob_crystal_inserted)
+ message_admins("[ADMIN_LOOKUPFLW(usr)] tried to toggle reaction suppression on a pipe scrubber without a noblium crystal inside, possible href exploit attempt.")
+ return
+ internal_tank.suppress_reactions = !internal_tank.suppress_reactions
+ SSair.start_processing_machine(internal_tank)
+ message_admins("[ADMIN_LOOKUPFLW(usr)] turned [internal_tank.suppress_reactions ? "on" : "off"] the [internal_tank] reaction suppression.")
+ usr.investigate_log("turned [internal_tank.suppress_reactions ? "on" : "off"] the [internal_tank] reaction suppression.")
+ . = TRUE
+ update_appearance()
+
+/obj/machinery/portable_atmospherics/pipe_scrubber/insert_nob_crystal()
+ . = ..()
+ internal_tank.nob_crystal_inserted = TRUE
+
+/obj/machinery/portable_atmospherics/pipe_scrubber/proc/toggle_reaction_suppression()
+ var/new_value = !suppress_reactions
+ suppress_reactions = new_value
+ internal_tank.suppress_reactions = new_value
diff --git a/code/modules/atmospherics/machinery/portable/portable_atmospherics.dm b/code/modules/atmospherics/machinery/portable/portable_atmospherics.dm
index 17f3a6fb439f7..9729c0871451a 100644
--- a/code/modules/atmospherics/machinery/portable/portable_atmospherics.dm
+++ b/code/modules/atmospherics/machinery/portable/portable_atmospherics.dm
@@ -212,8 +212,16 @@
* * new_tank: the tank we are trying to put in the machine
*/
/obj/machinery/portable_atmospherics/proc/replace_tank(mob/living/user, close_valve, obj/item/tank/new_tank)
+ if(machine_stat & BROKEN)
+ return FALSE
if(!user)
return FALSE
+ if(!user.transferItemToLoc(new_tank, src))
+ return FALSE
+
+ investigate_log("had its internal [holding] swapped with [new_tank] by [key_name(user)].", INVESTIGATE_ATMOS)
+ to_chat(user, span_notice("[holding ? "In one smooth motion you pop [holding] out of [src]'s connector and replace it with [new_tank]" : "You insert [new_tank] into [src]"]."))
+
if(holding && new_tank)//for when we are actually switching tanks
user.put_in_hands(holding)
UnregisterSignal(holding, COMSIG_QDELETING)
@@ -238,17 +246,9 @@
return TRUE
/obj/machinery/portable_atmospherics/attackby(obj/item/item, mob/user, params)
- if(!istype(item, /obj/item/tank))
- return ..()
- if(machine_stat & BROKEN)
- return FALSE
- var/obj/item/tank/insert_tank = item
- if(!user.transferItemToLoc(insert_tank, src))
- return FALSE
- to_chat(user, span_notice("[holding ? "In one smooth motion you pop [holding] out of [src]'s connector and replace it with [insert_tank]" : "You insert [insert_tank] into [src]"]."))
- investigate_log("had its internal [holding] swapped with [insert_tank] by [key_name(user)].", INVESTIGATE_ATMOS)
- replace_tank(user, FALSE, insert_tank)
- update_appearance()
+ if(istype(item, /obj/item/tank))
+ return replace_tank(user, FALSE, item)
+ return ..()
/obj/machinery/portable_atmospherics/wrench_act(mob/living/user, obj/item/wrench)
if(machine_stat & BROKEN)
@@ -295,4 +295,8 @@
UnregisterSignal(holding, COMSIG_QDELETING)
holding = null
+/// Insert Hypernob crystal into the machine
+/obj/machinery/portable_atmospherics/proc/insert_nob_crystal()
+ nob_crystal_inserted = TRUE
+
#undef PORTABLE_ATMOS_IGNORE_ATMOS_LIMIT
diff --git a/code/modules/atmospherics/machinery/portable/scrubber.dm b/code/modules/atmospherics/machinery/portable/scrubber.dm
index 09c6c64709b9a..b292180683f88 100644
--- a/code/modules/atmospherics/machinery/portable/scrubber.dm
+++ b/code/modules/atmospherics/machinery/portable/scrubber.dm
@@ -1,16 +1,17 @@
/obj/machinery/portable_atmospherics/scrubber
name = "portable air scrubber"
+ desc = "A portable variant of the station scrubbers, capable of filtering gas from the air around it or inserted tank. May also be wrenched into a port."
icon_state = "scrubber"
density = TRUE
max_integrity = 250
- volume = 1000
+ volume = 2000
///Is the machine on?
var/on = FALSE
///the rate the machine will scrub air
- var/volume_rate = 1000
+ var/volume_rate = 650
///Multiplier with ONE_ATMOSPHERE, if the enviroment pressure is higher than that, the scrubber won't work
- var/overpressure_m = 80
+ var/overpressure_m = 100
///Should the machine use overlay in update_overlays() when open/close?
var/use_overlays = TRUE
///List of gases that can be scrubbed
@@ -59,8 +60,15 @@
excited = TRUE
- var/atom/target = holding || get_turf(src)
- scrub(target.return_air())
+ if(!isnull(holding))
+ scrub(holding.return_air())
+ return ..()
+
+ var/turf/epicentre = get_turf(src)
+ if(isopenturf(epicentre))
+ scrub(epicentre.return_air())
+ for(var/turf/open/openturf as anything in epicentre.get_atmos_adjacent_turfs(alldir = TRUE))
+ scrub(openturf.return_air())
return ..()
/**
@@ -68,28 +76,39 @@
* Arguments:
* * mixture: the gas mixture to be scrubbed
*/
-/obj/machinery/portable_atmospherics/scrubber/proc/scrub(datum/gas_mixture/mixture)
+/obj/machinery/portable_atmospherics/scrubber/proc/scrub(datum/gas_mixture/environment)
if(air_contents.return_pressure() >= overpressure_m * ONE_ATMOSPHERE)
return
- var/transfer_moles = min(1, volume_rate / mixture.volume) * mixture.total_moles()
+ var/list/env_gases = environment.gases
- var/datum/gas_mixture/filtering = mixture.remove(transfer_moles) // Remove part of the mixture to filter.
- var/datum/gas_mixture/filtered = new
- if(!filtering)
- return
+ //contains all of the gas we're sucking out of the tile, gets put into our parent pipenet
+ var/datum/gas_mixture/filtered_out = new
+ var/list/filtered_gases = filtered_out.gases
+ filtered_out.temperature = environment.temperature
- filtered.temperature = filtering.temperature
- for(var/gas in filtering.gases & scrubbing)
- filtered.add_gas(gas)
- filtered.gases[gas][MOLES] = filtering.gases[gas][MOLES] // Shuffle the "bad" gasses to the filtered mixture.
- filtering.gases[gas][MOLES] = 0
- filtering.garbage_collect() // Now that the gasses are set to 0, clean up the mixture.
+ //maximum percentage of the turfs gas we can filter
+ var/removal_ratio = min(1, volume_rate / environment.volume)
- air_contents.merge(filtered) // Store filtered out gasses.
- mixture.merge(filtering) // Returned the cleaned gas.
- if(!holding)
- air_update_turf(FALSE, FALSE)
+ var/total_moles_to_remove = 0
+ for(var/gas in scrubbing & env_gases)
+ total_moles_to_remove += env_gases[gas][MOLES]
+
+ if(total_moles_to_remove == 0)//sometimes this gets non gc'd values
+ environment.garbage_collect()
+ return FALSE
+
+ for(var/gas in scrubbing & env_gases)
+ filtered_out.add_gas(gas)
+ var/transferred_moles = max(QUANTIZE(env_gases[gas][MOLES] * removal_ratio * (env_gases[gas][MOLES] / total_moles_to_remove)), min(MOLAR_ACCURACY*1000, env_gases[gas][MOLES]))
+
+ filtered_gases[gas][MOLES] = transferred_moles
+ env_gases[gas][MOLES] -= transferred_moles
+
+ environment.garbage_collect()
+
+ //Remix the resulting gases
+ air_contents.merge(filtered_out)
/obj/machinery/portable_atmospherics/scrubber/emp_act(severity)
. = ..()
diff --git a/code/modules/awaymissions/away_props.dm b/code/modules/awaymissions/away_props.dm
index 22d9f532efcbd..6f6a25b47c8cd 100644
--- a/code/modules/awaymissions/away_props.dm
+++ b/code/modules/awaymissions/away_props.dm
@@ -56,6 +56,7 @@
name = "pit grate"
icon = 'icons/obj/smooth_structures/lattice.dmi'
icon_state = "lattice-255"
+ layer = ABOVE_OPEN_TURF_LAYER
plane = FLOOR_PLANE
anchored = TRUE
obj_flags = CAN_BE_HIT | BLOCK_Z_OUT_DOWN | BLOCK_Z_IN_UP
@@ -95,6 +96,7 @@
talpha = 255
obj_flags |= BLOCK_Z_OUT_DOWN | BLOCK_Z_IN_UP
SET_PLANE_IMPLICIT(src, ABOVE_LIGHTING_PLANE) //What matters it's one above openspace, so our animation is not dependant on what's there. Up to revision with 513
+ layer = ABOVE_NORMAL_TURF_LAYER
animate(src,alpha = talpha,time = 10)
addtimer(CALLBACK(src, PROC_REF(reset_plane)), 1 SECONDS)
if(hidden)
@@ -106,6 +108,7 @@
/obj/structure/pitgrate/proc/reset_plane()
SET_PLANE_IMPLICIT(src, FLOOR_PLANE)
+ layer = ABOVE_OPEN_TURF_LAYER
/obj/structure/pitgrate/Destroy()
if(hidden)
diff --git a/code/modules/bitrunning/components/avatar_connection.dm b/code/modules/bitrunning/components/avatar_connection.dm
index a92e8ef3d2e6e..51263c339319e 100644
--- a/code/modules/bitrunning/components/avatar_connection.dm
+++ b/code/modules/bitrunning/components/avatar_connection.dm
@@ -60,7 +60,7 @@
var/datum/action/avatar_domain_info/action = new(help_datum)
action.Grant(avatar)
- var/client/our_client = old_body.client
+ var/client/our_client = avatar.client
var/alias = our_client?.prefs?.read_preference(/datum/preference/name/hacker_alias) || pick(GLOB.hacker_aliases)
if(alias && avatar.real_name != alias)
diff --git a/code/modules/bitrunning/components/bitrunning_points.dm b/code/modules/bitrunning/components/bitrunning_points.dm
index 328a70679e653..ea8f63f76d8df 100644
--- a/code/modules/bitrunning/components/bitrunning_points.dm
+++ b/code/modules/bitrunning/components/bitrunning_points.dm
@@ -5,6 +5,7 @@
/// A special condition limits this from spawning a crate
var/points_received = 0
+
/datum/component/bitrunning_points/Initialize(datum/lazy_template/virtual_domain/domain)
. = ..()
if(!isturf(parent))
@@ -12,6 +13,7 @@
RegisterSignal(domain, COMSIG_BITRUNNER_GOAL_POINT, PROC_REF(on_add_points))
+
/// Listens for points to be added which will eventually spawn a crate.
/datum/component/bitrunning_points/proc/on_add_points(datum/source, points_to_add)
SIGNAL_HANDLER
@@ -23,12 +25,14 @@
reveal()
+
/// Spawns the crate with some effects
/datum/component/bitrunning_points/proc/reveal()
playsound(src, 'sound/magic/blink.ogg', 50, TRUE)
var/turf/tile = parent
- new /obj/structure/closet/crate/secure/bitrunning/encrypted(tile)
+ var/obj/structure/closet/crate/secure/bitrunning/encrypted/crate = new()
+ crate.forceMove(tile) // Triggers any on-move effects on that turf
var/datum/effect_system/spark_spread/quantum/sparks = new(tile)
sparks.set_up(number = 5, location = tile)
diff --git a/code/modules/bitrunning/netpod/_netpod.dm b/code/modules/bitrunning/netpod/_netpod.dm
new file mode 100644
index 0000000000000..56e8b0a1a57d1
--- /dev/null
+++ b/code/modules/bitrunning/netpod/_netpod.dm
@@ -0,0 +1,137 @@
+#define BASE_DISCONNECT_DAMAGE 40
+
+
+/obj/machinery/netpod
+ name = "netpod"
+
+ base_icon_state = "netpod"
+ circuit = /obj/item/circuitboard/machine/netpod
+ desc = "Связущее звено с сетевым миром. Здесь есть множество кабелей для подключения себя к виртуальному домену."
+ icon = 'icons/obj/machines/bitrunning.dmi'
+ icon_state = "netpod"
+ max_integrity = 300
+ obj_flags = BLOCKS_CONSTRUCTION
+ state_open = TRUE
+ interaction_flags_mouse_drop = NEED_HANDS | NEED_DEXTERITY
+
+ /// Whether we have an ongoing connection
+ var/connected = FALSE
+ /// A player selected outfit by clicking the netpod
+ var/datum/outfit/netsuit = /datum/outfit/job/bitrunner
+ /// Holds this to see if it needs to generate a new one
+ var/datum/weakref/avatar_ref
+ /// The linked quantum server
+ var/datum/weakref/server_ref
+ /// The amount of brain damage done from force disconnects
+ var/disconnect_damage
+ /// Static list of outfits to select from
+ var/list/cached_outfits = list()
+
+
+/obj/machinery/netpod/post_machine_initialize()
+ . = ..()
+
+ disconnect_damage = BASE_DISCONNECT_DAMAGE
+ find_server()
+
+ RegisterSignal(src, COMSIG_ATOM_TAKE_DAMAGE, PROC_REF(on_damage_taken))
+ RegisterSignal(src, COMSIG_MACHINERY_POWER_LOST, PROC_REF(on_power_loss))
+ RegisterSignals(src, list(COMSIG_QDELETING, COMSIG_MACHINERY_BROKEN),PROC_REF(on_broken))
+
+ register_context()
+ update_appearance()
+
+
+/obj/machinery/netpod/Destroy()
+ . = ..()
+
+ QDEL_LIST(cached_outfits)
+
+
+/obj/machinery/netpod/examine(mob/user)
+ . = ..()
+
+ if(isnull(server_ref?.resolve()))
+ . += span_infoplain("Оно ни к чему не подключено.")
+ . += span_infoplain("Нетподы должны быть построены на расстоянии 4-х тайлов от сервера.")
+ return
+
+ if(!isobserver(user))
+ . += span_infoplain("Перетащите себя на под, чтобы начать подключение.")
+ . += span_infoplain("Под имеет ограниченные возможности реанимации. Нахождение в поде может вылечить некоторые ранения.")
+ . += span_infoplain("Имеется система безопасности, оповещающая пользователя, если начнется вмешательство с подом.")
+
+ if(isnull(occupant))
+ . += span_infoplain("Сейчас внутри пусто.")
+ return
+
+ . += span_infoplain("Сейчас внутри находится - [occupant].")
+
+ if(isobserver(user))
+ . += span_notice("Как наблюдатель, вы можете щелкнуть по этому нетподу, чтобы перейти к его аватару.")
+ return
+
+ . += span_notice("Оно может быть насильно открыто монтировкой, но системы безопасности оповестят пользователя.")
+
+
+/obj/machinery/netpod/add_context(atom/source, list/context, obj/item/held_item, mob/user)
+ . = ..()
+
+ if(isnull(held_item))
+ context[SCREENTIP_CONTEXT_LMB] = "Выбрать одежду"
+ return CONTEXTUAL_SCREENTIP_SET
+
+ if(istype(held_item, /obj/item/crowbar) && occupant)
+ context[SCREENTIP_CONTEXT_LMB] = "Насильно открыть"
+ return CONTEXTUAL_SCREENTIP_SET
+
+
+/obj/machinery/netpod/update_icon_state()
+ if(!is_operational)
+ icon_state = base_icon_state
+ return ..()
+
+ if(state_open)
+ icon_state = base_icon_state + "_open_active"
+ return ..()
+
+ if(panel_open)
+ icon_state = base_icon_state + "_panel"
+ return ..()
+
+ icon_state = base_icon_state + "_closed"
+ if(occupant)
+ icon_state += "_active"
+
+ return ..()
+
+
+/obj/machinery/netpod/mouse_drop_receive(mob/target, mob/user, params)
+ var/mob/living/carbon/player = user
+
+ if(!iscarbon(player) || !is_operational || !state_open || player.buckled)
+ return
+
+ close_machine(target)
+
+
+/obj/machinery/netpod/attack_hand(mob/living/user, list/modifiers)
+ . = ..()
+ if(!state_open && user == occupant)
+ container_resist_act(user)
+
+
+/obj/machinery/netpod/attack_ghost(mob/dead/observer/our_observer)
+ var/our_target = avatar_ref?.resolve()
+ if(isnull(our_target) || !our_observer.orbit(our_target))
+ return ..()
+
+
+/// When the server is upgraded, drops brain damage a little
+/obj/machinery/netpod/proc/on_server_upgraded(obj/machinery/quantum_server/source)
+ SIGNAL_HANDLER
+
+ disconnect_damage = BASE_DISCONNECT_DAMAGE * (1 - source.servo_bonus)
+
+
+#undef BASE_DISCONNECT_DAMAGE
diff --git a/code/modules/bitrunning/netpod/container.dm b/code/modules/bitrunning/netpod/container.dm
new file mode 100644
index 0000000000000..6165544544511
--- /dev/null
+++ b/code/modules/bitrunning/netpod/container.dm
@@ -0,0 +1,74 @@
+/obj/machinery/netpod/Exited(atom/movable/gone, direction)
+ . = ..()
+ if(!state_open && gone == occupant)
+ container_resist_act(gone)
+
+
+/obj/machinery/netpod/relaymove(mob/living/user, direction)
+ if(!state_open)
+ container_resist_act(user)
+
+
+/obj/machinery/netpod/container_resist_act(mob/living/user)
+ user.visible_message(span_notice("[occupant] emerges from [src]!"),
+ span_notice("You climb out of [src]!"),
+ span_notice("With a hiss, you hear a machine opening."))
+ open_machine()
+
+
+/obj/machinery/netpod/open_machine(drop = TRUE, density_to_set = FALSE)
+ playsound(src, 'sound/machines/tramopen.ogg', 60, TRUE, frequency = 65000)
+ flick("[base_icon_state]_opening", src)
+ SEND_SIGNAL(src, COMSIG_BITRUNNER_NETPOD_OPENED)
+ update_use_power(IDLE_POWER_USE)
+
+ return ..()
+
+
+/obj/machinery/netpod/close_machine(mob/user, density_to_set = TRUE)
+ if(!state_open || panel_open || !is_operational || !iscarbon(user))
+ return
+
+ playsound(src, 'sound/machines/tramclose.ogg', 60, TRUE, frequency = 65000)
+ flick("[base_icon_state]_closing", src)
+ ..()
+
+ enter_matrix()
+
+
+/obj/machinery/netpod/default_pry_open(obj/item/crowbar, mob/living/pryer)
+ if(isnull(occupant) || !iscarbon(occupant))
+ if(!state_open)
+ if(panel_open)
+ return FALSE
+ open_machine()
+ else
+ shut_pod()
+
+ return TRUE
+
+ pryer.visible_message(
+ span_danger("[pryer] starts prying open [src]!"),
+ span_notice("You start to pry open [src]."),
+ span_notice("You hear loud prying on metal.")
+ )
+ playsound(src, 'sound/machines/airlock_alien_prying.ogg', 100, TRUE)
+
+ SEND_SIGNAL(src, COMSIG_BITRUNNER_CROWBAR_ALERT, pryer)
+
+ if(do_after(pryer, 15 SECONDS, src))
+ if(!state_open)
+ sever_connection()
+ open_machine()
+
+ return TRUE
+
+
+/// Closes the machine without shoving in an occupant
+/obj/machinery/netpod/proc/shut_pod()
+ state_open = FALSE
+ playsound(src, 'sound/machines/tramclose.ogg', 60, TRUE, frequency = 65000)
+ flick("[base_icon_state]_closing", src)
+ set_density(TRUE)
+
+ update_appearance()
diff --git a/code/modules/bitrunning/netpod/outfitting.dm b/code/modules/bitrunning/netpod/outfitting.dm
new file mode 100644
index 0000000000000..07f251541980f
--- /dev/null
+++ b/code/modules/bitrunning/netpod/outfitting.dm
@@ -0,0 +1,30 @@
+/// Creates a list of outfit entries for the UI.
+/obj/machinery/netpod/proc/make_outfit_collection(identifier, list/outfit_list)
+ var/list/collection = list(
+ "name" = identifier,
+ "outfits" = list()
+ )
+
+ for(var/datum/outfit/outfit as anything in outfit_list)
+ var/outfit_name = initial(outfit.name)
+ if(findtext(outfit_name, "(") != 0 || findtext(outfit_name, "-") != 0) // No special variants please
+ continue
+
+ collection["outfits"] += list(list("path" = outfit, "name" = outfit_name))
+
+ return list(collection)
+
+
+/// Resolves a path to an outfit.
+/obj/machinery/netpod/proc/resolve_outfit(text)
+ var/path = text2path(text)
+ if(!ispath(path, /datum/outfit))
+ return
+
+ for(var/wardrobe in cached_outfits)
+ for(var/outfit in wardrobe["outfits"])
+ if(path == outfit["path"])
+ return path
+
+ message_admins("[usr]:[usr.ckey] attempted to select an unavailable outfit from a netpod")
+ return
diff --git a/code/modules/bitrunning/netpod/signals.dm b/code/modules/bitrunning/netpod/signals.dm
new file mode 100644
index 0000000000000..8f8416aeb7b1b
--- /dev/null
+++ b/code/modules/bitrunning/netpod/signals.dm
@@ -0,0 +1,64 @@
+/// Machine has been broken - handles signals and reverting sprites
+/obj/machinery/netpod/proc/on_broken(datum/source)
+ SIGNAL_HANDLER
+
+ sever_connection()
+
+
+/// Checks the integrity, alerts occupants
+/obj/machinery/netpod/proc/on_damage_taken(datum/source, damage_amount)
+ SIGNAL_HANDLER
+
+ if(isnull(occupant) || !connected)
+ return
+
+ var/total = max_integrity - damage_amount
+ var/integrity = (atom_integrity / total) * 100
+ if(integrity > 50)
+ return
+
+ SEND_SIGNAL(src, COMSIG_BITRUNNER_NETPOD_INTEGRITY)
+
+
+/// Puts points on the current occupant's card account
+/obj/machinery/netpod/proc/on_domain_complete(datum/source, atom/movable/crate, reward_points)
+ SIGNAL_HANDLER
+
+ if(isnull(occupant) || !connected)
+ return
+
+ var/mob/living/player = occupant
+
+ var/datum/bank_account/account = player.get_bank_account()
+ if(isnull(account))
+ return
+
+ account.bitrunning_points += reward_points * 100
+
+
+/// The domain has been fully purged, so we should double check our avatar is deleted
+/obj/machinery/netpod/proc/on_domain_scrubbed(datum/source)
+ SIGNAL_HANDLER
+
+ var/mob/avatar = avatar_ref?.resolve()
+ if(isnull(avatar))
+ return
+
+ QDEL_NULL(avatar)
+
+
+/// Boots out anyone in the machine && opens it
+/obj/machinery/netpod/proc/on_power_loss(datum/source)
+ SIGNAL_HANDLER
+
+ if(state_open)
+ return
+
+ if(isnull(occupant) || !connected)
+ connected = FALSE
+ open_machine()
+ return
+
+ sever_connection()
+
+
diff --git a/code/modules/bitrunning/netpod/tools.dm b/code/modules/bitrunning/netpod/tools.dm
new file mode 100644
index 0000000000000..0d31bdab86651
--- /dev/null
+++ b/code/modules/bitrunning/netpod/tools.dm
@@ -0,0 +1,22 @@
+/obj/machinery/netpod/crowbar_act(mob/living/user, obj/item/tool)
+ if(user.combat_mode)
+ attack_hand(user)
+ return ITEM_INTERACT_SUCCESS
+
+ if(default_pry_open(tool, user) || default_deconstruction_crowbar(tool))
+ return ITEM_INTERACT_SUCCESS
+
+
+/obj/machinery/netpod/screwdriver_act(mob/living/user, obj/item/tool)
+ if(occupant)
+ balloon_alert(user, "in use!")
+ return ITEM_INTERACT_SUCCESS
+
+ if(state_open)
+ balloon_alert(user, "close first.")
+ return ITEM_INTERACT_SUCCESS
+
+ if(default_deconstruction_screwdriver(user, "[base_icon_state]_panel", "[base_icon_state]_closed", tool))
+ update_appearance() // sometimes icon doesnt properly update during flick()
+ ui_close(user)
+ return ITEM_INTERACT_SUCCESS
diff --git a/code/modules/bitrunning/netpod/ui.dm b/code/modules/bitrunning/netpod/ui.dm
new file mode 100644
index 0000000000000..93719fe64ebef
--- /dev/null
+++ b/code/modules/bitrunning/netpod/ui.dm
@@ -0,0 +1,41 @@
+/obj/machinery/netpod/ui_interact(mob/user, datum/tgui/ui)
+ if(!is_operational || occupant)
+ return
+
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "NetpodOutfits")
+ ui.set_autoupdate(FALSE)
+ ui.open()
+
+
+/obj/machinery/netpod/ui_data()
+ var/list/data = list()
+
+ data["netsuit"] = netsuit
+ return data
+
+
+/obj/machinery/netpod/ui_static_data()
+ var/list/data = list()
+
+ if(!length(cached_outfits))
+ cached_outfits += make_outfit_collection("Jobs", subtypesof(/datum/outfit/job))
+
+ data["collections"] = cached_outfits
+
+ return data
+
+
+/obj/machinery/netpod/ui_act(action, params)
+ . = ..()
+ if(.)
+ return TRUE
+ switch(action)
+ if("select_outfit")
+ var/datum/outfit/new_suit = resolve_outfit(params["outfit"])
+ if(new_suit)
+ netsuit = new_suit
+ return TRUE
+
+ return FALSE
diff --git a/code/modules/bitrunning/netpod/utils.dm b/code/modules/bitrunning/netpod/utils.dm
new file mode 100644
index 0000000000000..73040b86b4c6b
--- /dev/null
+++ b/code/modules/bitrunning/netpod/utils.dm
@@ -0,0 +1,144 @@
+/// Puts the occupant in netpod stasis, basically short-circuiting environmental conditions
+/obj/machinery/netpod/proc/add_healing(mob/living/target)
+ if(target != occupant)
+ return
+
+ target.AddComponent(/datum/component/netpod_healing, pod = src)
+ target.playsound_local(src, 'sound/effects/submerge.ogg', 20, vary = TRUE)
+ target.extinguish_mob()
+ update_use_power(ACTIVE_POWER_USE)
+
+
+/// Disconnects the occupant after a certain time so they aren't just hibernating in netpod stasis. A balance change
+/obj/machinery/netpod/proc/auto_disconnect()
+ if(isnull(occupant) || state_open || connected)
+ return
+
+ var/mob/player = occupant
+ player.playsound_local(src, 'sound/effects/splash.ogg', 60, TRUE)
+ to_chat(player, span_notice("The machine disconnects itself and begins to drain."))
+ open_machine()
+
+
+/// Handles occupant post-disconnection effects like damage, sounds, etc
+/obj/machinery/netpod/proc/disconnect_occupant(cause_damage = FALSE)
+ connected = FALSE
+
+ var/mob/living/mob_occupant = occupant
+ if(isnull(occupant) || mob_occupant.stat == DEAD)
+ open_machine()
+ return
+
+ mob_occupant.playsound_local(src, 'sound/magic/blink.ogg', 25, TRUE)
+ mob_occupant.set_static_vision(2 SECONDS)
+ mob_occupant.set_temp_blindness(1 SECONDS)
+ mob_occupant.Paralyze(2 SECONDS)
+
+ if(!is_operational)
+ open_machine()
+ return
+
+ var/heal_time = 1
+ if(mob_occupant.health < mob_occupant.maxHealth)
+ heal_time = (mob_occupant.stat + 2) * 5
+ addtimer(CALLBACK(src, PROC_REF(auto_disconnect)), heal_time SECONDS, TIMER_UNIQUE|TIMER_STOPPABLE|TIMER_DELETE_ME)
+
+ if(!cause_damage)
+ return
+
+ mob_occupant.flash_act(override_blindness_check = TRUE, visual = TRUE)
+ mob_occupant.adjustOrganLoss(ORGAN_SLOT_BRAIN, disconnect_damage)
+ INVOKE_ASYNC(mob_occupant, TYPE_PROC_REF(/mob/living, emote), "scream")
+ to_chat(mob_occupant, span_danger("You've been forcefully disconnected from your avatar! Your thoughts feel scrambled!"))
+
+
+/**
+ * ### Enter Matrix
+ * Finds any current avatars from this chair - or generates a new one
+ *
+ * New avatars cost 1 attempt, and this will eject if there's none left
+ *
+ * Connects the mind to the avatar if everything is ok
+ */
+/obj/machinery/netpod/proc/enter_matrix()
+ var/mob/living/carbon/human/neo = occupant
+ if(!ishuman(neo) || neo.stat == DEAD || isnull(neo.mind))
+ balloon_alert(neo, "неверный пользователь.")
+ return
+
+ var/obj/machinery/quantum_server/server = find_server()
+ if(isnull(server))
+ balloon_alert(neo, "нет подключения к серверу!")
+ return
+
+ var/datum/lazy_template/virtual_domain/generated_domain = server.generated_domain
+ if(isnull(generated_domain) || !server.is_ready)
+ balloon_alert(neo, "ничего не загружено!!")
+ return
+
+ var/mob/living/carbon/current_avatar = avatar_ref?.resolve()
+ if(isnull(current_avatar) || current_avatar.stat != CONSCIOUS) // We need a viable avatar
+ current_avatar = server.start_new_connection(neo, netsuit)
+ if(isnull(current_avatar))
+ balloon_alert(neo, "кончилась пропускная способность!")
+ return
+
+ neo.set_static_vision(2 SECONDS)
+ add_healing(occupant)
+
+ if(!validate_entry(neo, current_avatar))
+ open_machine()
+ return
+
+ current_avatar.AddComponent( \
+ /datum/component/avatar_connection, \
+ old_mind = neo.mind, \
+ old_body = neo, \
+ server = server, \
+ pod = src, \
+ help_text = generated_domain.help_text, \
+ )
+
+ connected = TRUE
+
+
+/// Finds a server and sets the server_ref
+/obj/machinery/netpod/proc/find_server()
+ var/obj/machinery/quantum_server/server = server_ref?.resolve()
+ if(server)
+ return server
+
+ server = locate(/obj/machinery/quantum_server) in oview(4, src)
+ if(isnull(server))
+ return
+
+ server_ref = WEAKREF(server)
+ RegisterSignal(server, COMSIG_MACHINERY_REFRESH_PARTS, PROC_REF(on_server_upgraded))
+ RegisterSignal(server, COMSIG_BITRUNNER_DOMAIN_COMPLETE, PROC_REF(on_domain_complete))
+ RegisterSignal(server, COMSIG_BITRUNNER_DOMAIN_SCRUBBED, PROC_REF(on_domain_scrubbed))
+
+ return server
+
+
+/// Severs the connection with the current avatar
+/obj/machinery/netpod/proc/sever_connection()
+ if(isnull(occupant) || !connected)
+ return
+
+ SEND_SIGNAL(src, COMSIG_BITRUNNER_NETPOD_SEVER)
+
+
+/// Checks for cases to eject/fail connecting an avatar
+/obj/machinery/netpod/proc/validate_entry(mob/living/neo, mob/living/avatar)
+ if(!do_after(neo, 2 SECONDS, src))
+ return FALSE
+
+ // Very invalid
+ if(QDELETED(neo) || QDELETED(avatar) || QDELETED(src) || !is_operational)
+ return FALSE
+
+ // Invalid
+ if(occupant != neo || isnull(neo.mind) || neo.stat > SOFT_CRIT || avatar.stat == DEAD)
+ return FALSE
+
+ return TRUE
diff --git a/code/modules/bitrunning/objects/landmarks.dm b/code/modules/bitrunning/objects/landmarks.dm
index 3b38493edfffa..ea55b96979edd 100644
--- a/code/modules/bitrunning/objects/landmarks.dm
+++ b/code/modules/bitrunning/objects/landmarks.dm
@@ -12,6 +12,11 @@
name = "Bitrunning hololadder spawn"
icon_state = "hololadder"
+/// A permanent exit for the domain
+/obj/effect/landmark/bitrunning/permanent_exit
+ name = "Bitrunning permanent exit"
+ icon_state = "perm_exit"
+
/// Where the crates need to be taken
/obj/effect/landmark/bitrunning/cache_goal_turf
name = "Bitrunning goal turf"
diff --git a/code/modules/bitrunning/objects/netpod.dm b/code/modules/bitrunning/objects/netpod.dm
deleted file mode 100644
index 27b43748d465f..0000000000000
--- a/code/modules/bitrunning/objects/netpod.dm
+++ /dev/null
@@ -1,491 +0,0 @@
-#define BASE_DISCONNECT_DAMAGE 40
-
-/obj/machinery/netpod
- name = "netpod"
-
- base_icon_state = "netpod"
- circuit = /obj/item/circuitboard/machine/netpod
- desc = "Связущее звено с сетевым миром. Здесь есть множество кабелей для подключения себя к виртуальному домену."
- icon = 'icons/obj/machines/bitrunning.dmi'
- icon_state = "netpod"
- max_integrity = 300
- obj_flags = BLOCKS_CONSTRUCTION
- state_open = TRUE
- interaction_flags_mouse_drop = NEED_HANDS | NEED_DEXTERITY
-
- /// Whether we have an ongoing connection
- var/connected = FALSE
- /// A player selected outfit by clicking the netpod
- var/datum/outfit/netsuit = /datum/outfit/job/bitrunner
- /// Holds this to see if it needs to generate a new one
- var/datum/weakref/avatar_ref
- /// The linked quantum server
- var/datum/weakref/server_ref
- /// The amount of brain damage done from force disconnects
- var/disconnect_damage
- /// Static list of outfits to select from
- var/list/cached_outfits = list()
-
-/obj/machinery/netpod/post_machine_initialize()
- . = ..()
-
- disconnect_damage = BASE_DISCONNECT_DAMAGE
- find_server()
-
- RegisterSignal(src, COMSIG_ATOM_TAKE_DAMAGE, PROC_REF(on_damage_taken))
- RegisterSignal(src, COMSIG_MACHINERY_POWER_LOST, PROC_REF(on_power_loss))
- RegisterSignals(src, list(COMSIG_QDELETING, COMSIG_MACHINERY_BROKEN),PROC_REF(on_broken))
-
- register_context()
- update_appearance()
-
-/obj/machinery/netpod/Destroy()
- . = ..()
-
- QDEL_LIST(cached_outfits)
-
-/obj/machinery/netpod/examine(mob/user)
- . = ..()
-
- if(isnull(server_ref?.resolve()))
- . += span_infoplain("Оно ни к чему не подключено.")
- . += span_infoplain("Нетподы должны быть построены на расстоянии 4-х тайлов от сервера.")
- return
-
- if(!isobserver(user))
- . += span_infoplain("Перетащите себя на под, чтобы начать подключение.")
- . += span_infoplain("Под имеет ограниченные возможности реанимации. Нахождение в поде может вылечить некоторые ранения.")
- . += span_infoplain("Имеется система безопасности, оповещающая пользователя, если начнется вмешательство с подом.")
-
- if(isnull(occupant))
- . += span_infoplain("Сейчас внутри пусто.")
- return
-
- . += span_infoplain("Сейчас внутри находится - [occupant].")
-
- if(isobserver(user))
- . += span_notice("Как наблюдатель, вы можете щелкнуть по этому нетподу, чтобы перейти к его аватару.")
- return
-
- . += span_notice("Сейчас внутри находится - [occupant].")
- . += span_notice("Оно может быть насильно открыто монтировкой, но системы безопасности оповестят пользователя.")
-
-
-
-/obj/machinery/netpod/add_context(atom/source, list/context, obj/item/held_item, mob/user)
- . = ..()
-
- if(isnull(held_item))
- context[SCREENTIP_CONTEXT_LMB] = "Выбрать одежду"
- return CONTEXTUAL_SCREENTIP_SET
-
- if(istype(held_item, /obj/item/crowbar) && occupant)
- context[SCREENTIP_CONTEXT_LMB] = "Насильно открыть"
- return CONTEXTUAL_SCREENTIP_SET
-
-
-/obj/machinery/netpod/update_icon_state()
- if(!is_operational)
- icon_state = base_icon_state
- return ..()
-
- if(state_open)
- icon_state = base_icon_state + "_open_active"
- return ..()
-
- if(panel_open)
- icon_state = base_icon_state + "_panel"
- return ..()
-
- icon_state = base_icon_state + "_closed"
- if(occupant)
- icon_state += "_active"
-
- return ..()
-
-/obj/machinery/netpod/mouse_drop_receive(mob/target, mob/user, params)
- var/mob/living/carbon/player = user
-
- if(!iscarbon(player) || !is_operational || !state_open || player.buckled)
- return
-
- close_machine(target)
-
-/obj/machinery/netpod/crowbar_act(mob/living/user, obj/item/tool)
- if(user.combat_mode)
- attack_hand(user)
- return ITEM_INTERACT_SUCCESS
-
- if(default_pry_open(tool, user) || default_deconstruction_crowbar(tool))
- return ITEM_INTERACT_SUCCESS
-
-/obj/machinery/netpod/screwdriver_act(mob/living/user, obj/item/tool)
- if(occupant)
- balloon_alert(user, "in use!")
- return ITEM_INTERACT_SUCCESS
-
- if(state_open)
- balloon_alert(user, "close first.")
- return ITEM_INTERACT_SUCCESS
-
- if(default_deconstruction_screwdriver(user, "[base_icon_state]_panel", "[base_icon_state]_closed", tool))
- update_appearance() // sometimes icon doesnt properly update during flick()
- ui_close(user)
- return ITEM_INTERACT_SUCCESS
-
-/obj/machinery/netpod/attack_hand(mob/living/user, list/modifiers)
- . = ..()
- if(!state_open && user == occupant)
- container_resist_act(user)
-
-/obj/machinery/netpod/Exited(atom/movable/gone, direction)
- . = ..()
- if(!state_open && gone == occupant)
- container_resist_act(gone)
-
-/obj/machinery/netpod/relaymove(mob/living/user, direction)
- if(!state_open)
- container_resist_act(user)
-
-/obj/machinery/netpod/container_resist_act(mob/living/user)
- user.visible_message(span_notice("[occupant] emerges from [src]!"),
- span_notice("You climb out of [src]!"),
- span_notice("With a hiss, you hear a machine opening."))
- open_machine()
-
-/obj/machinery/netpod/open_machine(drop = TRUE, density_to_set = FALSE)
- playsound(src, 'sound/machines/tramopen.ogg', 60, TRUE, frequency = 65000)
- flick("[base_icon_state]_opening", src)
- SEND_SIGNAL(src, COMSIG_BITRUNNER_NETPOD_OPENED)
- update_use_power(IDLE_POWER_USE)
-
- return ..()
-
-/obj/machinery/netpod/close_machine(mob/user, density_to_set = TRUE)
- if(!state_open || panel_open || !is_operational || !iscarbon(user))
- return
-
- playsound(src, 'sound/machines/tramclose.ogg', 60, TRUE, frequency = 65000)
- flick("[base_icon_state]_closing", src)
- ..()
-
- enter_matrix()
-
-/obj/machinery/netpod/default_pry_open(obj/item/crowbar, mob/living/pryer)
- if(isnull(occupant) || !iscarbon(occupant))
- if(!state_open)
- if(panel_open)
- return FALSE
- open_machine()
- else
- shut_pod()
-
- return TRUE
-
- pryer.visible_message(
- span_danger("[pryer] starts prying open [src]!"),
- span_notice("You start to pry open [src]."),
- span_notice("You hear loud prying on metal.")
- )
- playsound(src, 'sound/machines/airlock_alien_prying.ogg', 100, TRUE)
-
- SEND_SIGNAL(src, COMSIG_BITRUNNER_CROWBAR_ALERT, pryer)
-
- if(do_after(pryer, 15 SECONDS, src))
- if(!state_open)
- sever_connection()
- open_machine()
-
- return TRUE
-
-/obj/machinery/netpod/ui_interact(mob/user, datum/tgui/ui)
- if(!is_operational || occupant)
- return
-
- ui = SStgui.try_update_ui(user, src, ui)
- if(!ui)
- ui = new(user, src, "NetpodOutfits")
- ui.set_autoupdate(FALSE)
- ui.open()
-
-/obj/machinery/netpod/ui_data()
- var/list/data = list()
-
- data["netsuit"] = netsuit
- return data
-
-/obj/machinery/netpod/ui_static_data()
- var/list/data = list()
-
- if(!length(cached_outfits))
- cached_outfits += make_outfit_collection("Jobs", subtypesof(/datum/outfit/job))
-
- data["collections"] = cached_outfits
-
- return data
-
-/obj/machinery/netpod/ui_act(action, params)
- . = ..()
- if(.)
- return TRUE
- switch(action)
- if("select_outfit")
- var/datum/outfit/new_suit = resolve_outfit(params["outfit"])
- if(new_suit)
- netsuit = new_suit
- return TRUE
-
- return FALSE
-
-/obj/machinery/netpod/attack_ghost(mob/dead/observer/our_observer)
- var/our_target = avatar_ref?.resolve()
- if(isnull(our_target) || !our_observer.orbit(our_target))
- return ..()
-
-/// Puts the occupant in netpod stasis, basically short-circuiting environmental conditions
-/obj/machinery/netpod/proc/add_healing(mob/living/target)
- if(target != occupant)
- return
-
- target.AddComponent(/datum/component/netpod_healing, pod = src)
- target.playsound_local(src, 'sound/effects/submerge.ogg', 20, vary = TRUE)
- target.extinguish_mob()
- update_use_power(ACTIVE_POWER_USE)
-
-/// Disconnects the occupant after a certain time so they aren't just hibernating in netpod stasis. A balance change
-/obj/machinery/netpod/proc/auto_disconnect()
- if(isnull(occupant) || state_open || connected)
- return
-
- var/mob/player = occupant
- player.playsound_local(src, 'sound/effects/splash.ogg', 60, TRUE)
- to_chat(player, span_notice("The machine disconnects itself and begins to drain."))
- open_machine()
-
-/// Handles occupant post-disconnection effects like damage, sounds, etc
-/obj/machinery/netpod/proc/disconnect_occupant(cause_damage = FALSE)
- connected = FALSE
-
- var/mob/living/mob_occupant = occupant
- if(isnull(occupant) || mob_occupant.stat == DEAD)
- open_machine()
- return
-
- mob_occupant.playsound_local(src, 'sound/magic/blink.ogg', 25, TRUE)
- mob_occupant.set_static_vision(2 SECONDS)
- mob_occupant.set_temp_blindness(1 SECONDS)
- mob_occupant.Paralyze(2 SECONDS)
-
- if(!is_operational)
- open_machine()
- return
-
- var/heal_time = 1
- if(mob_occupant.health < mob_occupant.maxHealth)
- heal_time = (mob_occupant.stat + 2) * 5
- addtimer(CALLBACK(src, PROC_REF(auto_disconnect)), heal_time SECONDS, TIMER_UNIQUE|TIMER_STOPPABLE|TIMER_DELETE_ME)
-
- if(!cause_damage)
- return
-
- mob_occupant.flash_act(override_blindness_check = TRUE, visual = TRUE)
- mob_occupant.adjustOrganLoss(ORGAN_SLOT_BRAIN, disconnect_damage)
- INVOKE_ASYNC(mob_occupant, TYPE_PROC_REF(/mob/living, emote), "scream")
- to_chat(mob_occupant, span_danger("You've been forcefully disconnected from your avatar! Your thoughts feel scrambled!"))
-
-/**
- * ### Enter Matrix
- * Finds any current avatars from this chair - or generates a new one
- *
- * New avatars cost 1 attempt, and this will eject if there's none left
- *
- * Connects the mind to the avatar if everything is ok
- */
-/obj/machinery/netpod/proc/enter_matrix()
- var/mob/living/carbon/human/neo = occupant
- if(!ishuman(neo) || neo.stat == DEAD || isnull(neo.mind))
- balloon_alert(neo, "неверный пользователь.")
- return
-
- var/obj/machinery/quantum_server/server = find_server()
- if(isnull(server))
- balloon_alert(neo, "нет подключения к серверу!")
- return
-
- var/datum/lazy_template/virtual_domain/generated_domain = server.generated_domain
- if(isnull(generated_domain) || !server.is_ready)
- balloon_alert(neo, "ничего не загружено!")
- return
-
- var/mob/living/carbon/current_avatar = avatar_ref?.resolve()
- if(isnull(current_avatar) || current_avatar.stat != CONSCIOUS) // We need a viable avatar
- var/obj/structure/hololadder/wayout = server.generate_hololadder()
- if(isnull(wayout))
- balloon_alert(neo, "кончилась пропускная способность!")
- return
- current_avatar = server.generate_avatar(wayout, netsuit)
- avatar_ref = WEAKREF(current_avatar)
- server.stock_gear(current_avatar, neo, generated_domain)
-
- neo.set_static_vision(3 SECONDS)
- add_healing(occupant)
-
- if(!validate_entry(neo, current_avatar))
- open_machine()
- return
-
- current_avatar.AddComponent( \
- /datum/component/avatar_connection, \
- old_mind = neo.mind, \
- old_body = neo, \
- server = server, \
- pod = src, \
- help_text = generated_domain.help_text, \
- )
-
- connected = TRUE
-
-/// Finds a server and sets the server_ref
-/obj/machinery/netpod/proc/find_server()
- var/obj/machinery/quantum_server/server = server_ref?.resolve()
- if(server)
- return server
-
- server = locate(/obj/machinery/quantum_server) in oview(4, src)
- if(isnull(server))
- return
-
- server_ref = WEAKREF(server)
- RegisterSignal(server, COMSIG_MACHINERY_REFRESH_PARTS, PROC_REF(on_server_upgraded))
- RegisterSignal(server, COMSIG_BITRUNNER_DOMAIN_COMPLETE, PROC_REF(on_domain_complete))
- RegisterSignal(server, COMSIG_BITRUNNER_DOMAIN_SCRUBBED, PROC_REF(on_domain_scrubbed))
-
- return server
-
-/// Creates a list of outfit entries for the UI.
-/obj/machinery/netpod/proc/make_outfit_collection(identifier, list/outfit_list)
- var/list/collection = list(
- "name" = identifier,
- "outfits" = list()
- )
-
- for(var/datum/outfit/outfit as anything in outfit_list)
- var/outfit_name = initial(outfit.name)
- if(findtext(outfit_name, "(") != 0 || findtext(outfit_name, "-") != 0) // No special variants please
- continue
-
- collection["outfits"] += list(list("path" = outfit, "name" = outfit_name))
-
- return list(collection)
-
-/// Machine has been broken - handles signals and reverting sprites
-/obj/machinery/netpod/proc/on_broken(datum/source)
- SIGNAL_HANDLER
-
- sever_connection()
-
-/// Checks the integrity, alerts occupants
-/obj/machinery/netpod/proc/on_damage_taken(datum/source, damage_amount)
- SIGNAL_HANDLER
-
- if(isnull(occupant) || !connected)
- return
-
- var/total = max_integrity - damage_amount
- var/integrity = (atom_integrity / total) * 100
- if(integrity > 50)
- return
-
- SEND_SIGNAL(src, COMSIG_BITRUNNER_NETPOD_INTEGRITY)
-
-/// Puts points on the current occupant's card account
-/obj/machinery/netpod/proc/on_domain_complete(datum/source, atom/movable/crate, reward_points)
- SIGNAL_HANDLER
-
- if(isnull(occupant) || !connected)
- return
-
- var/mob/living/player = occupant
-
- var/datum/bank_account/account = player.get_bank_account()
- if(isnull(account))
- return
-
- account.bitrunning_points += reward_points * 100
-
-/// The domain has been fully purged, so we should double check our avatar is deleted
-/obj/machinery/netpod/proc/on_domain_scrubbed(datum/source)
- SIGNAL_HANDLER
-
- var/mob/avatar = avatar_ref?.resolve()
- if(isnull(avatar))
- return
-
- QDEL_NULL(avatar)
-
-/// Boots out anyone in the machine && opens it
-/obj/machinery/netpod/proc/on_power_loss(datum/source)
- SIGNAL_HANDLER
-
- if(state_open)
- return
-
- if(isnull(occupant) || !connected)
- connected = FALSE
- open_machine()
- return
-
- sever_connection()
-
-/// When the server is upgraded, drops brain damage a little
-/obj/machinery/netpod/proc/on_server_upgraded(obj/machinery/quantum_server/source)
- SIGNAL_HANDLER
-
- disconnect_damage = BASE_DISCONNECT_DAMAGE * (1 - source.servo_bonus)
-
-/// Resolves a path to an outfit.
-/obj/machinery/netpod/proc/resolve_outfit(text)
- var/path = text2path(text)
- if(!ispath(path, /datum/outfit))
- return
-
- for(var/wardrobe in cached_outfits)
- for(var/outfit in wardrobe["outfits"])
- if(path == outfit["path"])
- return path
-
- message_admins("[usr]:[usr.ckey] attempted to select an unavailable outfit from a netpod")
- return
-
-/// Severs the connection with the current avatar
-/obj/machinery/netpod/proc/sever_connection()
- if(isnull(occupant) || !connected)
- return
-
- SEND_SIGNAL(src, COMSIG_BITRUNNER_NETPOD_SEVER)
-
-/// Closes the machine without shoving in an occupant
-/obj/machinery/netpod/proc/shut_pod()
- state_open = FALSE
- playsound(src, 'sound/machines/tramclose.ogg', 60, TRUE, frequency = 65000)
- flick("[base_icon_state]_closing", src)
- set_density(TRUE)
-
- update_appearance()
-
-/// Checks for cases to eject/fail connecting an avatar
-/obj/machinery/netpod/proc/validate_entry(mob/living/neo, mob/living/avatar)
- if(!do_after(neo, 2 SECONDS, src))
- return FALSE
-
- // Very invalid
- if(QDELETED(neo) || QDELETED(avatar) || QDELETED(src) || !is_operational)
- return FALSE
-
- // Invalid
- if(occupant != neo || isnull(neo.mind) || neo.stat > SOFT_CRIT || avatar.stat == DEAD)
- return FALSE
-
- return TRUE
-
-#undef BASE_DISCONNECT_DAMAGE
diff --git a/code/modules/bitrunning/outfits.dm b/code/modules/bitrunning/outfits.dm
index 41a65b228ff44..c0bb01ebc06e5 100644
--- a/code/modules/bitrunning/outfits.dm
+++ b/code/modules/bitrunning/outfits.dm
@@ -10,12 +10,62 @@
suit = /obj/item/clothing/suit/jacket/trenchcoat
id = /obj/item/card/id/advanced
+
/datum/outfit/echolocator/post_equip(mob/living/carbon/human/user, visualsOnly)
. = ..()
user.psykerize()
+
/datum/outfit/bitductor
name = "Bitrunning Abductor"
uniform = /obj/item/clothing/under/abductor
gloves = /obj/item/clothing/gloves/fingerless
shoes = /obj/item/clothing/shoes/jackboots
+
+
+/datum/outfit/beachbum_combat
+ name = "Beachbum: Island Combat"
+ id = /obj/item/card/id/advanced
+ l_pocket = null
+ r_pocket = null
+ shoes = /obj/item/clothing/shoes/sandal
+ uniform = /obj/item/clothing/under/pants/jeans
+ /// Available ranged weapons
+ var/list/ranged_weaps = list(
+ /obj/item/gun/ballistic/automatic/pistol,
+ /obj/item/gun/ballistic/rifle/boltaction,
+ /obj/item/gun/ballistic/automatic/mini_uzi,
+ /obj/item/gun/ballistic/automatic/pistol/deagle,
+ /obj/item/gun/ballistic/rocketlauncher/unrestricted,
+ /obj/item/gun/ballistic/automatic/ar,
+
+ )
+ /// Corresponding ammo
+ var/list/corresponding_ammo = list(
+ /obj/item/ammo_box/magazine/m9mm,
+ /obj/item/ammo_box/strilka310,
+ /obj/item/ammo_box/magazine/uzim9mm,
+ /obj/item/ammo_box/magazine/m50,
+ /obj/item/food/pizzaslice/dank, // more silly, less destructive
+ /obj/item/ammo_box/magazine/m223,
+ )
+
+
+/datum/outfit/beachbum_combat/post_equip(mob/living/carbon/human/bum, visualsOnly)
+ . = ..()
+
+ var/choice = rand(1, length(ranged_weaps))
+ var/weapon = ranged_weaps[choice]
+ bum.put_in_active_hand(new weapon)
+
+ var/ammo = corresponding_ammo[choice]
+ var/obj/item/ammo1 = new ammo
+ var/obj/item/ammo2 = new ammo
+
+ if(!bum.equip_to_slot_if_possible(new ammo, ITEM_SLOT_LPOCKET))
+ ammo1.forceMove(get_turf(bum))
+ if(!bum.equip_to_slot_if_possible(new ammo, ITEM_SLOT_RPOCKET))
+ ammo2.forceMove(get_turf(bum))
+
+ if(prob(50))
+ bum.equip_to_slot_if_possible(new /obj/item/clothing/glasses/sunglasses, ITEM_SLOT_EYES)
diff --git a/code/modules/bitrunning/server/_parent.dm b/code/modules/bitrunning/server/_parent.dm
index 776bae1db4434..f848a6bb966b6 100644
--- a/code/modules/bitrunning/server/_parent.dm
+++ b/code/modules/bitrunning/server/_parent.dm
@@ -77,12 +77,18 @@
. += span_infoplain("Может требовать много ресурсов при работе. Обеспечьте достаточное энергоснабжение.")
+ var/upgraded = FALSE
if(capacitor_coefficient < 1)
. += span_infoplain("- Вместимость охладителя уменьшает время задержки на [(1 - capacitor_coefficient) * 100]%.")
+ upgraded = TRUE
if(servo_bonus > 0.2)
. += span_infoplain("- Потенциал манипуляторов увеличивает награду на [servo_bonus]x.")
. += span_infoplain("- Повреждения, получаемые при небезопасном выходе, уменьшены на [servo_bonus * 100]%.")
+ upgraded = TRUE
+
+ if(!upgraded)
+ . += span_notice("Его производительность неоптимальна. Улучшенные компоненты дадут информацию о домене, сократят время действия и увеличат награду.")
if(!is_ready)
. += span_notice("Сервер охлаждается, пожалуйста, ожидайте.")
diff --git a/code/modules/bitrunning/server/loot.dm b/code/modules/bitrunning/server/loot.dm
index c607b784b8cc5..a340f69962510 100644
--- a/code/modules/bitrunning/server/loot.dm
+++ b/code/modules/bitrunning/server/loot.dm
@@ -4,6 +4,7 @@
#define GRADE_A "A"
#define GRADE_S "S"
+
/// Handles calculating rewards based on number of players, parts, threats, etc
/obj/machinery/quantum_server/proc/calculate_rewards()
var/rewards_base = 0.8
@@ -20,6 +21,7 @@
return rewards_base
+
/// Handles spawning the (new) crate and deleting the former
/obj/machinery/quantum_server/proc/generate_loot(obj/cache, obj/machinery/byteforge/chosen_forge)
SSblackbox.record_feedback("tally", "bitrunning_domain_primary_completed", 1, generated_domain.key)
@@ -55,6 +57,8 @@
chosen_forge.start_to_spawn(reward_cache)
return TRUE
+
+/// Builds secondary loot if the achievements were met
/obj/machinery/quantum_server/proc/generate_secondary_loot(obj/curiosity, obj/machinery/byteforge/chosen_forge)
SSblackbox.record_feedback("tally", "bitrunning_domain_secondary_completed", 1, generated_domain.key)
spark_at_location(curiosity) // abracadabra!
@@ -65,6 +69,7 @@
chosen_forge.start_to_spawn(reward_curiosity)
return TRUE
+
/// Returns the markdown text containing domain completion information
/obj/machinery/quantum_server/proc/get_completion_certificate(time_difference, grade)
var/base_points = generated_domain.reward_points
@@ -126,6 +131,7 @@
return generated_domain.difficulty >= BITRUNNER_DIFFICULTY_MEDIUM && (grade in passing_grades)
+
/// Grades the player's run based on several factors
/obj/machinery/quantum_server/proc/grade_completion(completion_time)
var/score = length(spawned_threat_refs) * 5
diff --git a/code/modules/bitrunning/server/map_handling.dm b/code/modules/bitrunning/server/map_handling.dm
index 06f04a37b119c..c2756b4f5df29 100644
--- a/code/modules/bitrunning/server/map_handling.dm
+++ b/code/modules/bitrunning/server/map_handling.dm
@@ -24,6 +24,7 @@
reset()
+
/// Links all the loading processes together - does validation for booting a map
/obj/machinery/quantum_server/proc/cold_boot_map(map_key)
if(!is_ready)
@@ -69,8 +70,15 @@
if(broadcasting)
start_broadcasting_network(BITRUNNER_CAMERA_NET)
+ if(generated_domain.announce_to_ghosts)
+ notify_ghosts("Bitrunners have loaded a domain that offers ghost interactions. Check the spawners menu for more information.",
+ src,
+ "Matrix Glitch",
+ )
+
return TRUE
+
/// Initializes a new domain if the given key is valid and the user has enough points
/obj/machinery/quantum_server/proc/load_domain(map_key)
for(var/datum/lazy_template/virtual_domain/available in SSbitrunning.all_domains)
@@ -82,6 +90,7 @@
return FALSE
+
/// Loads in necessary map items like hololadder spawns, caches, etc
/obj/machinery/quantum_server/proc/load_map_items()
var/turf/goal_turfs = list()
@@ -116,6 +125,15 @@
var/turf/signaler_turf = get_turf(thing)
signaler_turf.AddComponent(/datum/component/bitrunning_points, generated_domain)
qdel(thing)
+ continue
+
+ if(istype(thing, /obj/effect/landmark/bitrunning/permanent_exit))
+ var/turf/tile = get_turf(thing)
+ exit_turfs += tile
+ qdel(thing)
+
+ new /obj/structure/hololadder(tile)
+
if(!length(exit_turfs))
CRASH("Failed to find exit turfs on generated domain.")
@@ -134,6 +152,7 @@
return TRUE
+
/// Stops the current virtual domain and disconnects all users
/obj/machinery/quantum_server/proc/reset(fast = FALSE)
is_ready = FALSE
@@ -155,6 +174,7 @@
stop_broadcasting_network(BITRUNNER_CAMERA_NET)
+
/// Tries to clean up everything in the domain
/obj/machinery/quantum_server/proc/scrub_vdom()
sever_connections() /// just in case someone's connected
diff --git a/code/modules/bitrunning/server/obj_generation.dm b/code/modules/bitrunning/server/obj_generation.dm
index 232847e29bd86..327c9785c5b61 100644
--- a/code/modules/bitrunning/server/obj_generation.dm
+++ b/code/modules/bitrunning/server/obj_generation.dm
@@ -15,6 +15,7 @@
new /obj/structure/closet/crate/secure/bitrunning/encrypted(chosen_turf)
return TRUE
+
/// Attempts to spawn a lootbox
/obj/machinery/quantum_server/proc/attempt_spawn_curiosity(list/possible_turfs)
if(!length(possible_turfs)) // Out of turfs to place a curiosity
@@ -35,9 +36,10 @@
new /obj/item/storage/lockbox/bitrunning/encrypted(chosen_turf)
return chosen_turf
+
/// Generates a new avatar for the bitrunner.
-/obj/machinery/quantum_server/proc/generate_avatar(obj/structure/hololadder/wayout, datum/outfit/netsuit)
- var/mob/living/carbon/human/avatar = new(wayout.loc)
+/obj/machinery/quantum_server/proc/generate_avatar(turf/destination, datum/outfit/netsuit)
+ var/mob/living/carbon/human/avatar = new(destination)
var/outfit_path = generated_domain.forced_outfit || netsuit
var/datum/outfit/to_wear = new outfit_path()
@@ -61,8 +63,9 @@
if(istype(hat))
hat.set_armor(/datum/armor/none)
- for(var/obj/thing in avatar.held_items)
- qdel(thing)
+ if(!generated_domain.forced_outfit)
+ for(var/obj/thing in avatar.held_items)
+ qdel(thing)
var/obj/item/storage/backpack/bag = avatar.back
if(istype(bag))
@@ -88,32 +91,9 @@
network = BITRUNNER_CAMERA_NET, \
emp_proof = TRUE, \
)
- return avatar
-
-/// Generates a new hololadder for the bitrunner. Effectively a respawn attempt.
-/obj/machinery/quantum_server/proc/generate_hololadder()
- if(!length(exit_turfs))
- return
-
- if(retries_spent >= length(exit_turfs))
- return
- var/turf/destination
- for(var/turf/dest_turf in exit_turfs)
- if(!locate(/obj/structure/hololadder) in dest_turf)
- destination = dest_turf
- break
-
- if(isnull(destination))
- return
-
- var/obj/structure/hololadder/wayout = new(destination, src)
- if(isnull(wayout))
- return
-
- retries_spent += 1
+ return avatar
- return wayout
/// Loads in any mob segments of the map
/obj/machinery/quantum_server/proc/load_mob_segments()
@@ -142,6 +122,7 @@
return TRUE
+
/// Scans over neo's contents for bitrunning tech disks. Loads the items or abilities onto the avatar.
/obj/machinery/quantum_server/proc/stock_gear(mob/living/carbon/human/avatar, mob/living/carbon/human/neo, datum/lazy_template/virtual_domain/generated_domain)
var/domain_forbids_items = generated_domain.forbids_disk_items
diff --git a/code/modules/bitrunning/server/signal_handlers.dm b/code/modules/bitrunning/server/signal_handlers.dm
index 0e5e949e8bb51..f5d2840143e16 100644
--- a/code/modules/bitrunning/server/signal_handlers.dm
+++ b/code/modules/bitrunning/server/signal_handlers.dm
@@ -4,12 +4,14 @@
sever_connections()
+
/// Whenever a corpse spawner makes a new corpse, add it to the list of potential mutations
/obj/machinery/quantum_server/proc/on_corpse_spawned(datum/source, mob/living/corpse)
SIGNAL_HANDLER
mutation_candidate_refs.Add(WEAKREF(corpse))
+
/// Being qdeleted - make sure the circuit and connected mobs go with it
/obj/machinery/quantum_server/proc/on_delete(datum/source)
SIGNAL_HANDLER
@@ -26,6 +28,7 @@
if(circuit)
qdel(circuit)
+
/// Whenever something enters the send tiles, check if it's a loot crate. If so, alert players.
/obj/machinery/quantum_server/proc/on_goal_turf_entered(datum/source, atom/movable/arrived, atom/old_loc, list/atom/old_locs)
SIGNAL_HANDLER
@@ -51,6 +54,7 @@
generate_secondary_loot(arrived, chosen_forge, generated_domain)
return
+
/// Handles examining the server. Shows cooldown time and efficiency.
/obj/machinery/quantum_server/proc/on_goal_turf_examined(datum/source, mob/examiner, list/examine_text)
SIGNAL_HANDLER
@@ -58,6 +62,7 @@
examine_text += span_info("Beneath your gaze, the floor pulses subtly with streams of encoded data.")
examine_text += span_info("It seems to be part of the location designated for retrieving encrypted payloads.")
+
/// Scans over the inbound created_atoms from lazy templates
/obj/machinery/quantum_server/proc/on_template_loaded(datum/lazy_template/source, list/created_atoms)
SIGNAL_HANDLER
@@ -98,6 +103,7 @@
/// Just in case there's any special handling for the domain
generated_domain.setup_domain(created_atoms)
+
/// Handles when cybercops are summoned into the area or ghosts click a ghost role spawner
/obj/machinery/quantum_server/proc/on_threat_created(datum/source, mob/living/threat)
SIGNAL_HANDLER
diff --git a/code/modules/bitrunning/server/threats.dm b/code/modules/bitrunning/server/threats.dm
index 25a891c03b24b..28d91aa4b3714 100644
--- a/code/modules/bitrunning/server/threats.dm
+++ b/code/modules/bitrunning/server/threats.dm
@@ -4,6 +4,7 @@
SEND_SIGNAL(src, COMSIG_BITRUNNER_THREAT_CREATED)
threat.AddComponent(/datum/component/virtual_entity, src)
+
/// Choses which antagonist role is spawned based on threat
/obj/machinery/quantum_server/proc/get_antagonist_role()
var/list/available = list()
@@ -19,6 +20,7 @@
return chosen
+
/// Selects a target to mutate. Gives two attempts, then crashes if it fails.
/obj/machinery/quantum_server/proc/get_mutation_target()
var/datum/weakref/target_ref = pick(mutation_candidate_refs)
@@ -35,6 +37,7 @@
resolved = target_ref.resolve()
return resolved
+
/// Finds any mobs with minds in the zones and gives them the bad news
/obj/machinery/quantum_server/proc/notify_spawned_threats()
for(var/datum/weakref/baddie_ref as anything in spawned_threat_refs)
@@ -52,10 +55,12 @@
to_chat(baddie, span_userdanger("You have been flagged for deletion! Thank you for your service."))
+
/// Removes a specific threat - used when station spawning
/obj/machinery/quantum_server/proc/remove_threat(mob/living/threat)
spawned_threat_refs.Remove(WEAKREF(threat))
+
/// Selects the role and waits for a ghost orbiter
/obj/machinery/quantum_server/proc/setup_glitch(datum/antagonist/bitrunning_glitch/forced_role)
if(!validate_mutation_candidates())
@@ -83,6 +88,7 @@
spawn_glitch(chosen_role, mutation_target, chosen_one)
return mutation_target
+
/// Orbit poll has concluded - spawn the antag
/obj/machinery/quantum_server/proc/spawn_glitch(datum/antagonist/bitrunning_glitch/chosen_role, mob/living/mutation_target, mob/dead/observer/ghost)
if(QDELETED(mutation_target))
@@ -121,6 +127,7 @@
add_threats(new_mob)
+
/// Oh boy - transports the antag station side
/obj/machinery/quantum_server/proc/station_spawn(mob/living/antag, obj/machinery/byteforge/chosen_forge)
antag.balloon_alert(antag, "scanning...")
@@ -165,6 +172,7 @@
do_teleport(antag, get_turf(chosen_forge), forced = TRUE, asoundin = 'sound/magic/ethereal_enter.ogg', asoundout = 'sound/magic/ethereal_exit.ogg', channel = TELEPORT_CHANNEL_QUANTUM)
+
/// Removes any invalid candidates from the list
/obj/machinery/quantum_server/proc/validate_mutation_candidates()
for(var/datum/weakref/creature_ref as anything in mutation_candidate_refs)
diff --git a/code/modules/bitrunning/server/util.dm b/code/modules/bitrunning/server/util.dm
index 6b5352bde6cb1..ab267a34cd330 100644
--- a/code/modules/bitrunning/server/util.dm
+++ b/code/modules/bitrunning/server/util.dm
@@ -1,11 +1,13 @@
#define MAX_DISTANCE 4 // How far crates can spawn from the server
+
/// Resets the cooldown state and updates icons
/obj/machinery/quantum_server/proc/cool_off()
is_ready = TRUE
update_appearance()
radio.talk_into(src, "Thermal systems within operational parameters. Proceeding to domain configuration.", RADIO_CHANNEL_SUPPLY)
+
/// If there are hosted minds, attempts to get a list of their current virtual bodies w/ vitals
/obj/machinery/quantum_server/proc/get_avatar_data()
var/list/hosted_avatars = list()
@@ -31,6 +33,49 @@
return hosted_avatars
+
+/// I grab the atom here so I can signal it / manipulate spawners etc
+/obj/machinery/quantum_server/proc/get_avatar_destination() as /atom
+ // Branch A: Custom spawns
+ if(length(generated_domain.custom_spawns))
+ var/atom/valid_spawner
+
+ while(isnull(valid_spawner))
+ var/atom/chosen = pick(generated_domain.custom_spawns)
+ if(QDELETED(chosen))
+ generated_domain.custom_spawns -= chosen
+ continue
+
+ valid_spawner = chosen
+ break
+
+ return valid_spawner
+
+ // Branch B: Hololadders
+ if(!length(exit_turfs))
+ return
+
+ if(retries_spent >= length(exit_turfs))
+ return
+
+ var/turf/exit_tile
+ for(var/turf/dest_turf in exit_turfs)
+ if(!locate(/obj/structure/hololadder) in dest_turf)
+ exit_tile = dest_turf
+ break
+
+ if(isnull(exit_tile))
+ return
+
+ var/obj/structure/hololadder/wayout = new(exit_tile, src)
+ if(isnull(wayout))
+ return
+
+ retries_spent += 1
+
+ return wayout
+
+
/// Locates any turfs with forges on them, returns a random one
/obj/machinery/quantum_server/proc/get_random_nearby_forge()
var/list/nearby_forges = list()
@@ -40,6 +85,7 @@
return pick(nearby_forges)
+
/// Gets a random available domain given the current points.
/obj/machinery/quantum_server/proc/get_random_domain_id()
if(points < 1)
@@ -85,6 +131,7 @@
SEND_SIGNAL(src, COMSIG_BITRUNNER_QSRV_SEVER)
+
/// Do some magic teleport sparks
/obj/machinery/quantum_server/proc/spark_at_location(obj/cache)
playsound(cache, 'sound/magic/blink.ogg', 50, vary = TRUE)
@@ -92,16 +139,33 @@
sparks.set_up(5, location = get_turf(cache))
sparks.start()
-/// Returns a turf if it's not dense, else will find a neighbor.
-/obj/machinery/quantum_server/proc/validate_turf(turf/chosen_turf)
- if(!chosen_turf.is_blocked_turf())
- return chosen_turf
- for(var/turf/tile in get_adjacent_open_turfs(chosen_turf))
- if(!tile.is_blocked_turf())
- return chosen_turf
+/// Starts building a new avatar for the player.
+/// Called by netpods when they don't have a current avatar.
+/// This is a procedural proc which links several others together.
+/obj/machinery/quantum_server/proc/start_new_connection(mob/living/carbon/human/neo, datum/outfit/netsuit) as /mob/living/carbon/human
+ var/atom/entry_atom = get_avatar_destination()
+ if(isnull(entry_atom))
+ return
+
+ var/mob/living/carbon/new_avatar = generate_avatar(get_turf(entry_atom), netsuit)
+ stock_gear(new_avatar, neo, generated_domain)
+
+ // Cleanup for domains with one time use custom spawns
+ if(!length(generated_domain.custom_spawns))
+ return new_avatar
+
+ // If we're spawning from some other fuckery, no need for this
+ if(istype(entry_atom, /obj/effect/mob_spawn/ghost_role/human/virtual_domain))
+ var/obj/effect/mob_spawn/ghost_role/human/virtual_domain/spawner = entry_atom
+ spawner.artificial_spawn(new_avatar)
+
+ if(!generated_domain.keep_custom_spawns)
+ generated_domain.custom_spawns -= entry_atom
+ qdel(entry_atom)
+
+ return new_avatar
-#undef MAX_DISTANCE
/// Toggles broadcast on and off
/obj/machinery/quantum_server/proc/toggle_broadcast()
@@ -116,3 +180,16 @@
// And we only flip TVs when there's a domain, because otherwise there's no cams to watch
set_network_broadcast_status(BITRUNNER_CAMERA_NET, broadcasting)
return TRUE
+
+
+/// Returns a turf if it's not dense, else will find a neighbor.
+/obj/machinery/quantum_server/proc/validate_turf(turf/chosen_turf)
+ if(!chosen_turf.is_blocked_turf())
+ return chosen_turf
+
+ for(var/turf/tile in get_adjacent_open_turfs(chosen_turf))
+ if(!tile.is_blocked_turf())
+ return chosen_turf
+
+
+#undef MAX_DISTANCE
diff --git a/code/modules/bitrunning/spawners.dm b/code/modules/bitrunning/spawners.dm
index 5fa889ac655fd..4b5f79a43b186 100644
--- a/code/modules/bitrunning/spawners.dm
+++ b/code/modules/bitrunning/spawners.dm
@@ -3,12 +3,9 @@
prompt_name = "a virtual domain debug entity"
flavour_text = "You probably shouldn't be seeing this, contact a coder!"
you_are_text = "You are NOT supposed to be here. How did you let this happen?"
- important_text = "You must eliminate any bitrunners from the domain."
+ important_text = "Bitrunning is a crime, and your primary threat."
temp_body = TRUE
-/obj/effect/mob_spawn/ghost_role/human/virtual_domain/Initialize(mapload)
- . = ..()
- notify_ghosts("The [name] has been created. The virtual world calls for aid!", src, "Virtual Insanity!")
/obj/effect/mob_spawn/ghost_role/human/virtual_domain/special(mob/living/spawned_mob, mob/mob_possessor)
var/datum/mind/ghost_mind = mob_possessor.mind
@@ -19,6 +16,12 @@
spawned_mob.mind.add_antag_datum(/datum/antagonist/domain_ghost_actor)
+
+/// Simulates a ghost role spawn without calling special(), ie a bitrunner spawn instead of a ghost.
+/obj/effect/mob_spawn/ghost_role/human/virtual_domain/proc/artificial_spawn(mob/living/runner)
+ SEND_SIGNAL(src, COMSIG_BITRUNNER_SPAWNED, runner)
+
+
/obj/effect/mob_spawn/ghost_role/human/virtual_domain/pirate
name = "Virtual Pirate Remains"
desc = "Some inanimate bones. They feel like they could spring to life at any moment!"
@@ -27,22 +30,25 @@
icon_state = "remains"
prompt_name = "a virtual skeleton pirate"
you_are_text = "You are a virtual pirate. Yarrr!"
- flavour_text = "You have awoken, without instruction. There's a LANDLUBBER after yer booty. Stop them!"
+ flavour_text = " There's a LANDLUBBER after yer booty. Stop them!"
+
/obj/effect/mob_spawn/ghost_role/human/virtual_domain/pirate/special(mob/living/spawned_mob, mob/mob_possessor)
. = ..()
spawned_mob.fully_replace_character_name(spawned_mob.real_name, "[pick(strings(PIRATE_NAMES_FILE, "generic_beginnings"))][pick(strings(PIRATE_NAMES_FILE, "generic_endings"))]")
+
/obj/effect/mob_spawn/ghost_role/human/virtual_domain/syndie
name = "Virtual Syndicate Sleeper"
icon = 'icons/obj/machines/sleeper.dmi'
icon_state = "sleeper_s"
prompt_name = "a virtual syndicate operative"
you_are_text = "You are a virtual syndicate operative."
- flavour_text = "You have awoken, without instruction. Alarms blare! We are being boarded!"
+ flavour_text = "Alarms blare! We are being boarded!"
outfit = /datum/outfit/virtual_syndicate
spawner_job_path = /datum/job/space_syndicate
+
/datum/outfit/virtual_syndicate
name = "Virtual Syndie"
id = /obj/item/card/id/advanced/chameleon
@@ -53,5 +59,6 @@
shoes = /obj/item/clothing/shoes/combat
implants = list(/obj/item/implant/weapons_auth)
+
/datum/outfit/virtual_syndicate/post_equip(mob/living/carbon/human/user, visualsOnly)
user.faction |= ROLE_SYNDICATE
diff --git a/code/modules/bitrunning/virtual_domain/domains/island_brawl.dm b/code/modules/bitrunning/virtual_domain/domains/island_brawl.dm
new file mode 100644
index 0000000000000..b745a4746aa24
--- /dev/null
+++ b/code/modules/bitrunning/virtual_domain/domains/island_brawl.dm
@@ -0,0 +1,46 @@
+/datum/lazy_template/virtual_domain/island_brawl
+ name = "Island Brawl"
+ announce_to_ghosts = TRUE
+ cost = BITRUNNER_COST_HIGH
+ desc = "A 'peaceful' island tucked away in the middle of nowhere. This map will auto-complete after a number of deaths have occurred."
+ difficulty = BITRUNNER_DIFFICULTY_HIGH
+ forced_outfit = /datum/outfit/beachbum_combat
+ help_text = "There may be bounties laid out across the island, but the primary objective is to survive. Deaths on the island will count towards the final score."
+ key = "island_brawl"
+ map_name = "island_brawl"
+ reward_points = BITRUNNER_REWARD_HIGH
+ secondary_loot = list(
+ /obj/item/toy/beach_ball = 2,
+ /obj/item/clothing/shoes/sandal = 1,
+ /obj/item/clothing/glasses/sunglasses = 1,
+ /obj/item/gun/ballistic/automatic/mini_uzi = 1,
+ )
+
+
+/datum/lazy_template/virtual_domain/island_brawl/setup_domain(list/created_atoms)
+ for(var/obj/effect/mob_spawn/ghost_role/human/virtual_domain/islander/spawner in created_atoms)
+ custom_spawns += spawner
+
+ RegisterSignals(spawner, list(COMSIG_GHOSTROLE_SPAWNED, COMSIG_BITRUNNER_SPAWNED), PROC_REF(on_spawn))
+
+
+/// Someone has spawned in, so we check for their death
+/datum/lazy_template/virtual_domain/island_brawl/proc/on_spawn(datum/source, mob/living/spawned_mob)
+ SIGNAL_HANDLER
+
+ RegisterSignals(spawned_mob, list(COMSIG_LIVING_DEATH), PROC_REF(on_death))
+
+
+/// Mob has died, so we add a point to the domain
+/datum/lazy_template/virtual_domain/island_brawl/proc/on_death(datum/source, gibbed)
+ SIGNAL_HANDLER
+
+ add_points(1)
+
+
+/obj/effect/mob_spawn/ghost_role/human/virtual_domain/islander
+ name = "Islander"
+ outfit = /datum/outfit/beachbum_combat
+ prompt_name = "a combat beach bum"
+ you_are_text = "You are a virtual islander."
+ flavour_text = "Don't let anyone ruin your idyllic vacation spot. Coordinate with others- or don't!"
diff --git a/code/modules/bitrunning/virtual_domain/domains/pirates.dm b/code/modules/bitrunning/virtual_domain/domains/pirates.dm
index 3c756caacc6eb..80981e759f6e4 100644
--- a/code/modules/bitrunning/virtual_domain/domains/pirates.dm
+++ b/code/modules/bitrunning/virtual_domain/domains/pirates.dm
@@ -1,5 +1,6 @@
/datum/lazy_template/virtual_domain/pirates
name = "Бухта корсаров"
+ announce_to_ghosts = TRUE
cost = BITRUNNER_COST_MEDIUM
desc = "Пробейтесь с боем к спрятанным сокровищам, захватите добычу и поспешите сбежать, прежде чем пираты переломят ситуацию."
difficulty = BITRUNNER_DIFFICULTY_MEDIUM
diff --git a/code/modules/bitrunning/virtual_domain/domains/syndicate_assault.dm b/code/modules/bitrunning/virtual_domain/domains/syndicate_assault.dm
index e17c24edd29c3..ae7bbd7c63c96 100644
--- a/code/modules/bitrunning/virtual_domain/domains/syndicate_assault.dm
+++ b/code/modules/bitrunning/virtual_domain/domains/syndicate_assault.dm
@@ -1,5 +1,6 @@
/datum/lazy_template/virtual_domain/syndicate_assault
name = "Нападение Синдиката"
+ announce_to_ghosts = TRUE
cost = BITRUNNER_COST_MEDIUM
desc = "Возьмите на абордаж вражеский корабль и верните украденный груз."
difficulty = BITRUNNER_DIFFICULTY_MEDIUM
diff --git a/code/modules/bitrunning/virtual_domain/virtual_domain.dm b/code/modules/bitrunning/virtual_domain/virtual_domain.dm
index 21898daad72d7..f81f6186cbe6e 100644
--- a/code/modules/bitrunning/virtual_domain/virtual_domain.dm
+++ b/code/modules/bitrunning/virtual_domain/virtual_domain.dm
@@ -7,50 +7,82 @@
map_name = "None"
key = "Virtual Domain"
place_on_top = TRUE
+ /// Whether to tell observers this map is being used
+ var/announce_to_ghosts = FALSE
+ /// The map file to load
+ var/filename = "virtual_domain.dmm"
+ /// The start time of the map. Used to calculate time taken
+ var/start_time
+ /// This map is specifically for unit tests. Shouldn't display in game
+ var/test_only = FALSE
+
+ /**
+ * Generic settings / UI
+ */
/// Cost of this map to load
var/cost = BITRUNNER_COST_NONE
- /// Any outfit that you wish to force on avatars. Overrides preferences
- var/datum/outfit/forced_outfit
/// The description of the map for the console UI
var/desc = "A map."
/// Affects the ui and ability to scan info.
var/difficulty = BITRUNNER_DIFFICULTY_NONE
- /// The map file to load
- var/filename = "virtual_domain.dmm"
+ /// Write these to help complete puzzles and other objectives. Viewed in the domain info ability.
+ var/help_text
+ // Name to show in the UI
+ var/name = "Virtual Domain"
+ /// Points to reward for completion. Used to purchase new domains and calculate ore rewards.
+ var/reward_points = BITRUNNER_REWARD_MIN
+
+ /**
+ * Player customization
+ */
+
/// If this domain blocks the use of items from disks, for whatever reason
var/forbids_disk_items = FALSE
/// If this domain blocks the use of spells from disks, for whatever reason
var/forbids_disk_spells = FALSE
- /// Information given to connected clients via ability
- var/help_text
- /// Whether to display this as a modular map
- var/is_modular = FALSE
- /// Byond will look for modular mob segment landmarks then choose from here at random. You can make them unique also.
- var/list/datum/modular_mob_segment/mob_modules = list()
+ /// Any outfit that you wish to force on avatars. Overrides preferences
+ var/datum/outfit/forced_outfit
+
+ /**
+ * Loot
+ */
+
/// An assoc list of typepath/amount to spawn on completion. Not weighted - the value is the amount
var/list/completion_loot
- /// An accoc list of typepath/amount to spawn from secondary objectives. Not weighted - the value is the total number of items that can be obtained.
+ /// An assoc list of typepath/amount to spawn from secondary objectives. Not weighted - the value is the total number of items that can be obtained.
var/list/secondary_loot = list()
/// Number of secondary loot boxes generated. Resets when the domain is reloaded.
var/secondary_loot_generated
- /// Forces all mob modules to only load once
- var/modular_unique_mobs = FALSE
- // Name to show in the UI
- var/name = "Virtual Domain"
- /// Points to reward for completion. Used to purchase new domains and calculate ore rewards.
- var/reward_points = BITRUNNER_REWARD_MIN
- /// The start time of the map. Used to calculate time taken
- var/start_time
- /// This map is specifically for unit tests. Shouldn't display in game
- var/test_only = FALSE
/// Has this domain been beaten with high enough score to spawn a tech disk?
var/disk_reward_spawned = FALSE
+ /**
+ * Modularity
+ */
+
+ /// Whether to display this as a modular map
+ var/is_modular = FALSE
+ /// Byond will look for modular mob segment landmarks then choose from here at random. You can make them unique also.
+ var/list/datum/modular_mob_segment/mob_modules = list()
+ /// Forces all mob modules to only load once
+ var/modular_unique_mobs = FALSE
+
+ /**
+ * Spawning
+ */
+
+ /// Looks for random landmarks to spawn on.
+ var/list/custom_spawns = list()
+ /// Set TRUE if you want reusable custom spawners
+ var/keep_custom_spawns = FALSE
+
+
/// Sends a point to any loot signals on the map
/datum/lazy_template/virtual_domain/proc/add_points(points_to_add)
SEND_SIGNAL(src, COMSIG_BITRUNNER_GOAL_POINT, points_to_add)
+
/// Overridable proc to be called after the map is loaded.
/datum/lazy_template/virtual_domain/proc/setup_domain(list/created_atoms)
return
diff --git a/code/modules/capture_the_flag/medieval_sim/medisim_classes.dm b/code/modules/capture_the_flag/medieval_sim/medisim_classes.dm
index 3c4a55748c4c6..09ed5b40b1f20 100644
--- a/code/modules/capture_the_flag/medieval_sim/medisim_classes.dm
+++ b/code/modules/capture_the_flag/medieval_sim/medisim_classes.dm
@@ -26,7 +26,7 @@
name = "Redfield Castle Archer"
icon_state = "medisim_archer"
- belt = /obj/item/storage/bag/quiver
+ belt = /obj/item/storage/bag/quiver/full
suit = /obj/item/clothing/suit/armor/vest/cuirass
l_hand = /obj/item/gun/ballistic/bow
diff --git a/code/modules/capture_the_flag/medieval_sim/medisim_game.dm b/code/modules/capture_the_flag/medieval_sim/medisim_game.dm
index 18e1cd13e7ede..118184698a3cc 100644
--- a/code/modules/capture_the_flag/medieval_sim/medisim_game.dm
+++ b/code/modules/capture_the_flag/medieval_sim/medisim_game.dm
@@ -22,7 +22,7 @@
if(!.)
return
var/mob/living/carbon/human/human_knight = .
- randomize_human(human_knight)
+ randomize_human_normie(human_knight)
human_knight.dna.add_mutation(/datum/mutation/human/medieval, MUT_OTHER)
var/oldname = human_knight.name
var/title = "error"
diff --git a/code/modules/cargo/bounties/science.dm b/code/modules/cargo/bounties/science.dm
index 9da701d097ecc..ae2752a3b17bb 100644
--- a/code/modules/cargo/bounties/science.dm
+++ b/code/modules/cargo/bounties/science.dm
@@ -9,7 +9,7 @@
if(!..())
return FALSE
var/obj/item/relic/experiment = O
- if(experiment.revealed)
+ if(experiment.activated)
return TRUE
return
diff --git a/code/modules/cargo/packs/emergency.dm b/code/modules/cargo/packs/emergency.dm
index b48ef268da683..2d19c276ec115 100644
--- a/code/modules/cargo/packs/emergency.dm
+++ b/code/modules/cargo/packs/emergency.dm
@@ -6,8 +6,8 @@
name = "Biological Emergency Crate"
desc = "This crate includes 2 complete bio suits, along with a box containing sterile masks and latex gloves, providing effective protection against viruses."
cost = CARGO_CRATE_VALUE * 2
- contains = list(/obj/item/clothing/head/bio_hood = 2,
- /obj/item/clothing/suit/bio_suit = 2,
+ contains = list(/obj/item/clothing/head/bio_hood/general = 2,
+ /obj/item/clothing/suit/bio_suit/general = 2,
/obj/item/storage/bag/bio,
/obj/item/reagent_containers/syringe/antiviral = 2,
/obj/item/clothing/gloves/latex/nitrile = 2,
diff --git a/code/modules/cargo/packs/imports.dm b/code/modules/cargo/packs/imports.dm
index 3f7645559ff7f..f270b1da11f39 100644
--- a/code/modules/cargo/packs/imports.dm
+++ b/code/modules/cargo/packs/imports.dm
@@ -309,11 +309,12 @@
risky espionage hallway operations. Enjoy our product!"
contraband = TRUE
cost = CARGO_CRATE_VALUE * 6
- contains = list(/obj/item/clothing/under/syndicate/floortilecamo = 3,
- /obj/item/clothing/mask/floortilebalaclava = 3,
- /obj/item/clothing/gloves/combat/floortile = 3,
- /obj/item/clothing/shoes/jackboots/floortile = 3,
- /obj/item/storage/backpack/floortile = 3
+ contains = list(
+ /obj/item/clothing/under/syndicate/floortilecamo = 3,
+ /obj/item/clothing/mask/floortilebalaclava = 3,
+ /obj/item/clothing/gloves/combat/floortile = 3,
+ /obj/item/clothing/shoes/jackboots/floortile = 3,
+ /obj/item/storage/backpack/floortile = 3
)
crate_name = "floortile camouflauge crate"
crate_type = /obj/structure/closet/crate/secure/weapon
diff --git a/code/modules/cargo/packs/science.dm b/code/modules/cargo/packs/science.dm
index 4059b330e2f66..dfa2a66359336 100644
--- a/code/modules/cargo/packs/science.dm
+++ b/code/modules/cargo/packs/science.dm
@@ -176,7 +176,8 @@
/obj/item/biopsy_tool,
/obj/item/storage/box/petridish = 2,
/obj/item/storage/box/swab,
- /obj/item/construction/plumbing/research,
+ /obj/item/circuitboard/machine/vatgrower,
+ /obj/item/reagent_containers/condiment/protein,
)
crate_name = "cytology supplies crate"
diff --git a/code/modules/cargo/packs/service.dm b/code/modules/cargo/packs/service.dm
index fddd093b4fb7e..228d0d3e2fa46 100644
--- a/code/modules/cargo/packs/service.dm
+++ b/code/modules/cargo/packs/service.dm
@@ -303,3 +303,17 @@
contains = list(/obj/item/wallframe/barsign/all_access)
crate_name = "bar sign crate"
discountable = SUPPLY_PACK_RARE_DISCOUNTABLE
+
+/datum/supply_pack/service/bowmaking
+ name = "Fletching and Bow-Making Starter Kit"
+ desc = "A fairly outdated copy of 'Whittle Me This: Fletching for the Modern Spacer', along with some useful materials. \
+ For those looking to get into bow-making, or give their LARPing a little more edge, you can't go wrong. Also has \
+ instructions for making violins."
+ cost = CARGO_CRATE_VALUE * 5
+ contains = list(
+ /obj/item/book/granter/crafting_recipe/fletching = 1,
+ /obj/item/stack/sheet/mineral/wood = 10,
+ /obj/item/stack/sheet/cloth = 10,
+ )
+ crate_name = "bowmaking starter kit crate"
+ crate_type = /obj/structure/closet/crate/wooden
diff --git a/code/modules/client/client_colour.dm b/code/modules/client/client_colour.dm
index 1209b5c35823c..4ca500a9e7198 100644
--- a/code/modules/client/client_colour.dm
+++ b/code/modules/client/client_colour.dm
@@ -180,9 +180,15 @@
/datum/client_colour/glass_colour/yellow
colour = "#ffff66"
+/datum/client_colour/glass_colour/lightyellow
+ colour = "#ffffaa"
+
/datum/client_colour/glass_colour/red
colour = "#ffaaaa"
+/datum/client_colour/glass_colour/lightred
+ colour = "#ffcccc"
+
/datum/client_colour/glass_colour/darkred
colour = "#bb5555"
@@ -195,9 +201,19 @@
/datum/client_colour/glass_colour/purple
colour = "#ff99ff"
+/datum/client_colour/glass_colour/lightpurple
+ colour = "#ffccff"
+
/datum/client_colour/glass_colour/gray
colour = "#cccccc"
+///A client colour that makes the screen look a bit more grungy, halloweenesque even.
+/datum/client_colour/halloween_helmet
+ colour = list(0.75,0.13,0.13,0, 0.13,0.7,0.13,0, 0.13,0.13,0.75,0, -0.06,-0.09,-0.08,1, 0,0,0,0)
+
+/datum/client_colour/flash_hood
+ colour = COLOR_MATRIX_POLAROID
+
/datum/client_colour/glass_colour/nightmare
colour = list(255,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,1, -130,0,0,0) //every color is either red or black
diff --git a/code/modules/client/preferences/_preference.dm b/code/modules/client/preferences/_preference.dm
index 485276b4ade2a..1ba43cf3d8a63 100644
--- a/code/modules/client/preferences/_preference.dm
+++ b/code/modules/client/preferences/_preference.dm
@@ -18,16 +18,19 @@
/// support the "use gender" option.
#define PREFERENCE_PRIORITY_BODY_TYPE 5
+/// Used for preferences that rely on body setup being finalized.
+#define PREFERENCE_PRORITY_LATE_BODY_TYPE 6
+
/// Equpping items based on preferences.
/// Should happen after species and body type to make sure it looks right.
/// Mostly redundant, but a safety net for saving/loading.
-#define PREFERENCE_PRIORITY_LOADOUT 6
+#define PREFERENCE_PRIORITY_LOADOUT 7
/// The priority at which names are decided, needed for proper randomization.
-#define PREFERENCE_PRIORITY_NAMES 7
+#define PREFERENCE_PRIORITY_NAMES 8
/// Preferences that aren't names, but change the name changes set by PREFERENCE_PRIORITY_NAMES.
-#define PREFERENCE_PRIORITY_NAME_MODIFICATIONS 8
+#define PREFERENCE_PRIORITY_NAME_MODIFICATIONS 9
/// The maximum preference priority, keep this updated, but don't use it for `priority`.
#define MAX_PREFERENCE_PRIORITY PREFERENCE_PRIORITY_NAME_MODIFICATIONS
diff --git a/code/modules/client/preferences/age.dm b/code/modules/client/preferences/age.dm
index cad9786ce1fef..07b4644bc6a9e 100644
--- a/code/modules/client/preferences/age.dm
+++ b/code/modules/client/preferences/age.dm
@@ -8,3 +8,6 @@
/datum/preference/numeric/age/apply_to_human(mob/living/carbon/human/target, value)
target.age = value
+
+/datum/preference/numeric/age/create_informed_default_value(datum/preferences/preferences)
+ return rand(max(minimum, 21), min(maximum, 50))
diff --git a/code/modules/client/preferences/blindfold_color.dm b/code/modules/client/preferences/blindfold_color.dm
index 9e6504579acb6..8081ac15be8ae 100644
--- a/code/modules/client/preferences/blindfold_color.dm
+++ b/code/modules/client/preferences/blindfold_color.dm
@@ -4,6 +4,9 @@
savefile_key = "blindfold_color"
savefile_identifier = PREFERENCE_CHARACTER
+/datum/preference/color/blindfold_color/create_default_value()
+ return COLOR_WHITE
+
/datum/preference/color/blindfold_color/is_accessible(datum/preferences/preferences)
if (!..(preferences))
return FALSE
diff --git a/code/modules/client/preferences/body_type.dm b/code/modules/client/preferences/body_type.dm
index 469b564e46f7b..6b27e0f0f0da7 100644
--- a/code/modules/client/preferences/body_type.dm
+++ b/code/modules/client/preferences/body_type.dm
@@ -5,6 +5,7 @@
priority = PREFERENCE_PRIORITY_BODY_TYPE
savefile_key = "body_type"
savefile_identifier = PREFERENCE_CHARACTER
+ can_randomize = FALSE
/datum/preference/choiced/body_type/init_possible_values()
return list(USE_GENDER, MALE, FEMALE)
diff --git a/code/modules/client/preferences/clothing.dm b/code/modules/client/preferences/clothing.dm
index b4d3e98082469..b2cf6c5c1ef0a 100644
--- a/code/modules/client/preferences/clothing.dm
+++ b/code/modules/client/preferences/clothing.dm
@@ -33,6 +33,9 @@
DMESSENGER,
)
+/datum/preference/choiced/backpack/create_default_value()
+ return GBACKPACK
+
/datum/preference/choiced/backpack/icon_for(value)
switch (value)
if (GBACKPACK)
@@ -66,6 +69,7 @@
/datum/preference/choiced/jumpsuit
savefile_key = "jumpsuit_style"
savefile_identifier = PREFERENCE_CHARACTER
+ priority = PREFERENCE_PRIORITY_BODY_TYPE
main_feature_name = "Комбинезон"
category = PREFERENCE_CATEGORY_CLOTHING
should_generate_icons = TRUE
@@ -86,6 +90,15 @@
/datum/preference/choiced/jumpsuit/apply_to_human(mob/living/carbon/human/target, value)
target.jumpsuit_style = value
+/datum/preference/choiced/jumpsuit/create_informed_default_value(datum/preferences/preferences)
+ switch(preferences.read_preference(/datum/preference/choiced/gender))
+ if(MALE)
+ return PREF_SUIT
+ if(FEMALE)
+ return PREF_SKIRT
+
+ return ..()
+
/// Socks preference
/datum/preference/choiced/socks
savefile_key = "socks"
@@ -93,10 +106,14 @@
main_feature_name = "Носки"
category = PREFERENCE_CATEGORY_CLOTHING
should_generate_icons = TRUE
+ can_randomize = FALSE
/datum/preference/choiced/socks/init_possible_values()
return assoc_to_keys_features(SSaccessories.socks_list)
+/datum/preference/choiced/socks/create_default_value()
+ return /datum/sprite_accessory/socks/nude::name
+
/datum/preference/choiced/socks/icon_for(value)
var/static/icon/lower_half
@@ -114,13 +131,27 @@
/datum/preference/choiced/undershirt
savefile_key = "undershirt"
savefile_identifier = PREFERENCE_CHARACTER
+ priority = PREFERENCE_PRIORITY_BODY_TYPE
main_feature_name = "Одежда"
category = PREFERENCE_CATEGORY_CLOTHING
should_generate_icons = TRUE
+ can_randomize = FALSE
/datum/preference/choiced/undershirt/init_possible_values()
return assoc_to_keys_features(SSaccessories.undershirt_list)
+/datum/preference/choiced/undershirt/create_default_value()
+ return /datum/sprite_accessory/undershirt/nude::name
+
+/datum/preference/choiced/undershirt/create_informed_default_value(datum/preferences/preferences)
+ switch(preferences.read_preference(/datum/preference/choiced/gender))
+ if(MALE)
+ return /datum/sprite_accessory/undershirt/nude::name
+ if(FEMALE)
+ return /datum/sprite_accessory/undershirt/sports_bra::name
+
+ return ..()
+
/datum/preference/choiced/undershirt/icon_for(value)
var/static/icon/body
if (isnull(body))
@@ -152,10 +183,14 @@
main_feature_name = "Нижнее белье"
category = PREFERENCE_CATEGORY_CLOTHING
should_generate_icons = TRUE
+ can_randomize = FALSE
/datum/preference/choiced/underwear/init_possible_values()
return assoc_to_keys_features(SSaccessories.underwear_list)
+/datum/preference/choiced/underwear/create_default_value()
+ return /datum/sprite_accessory/underwear/male_hearts::name
+
/datum/preference/choiced/underwear/icon_for(value)
var/static/icon/lower_half
diff --git a/code/modules/client/preferences/gender.dm b/code/modules/client/preferences/gender.dm
index bea6674d7b086..a95874f160680 100644
--- a/code/modules/client/preferences/gender.dm
+++ b/code/modules/client/preferences/gender.dm
@@ -11,3 +11,8 @@
if(!target.dna.species.sexes)
value = PLURAL //disregard gender preferences on this species
target.gender = value
+
+/datum/preference/choiced/gender/create_informed_default_value(datum/preferences/preferences)
+ // The only reason I'm limiting this to male or female
+ // is that hairstyle randomization handles enbies poorly
+ return pick(MALE, FEMALE)
diff --git a/code/modules/client/preferences/glasses.dm b/code/modules/client/preferences/glasses.dm
index a08f15955eaa4..e5158f1acbbde 100644
--- a/code/modules/client/preferences/glasses.dm
+++ b/code/modules/client/preferences/glasses.dm
@@ -4,6 +4,9 @@
savefile_identifier = PREFERENCE_CHARACTER
should_generate_icons = TRUE
+/datum/preference/choiced/glasses/create_default_value()
+ return "Random"
+
/datum/preference/choiced/glasses/init_possible_values()
return assoc_to_keys(GLOB.nearsighted_glasses) + "Random"
diff --git a/code/modules/client/preferences/language.dm b/code/modules/client/preferences/language.dm
index f602d6b3a66c9..637c4542da277 100644
--- a/code/modules/client/preferences/language.dm
+++ b/code/modules/client/preferences/language.dm
@@ -3,6 +3,9 @@
savefile_key = "language"
savefile_identifier = PREFERENCE_CHARACTER
+/datum/preference/choiced/language/create_default_value()
+ return "Random"
+
/datum/preference/choiced/language/is_accessible(datum/preferences/preferences)
if (!..(preferences))
return FALSE
diff --git a/code/modules/client/preferences/names.dm b/code/modules/client/preferences/names.dm
index 4652550e1ffd5..9b671f416b787 100644
--- a/code/modules/client/preferences/names.dm
+++ b/code/modules/client/preferences/names.dm
@@ -17,21 +17,26 @@
/// If the highest priority job matches this, will prioritize this name in the UI
var/relevant_job
+
/datum/preference/name/apply_to_human(mob/living/carbon/human/target, value)
// Only real_name applies directly, everything else is applied by something else
return
+
/datum/preference/name/deserialize(input, datum/preferences/preferences)
return reject_bad_name("[input]", allow_numbers)
+
/datum/preference/name/serialize(input)
// `is_valid` should always be run before `serialize`, so it should not
// be possible for this to return `null`.
return reject_bad_name(input, allow_numbers)
+
/datum/preference/name/is_valid(value)
return istext(value) && !isnull(reject_bad_name(value, allow_numbers))
+
/// A character's real name
/datum/preference/name/real_name
explanation = "Имя"
@@ -181,8 +186,21 @@
explanation = "Hacker alias"
group = "bitrunning"
savefile_key = "hacker_alias"
- allow_numbers = TRUE
relevant_job = /datum/job/bitrunner
+
/datum/preference/name/hacker_alias/create_default_value()
return pick(GLOB.hacker_aliases)
+
+
+/datum/preference/name/hacker_alias/is_valid(value)
+ return !isnull(permissive_sanitize_name(value))
+
+
+/datum/preference/name/hacker_alias/deserialize(input, datum/preferences/preferences)
+ return permissive_sanitize_name(input)
+
+
+/datum/preference/name/hacker_alias/serialize(input)
+ return permissive_sanitize_name(input)
+
diff --git a/code/modules/client/preferences/prosthetic_limb.dm b/code/modules/client/preferences/prosthetic_limb.dm
index a4d5b5a577ba1..be807ac4e1f76 100644
--- a/code/modules/client/preferences/prosthetic_limb.dm
+++ b/code/modules/client/preferences/prosthetic_limb.dm
@@ -3,8 +3,11 @@
savefile_key = "prosthetic"
savefile_identifier = PREFERENCE_CHARACTER
+/datum/preference/choiced/prosthetic/create_default_value()
+ return "Random"
+
/datum/preference/choiced/prosthetic/init_possible_values()
- return list("Random") + GLOB.limb_choice
+ return list("Random") + GLOB.prosthetic_limb_choice
/datum/preference/choiced/prosthetic/is_accessible(datum/preferences/preferences)
. = ..()
diff --git a/code/modules/client/preferences/prosthetic_organ.dm b/code/modules/client/preferences/prosthetic_organ.dm
index 35d3b818355eb..02e8418e3db2f 100644
--- a/code/modules/client/preferences/prosthetic_organ.dm
+++ b/code/modules/client/preferences/prosthetic_organ.dm
@@ -3,6 +3,9 @@
savefile_key = "prosthetic_organ"
savefile_identifier = PREFERENCE_CHARACTER
+/datum/preference/choiced/prosthetic_organ/create_default_value()
+ return "Random"
+
/datum/preference/choiced/prosthetic_organ/init_possible_values()
return list("Random") + GLOB.organ_choice
diff --git a/code/modules/client/preferences/sounds.dm b/code/modules/client/preferences/sounds.dm
index 81263de677bc9..f1778405665ad 100644
--- a/code/modules/client/preferences/sounds.dm
+++ b/code/modules/client/preferences/sounds.dm
@@ -7,6 +7,11 @@
/datum/preference/toggle/sound_ambience/apply_to_client(client/client, value)
client.update_ambience_pref(value)
+/datum/preference/toggle/sound_breathing
+ category = PREFERENCE_CATEGORY_GAME_PREFERENCES
+ savefile_key = "sound_breathing"
+ savefile_identifier = PREFERENCE_PLAYER
+
/// Controls hearing announcement sounds
/datum/preference/toggle/sound_announcements
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
diff --git a/code/modules/client/preferences/species_features/basic.dm b/code/modules/client/preferences/species_features/basic.dm
index a09c6e2e2e9af..7e6c3758017a3 100644
--- a/code/modules/client/preferences/species_features/basic.dm
+++ b/code/modules/client/preferences/species_features/basic.dm
@@ -52,7 +52,7 @@
return random_eye_color()
/datum/preference/choiced/facial_hairstyle
- priority = PREFERENCE_PRIORITY_BODYPARTS
+ priority = PREFERENCE_PRORITY_LATE_BODY_TYPE
savefile_key = "facial_style_name"
savefile_identifier = PREFERENCE_CHARACTER
category = PREFERENCE_CATEGORY_FEATURES
@@ -69,15 +69,32 @@
/datum/preference/choiced/facial_hairstyle/apply_to_human(mob/living/carbon/human/target, value)
target.set_facial_hairstyle(value, update = FALSE)
+/datum/preference/choiced/facial_hairstyle/create_default_value()
+ return /datum/sprite_accessory/facial_hair/shaved::name
+
+/datum/preference/choiced/facial_hairstyle/create_informed_default_value(datum/preferences/preferences)
+ var/gender = preferences.read_preference(/datum/preference/choiced/gender)
+ var/species_type = preferences.read_preference(/datum/preference/choiced/species)
+ var/datum/species/species_real = GLOB.species_prototypes[species_type]
+ if(!gender || !species_real || !species_real.sexes)
+ return ..()
+
+ var/picked_beard = random_facial_hairstyle(gender)
+ var/datum/sprite_accessory/beard_style = SSaccessories.facial_hairstyles_list[picked_beard]
+ if(!beard_style || !beard_style.natural_spawn || beard_style.locked) // Invalid, go with god(bald)
+ return ..()
+
+ return picked_beard
+
/datum/preference/choiced/facial_hairstyle/compile_constant_data()
var/list/data = ..()
- data[SUPPLEMENTAL_FEATURE_KEY] = "facial_hair_color"
+ data[SUPPLEMENTAL_FEATURE_KEY] = /datum/preference/color/facial_hair_color::savefile_key
return data
/datum/preference/color/facial_hair_color
- priority = PREFERENCE_PRIORITY_BODYPARTS
+ priority = PREFERENCE_PRORITY_LATE_BODY_TYPE // Need to happen after hair oclor is set so we can match by default
savefile_key = "facial_hair_color"
savefile_identifier = PREFERENCE_CHARACTER
category = PREFERENCE_CATEGORY_SUPPLEMENTAL_FEATURES
@@ -86,12 +103,16 @@
/datum/preference/color/facial_hair_color/apply_to_human(mob/living/carbon/human/target, value)
target.set_facial_haircolor(value, update = FALSE)
+/datum/preference/color/facial_hair_color/create_informed_default_value(datum/preferences/preferences)
+ return preferences.read_preference(/datum/preference/color/hair_color) || random_hair_color()
+
/datum/preference/choiced/facial_hair_gradient
- priority = PREFERENCE_PRIORITY_BODYPARTS
+ priority = PREFERENCE_PRORITY_LATE_BODY_TYPE
category = PREFERENCE_CATEGORY_SECONDARY_FEATURES
savefile_identifier = PREFERENCE_CHARACTER
savefile_key = "facial_hair_gradient"
relevant_head_flag = HEAD_FACIAL_HAIR
+ can_randomize = FALSE
/datum/preference/choiced/facial_hair_gradient/init_possible_values()
return assoc_to_keys_features(SSaccessories.facial_hair_gradients_list)
@@ -100,10 +121,10 @@
target.set_facial_hair_gradient_style(new_style = value, update = FALSE)
/datum/preference/choiced/facial_hair_gradient/create_default_value()
- return "None"
+ return /datum/sprite_accessory/gradient/none::name
/datum/preference/color/facial_hair_gradient
- priority = PREFERENCE_PRIORITY_BODYPARTS
+ priority = PREFERENCE_PRORITY_LATE_BODY_TYPE
category = PREFERENCE_CATEGORY_SECONDARY_FEATURES
savefile_identifier = PREFERENCE_CHARACTER
savefile_key = "facial_hair_gradient_color"
@@ -115,10 +136,10 @@
/datum/preference/color/facial_hair_gradient/is_accessible(datum/preferences/preferences)
if (!..(preferences))
return FALSE
- return preferences.read_preference(/datum/preference/choiced/facial_hair_gradient) != "None"
+ return preferences.read_preference(/datum/preference/choiced/facial_hair_gradient) != /datum/sprite_accessory/gradient/none::name
/datum/preference/color/hair_color
- priority = PREFERENCE_PRIORITY_BODYPARTS
+ priority = PREFERENCE_PRIORITY_BODY_TYPE
savefile_key = "hair_color"
savefile_identifier = PREFERENCE_CHARACTER
category = PREFERENCE_CATEGORY_SUPPLEMENTAL_FEATURES
@@ -127,8 +148,11 @@
/datum/preference/color/hair_color/apply_to_human(mob/living/carbon/human/target, value)
target.set_haircolor(value, update = FALSE)
+/datum/preference/color/hair_color/create_informed_default_value(datum/preferences/preferences)
+ return random_hair_color()
+
/datum/preference/choiced/hairstyle
- priority = PREFERENCE_PRIORITY_BODYPARTS
+ priority = PREFERENCE_PRIORITY_BODY_TYPE // Happens after gender so we can picka hairstyle based on that
savefile_key = "hairstyle_name"
savefile_identifier = PREFERENCE_CHARACTER
category = PREFERENCE_CATEGORY_FEATURES
@@ -146,19 +170,37 @@
/datum/preference/choiced/hairstyle/apply_to_human(mob/living/carbon/human/target, value)
target.set_hairstyle(value, update = FALSE)
+/datum/preference/choiced/hairstyle/create_default_value()
+ return /datum/sprite_accessory/hair/bald::name
+
+/datum/preference/choiced/hairstyle/create_informed_default_value(datum/preferences/preferences)
+ var/gender = preferences.read_preference(/datum/preference/choiced/gender)
+ var/species_type = preferences.read_preference(/datum/preference/choiced/species)
+ var/datum/species/species_real = GLOB.species_prototypes[species_type]
+ if(!gender || !species_real || !species_real.sexes)
+ return ..()
+
+ var/picked_hair = random_hairstyle(gender)
+ var/datum/sprite_accessory/hair_style = SSaccessories.hairstyles_list[picked_hair]
+ if(!hair_style || !hair_style.natural_spawn || hair_style.locked) // Invalid, go with god(bald)
+ return ..()
+
+ return picked_hair
+
/datum/preference/choiced/hairstyle/compile_constant_data()
var/list/data = ..()
- data[SUPPLEMENTAL_FEATURE_KEY] = "hair_color"
+ data[SUPPLEMENTAL_FEATURE_KEY] = /datum/preference/color/hair_color::savefile_key
return data
/datum/preference/choiced/hair_gradient
- priority = PREFERENCE_PRIORITY_BODYPARTS
+ priority = PREFERENCE_PRIORITY_BODY_TYPE
category = PREFERENCE_CATEGORY_SECONDARY_FEATURES
savefile_identifier = PREFERENCE_CHARACTER
savefile_key = "hair_gradient"
relevant_head_flag = HEAD_HAIR
+ can_randomize = FALSE
/datum/preference/choiced/hair_gradient/init_possible_values()
return assoc_to_keys_features(SSaccessories.hair_gradients_list)
@@ -167,10 +209,10 @@
target.set_hair_gradient_style(new_style = value, update = FALSE)
/datum/preference/choiced/hair_gradient/create_default_value()
- return "None"
+ return /datum/sprite_accessory/gradient/none::name
/datum/preference/color/hair_gradient
- priority = PREFERENCE_PRIORITY_BODYPARTS
+ priority = PREFERENCE_PRIORITY_BODY_TYPE
category = PREFERENCE_CATEGORY_SECONDARY_FEATURES
savefile_identifier = PREFERENCE_CHARACTER
savefile_key = "hair_gradient_color"
@@ -182,4 +224,4 @@
/datum/preference/color/hair_gradient/is_accessible(datum/preferences/preferences)
if (!..(preferences))
return FALSE
- return preferences.read_preference(/datum/preference/choiced/hair_gradient) != "None"
+ return preferences.read_preference(/datum/preference/choiced/hair_gradient) != /datum/sprite_accessory/gradient/none::name
diff --git a/code/modules/client/preferences/species_features/felinid.dm b/code/modules/client/preferences/species_features/felinid.dm
index a6d43736cf46c..4c874ea7df750 100644
--- a/code/modules/client/preferences/species_features/felinid.dm
+++ b/code/modules/client/preferences/species_features/felinid.dm
@@ -29,5 +29,4 @@
target.dna.features["ears"] = value
/datum/preference/choiced/ears/create_default_value()
- var/datum/sprite_accessory/ears/cat/ears = /datum/sprite_accessory/ears/cat
- return initial(ears.name)
+ return /datum/sprite_accessory/ears/cat::name
diff --git a/code/modules/client/preferences/species_features/lizard.dm b/code/modules/client/preferences/species_features/lizard.dm
index 5e4faf37d1f46..62df9e846aa51 100644
--- a/code/modules/client/preferences/species_features/lizard.dm
+++ b/code/modules/client/preferences/species_features/lizard.dm
@@ -142,5 +142,4 @@
target.dna.features["tail_lizard"] = value
/datum/preference/choiced/lizard_tail/create_default_value()
- var/datum/sprite_accessory/tails/lizard/smooth/tail = /datum/sprite_accessory/tails/lizard/smooth
- return initial(tail.name)
+ return /datum/sprite_accessory/tails/lizard/smooth::name
diff --git a/code/modules/client/preferences/species_features/monkey.dm b/code/modules/client/preferences/species_features/monkey.dm
index adf9e367723de..8417cd7142e07 100644
--- a/code/modules/client/preferences/species_features/monkey.dm
+++ b/code/modules/client/preferences/species_features/monkey.dm
@@ -3,6 +3,7 @@
savefile_identifier = PREFERENCE_CHARACTER
category = PREFERENCE_CATEGORY_SECONDARY_FEATURES
relevant_external_organ = /obj/item/organ/external/tail/monkey
+ can_randomize = FALSE
/datum/preference/choiced/monkey_tail/init_possible_values()
return assoc_to_keys_features(SSaccessories.tails_list_monkey)
@@ -11,5 +12,4 @@
target.dna.features["tail_monkey"] = value
/datum/preference/choiced/monkey_tail/create_default_value()
- var/datum/sprite_accessory/tails/monkey/default/tail = /datum/sprite_accessory/tails/monkey/default
- return initial(tail.name)
+ return /datum/sprite_accessory/tails/monkey/default::name
diff --git a/code/modules/client/preferences/trans_prosthetic.dm b/code/modules/client/preferences/trans_prosthetic.dm
index fd28cb1ecf8c8..ea8128a1f44e4 100644
--- a/code/modules/client/preferences/trans_prosthetic.dm
+++ b/code/modules/client/preferences/trans_prosthetic.dm
@@ -3,6 +3,9 @@
savefile_key = "trans_prosthetic"
savefile_identifier = PREFERENCE_CHARACTER
+/datum/preference/choiced/trans_prosthetic/create_default_value()
+ return "Random"
+
/datum/preference/choiced/trans_prosthetic/init_possible_values()
return list("Random") + GLOB.part_choice_transhuman
diff --git a/code/modules/client/preferences_savefile.dm b/code/modules/client/preferences_savefile.dm
index 9a6448e2e6d54..5e848879d01f7 100644
--- a/code/modules/client/preferences_savefile.dm
+++ b/code/modules/client/preferences_savefile.dm
@@ -291,13 +291,13 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
return FALSE
// Read everything into cache
- for (var/preference_type in GLOB.preference_entries)
- var/datum/preference/preference = GLOB.preference_entries[preference_type]
+ // Uses priority order as some values may rely on others for creating default values
+ for (var/datum/preference/preference as anything in get_preferences_in_priority_order())
if (preference.savefile_identifier != PREFERENCE_CHARACTER)
continue
- value_cache -= preference_type
- read_preference(preference_type)
+ value_cache -= preference.type
+ read_preference(preference.type)
//Character
randomise = save_data?["randomise"]
diff --git a/code/modules/clothing/glasses/_glasses.dm b/code/modules/clothing/glasses/_glasses.dm
index 358bff5ce80d5..e2632dd394e0c 100644
--- a/code/modules/clothing/glasses/_glasses.dm
+++ b/code/modules/clothing/glasses/_glasses.dm
@@ -27,15 +27,15 @@
/// Whether or not vision coloring is forcing
var/forced_glass_color = FALSE
+/obj/item/clothing/glasses/Initialize(mapload)
+ . = ..()
+ if(glass_colour_type)
+ AddElement(/datum/element/wearable_client_colour, glass_colour_type, ITEM_SLOT_EYES, forced = forced_glass_color)
+
/obj/item/clothing/glasses/suicide_act(mob/living/carbon/user)
user.visible_message(span_suicide("[user] is stabbing \the [src] into [user.p_their()] eyes! It looks like [user.p_theyre()] trying to commit suicide!"))
return BRUTELOSS
-/obj/item/clothing/glasses/examine(mob/user)
- . = ..()
- if(glass_colour_type && !forced_glass_color && ishuman(user))
- . += span_notice("Alt-click to toggle [p_their()] colors.")
-
/obj/item/clothing/glasses/visor_toggling()
. = ..()
alternate_worn_layer = up ? ABOVE_BODY_FRONT_HEAD_LAYER : null
@@ -62,37 +62,12 @@
H.set_eye_blur_if_lower(10 SECONDS)
eyes.apply_organ_damage(5)
-/obj/item/clothing/glasses/click_alt(mob/user)
- if(isnull(glass_colour_type) || forced_glass_color || !ishuman(user))
- return NONE
- var/mob/living/carbon/human/human_user = user
-
- if (HAS_TRAIT_FROM(human_user, TRAIT_SEE_GLASS_COLORS, GLASSES_TRAIT))
- REMOVE_TRAIT(human_user, TRAIT_SEE_GLASS_COLORS, GLASSES_TRAIT)
- to_chat(human_user, span_notice("You will no longer see glasses colors."))
- else
- ADD_TRAIT(human_user, TRAIT_SEE_GLASS_COLORS, GLASSES_TRAIT)
- to_chat(human_user, span_notice("You will now see glasses colors."))
- human_user.update_glasses_color(src, TRUE)
- return CLICK_ACTION_SUCCESS
-
-/obj/item/clothing/glasses/proc/change_glass_color(mob/living/carbon/human/H, datum/client_colour/glass_colour/new_color_type)
- var/old_colour_type = glass_colour_type
- if(!new_color_type || ispath(new_color_type)) //the new glass colour type must be null or a path.
- glass_colour_type = new_color_type
- if(H && H.glasses == src)
- if(old_colour_type)
- H.remove_client_colour(old_colour_type)
- if(glass_colour_type)
- H.update_glasses_color(src, 1)
-
-
-/mob/living/carbon/human/proc/update_glasses_color(obj/item/clothing/glasses/G, glasses_equipped)
- if((HAS_TRAIT(src, TRAIT_SEE_GLASS_COLORS) || G.forced_glass_color) && glasses_equipped)
- add_client_colour(G.glass_colour_type)
- else
- remove_client_colour(G.glass_colour_type)
-
+/obj/item/clothing/glasses/proc/change_glass_color(new_color_type)
+ if(glass_colour_type)
+ RemoveElement(/datum/element/wearable_client_colour, glass_colour_type, ITEM_SLOT_EYES, forced = forced_glass_color)
+ glass_colour_type = new_color_type
+ if(glass_colour_type)
+ AddElement(/datum/element/wearable_client_colour, glass_colour_type, ITEM_SLOT_EYES, forced = forced_glass_color)
/obj/item/clothing/glasses/meson
name = "optical meson scanner"
@@ -117,8 +92,14 @@
inhand_icon_state = "nvgmeson"
flash_protect = FLASH_PROTECTION_SENSITIVE
// Night vision mesons get the same but more intense
- color_cutoffs = list(10, 30, 10)
- glass_colour_type = /datum/client_colour/glass_colour/green
+ color_cutoffs = list(10, 35, 10)
+ glass_colour_type = /datum/client_colour/glass_colour/lightgreen
+ actions_types = list(/datum/action/item_action/toggle_nv)
+ forced_glass_color = TRUE
+
+/obj/item/clothing/glasses/meson/night/update_icon_state()
+ . = ..()
+ icon_state = length(color_cutoffs) ? initial(icon_state) : "nvgmeson_off"
/obj/item/clothing/glasses/meson/gar
name = "gar mesons"
@@ -163,8 +144,14 @@
icon_state = "scihudnight"
flash_protect = FLASH_PROTECTION_SENSITIVE
// Real vivid purple
- color_cutoffs = list(50, 10, 30)
- glass_colour_type = /datum/client_colour/glass_colour/green
+ color_cutoffs = list(30, 5, 15)
+ glass_colour_type = /datum/client_colour/glass_colour/lightpurple
+ actions_types = list(/datum/action/item_action/toggle_nv)
+ forced_glass_color = TRUE
+
+/obj/item/clothing/glasses/science/night/update_icon_state()
+ . = ..()
+ icon_state = length(color_cutoffs) ? initial(icon_state) : "night_off"
/obj/item/clothing/glasses/night
name = "night vision goggles"
@@ -174,8 +161,18 @@
flags_cover = GLASSESCOVERSEYES
flash_protect = FLASH_PROTECTION_SENSITIVE
// Dark green
- color_cutoffs = list(10, 30, 10)
- glass_colour_type = /datum/client_colour/glass_colour/green
+ color_cutoffs = list(10, 25, 10)
+ glass_colour_type = /datum/client_colour/glass_colour/lightgreen
+ actions_types = list(/datum/action/item_action/toggle_nv)
+ forced_glass_color = TRUE
+
+/obj/item/clothing/glasses/night/update_icon_state()
+ . = ..()
+ icon_state = length(color_cutoffs) ? initial(icon_state) : "night_off"
+
+/obj/item/clothing/glasses/night/colorless
+ desc = parent_type::desc + " Now with 50% less green!"
+ forced_glass_color = FALSE
/obj/item/clothing/glasses/eyepatch
name = "eyepatch"
@@ -351,8 +348,8 @@
/obj/item/clothing/glasses/sunglasses/proc/add_glasses_slapcraft_component()
var/static/list/slapcraft_recipe_list = list(/datum/crafting_recipe/hudsunsec, /datum/crafting_recipe/hudsunmed, /datum/crafting_recipe/hudsundiag, /datum/crafting_recipe/scienceglasses)
- AddComponent(
- /datum/component/slapcrafting,\
+ AddElement(
+ /datum/element/slapcrafting,\
slapcraft_recipes = slapcraft_recipe_list,\
)
@@ -371,8 +368,8 @@
/obj/item/clothing/glasses/sunglasses/chemical/add_glasses_slapcraft_component()
var/static/list/slapcraft_recipe_list = list(/datum/crafting_recipe/scienceglassesremoval)
- AddComponent(
- /datum/component/slapcrafting,\
+ AddElement(
+ /datum/element/slapcrafting,\
slapcraft_recipes = slapcraft_recipe_list,\
)
diff --git a/code/modules/clothing/glasses/engine_goggles.dm b/code/modules/clothing/glasses/engine_goggles.dm
index c9fbb159126f3..064a24cf5c779 100644
--- a/code/modules/clothing/glasses/engine_goggles.dm
+++ b/code/modules/clothing/glasses/engine_goggles.dm
@@ -47,21 +47,21 @@
if(MODE_MESON)
vision_flags = SEE_TURFS
color_cutoffs = list(15, 12, 0)
- change_glass_color(user, /datum/client_colour/glass_colour/yellow)
+ change_glass_color(/datum/client_colour/glass_colour/yellow)
if(MODE_TRAY) //undoes the last mode, meson
vision_flags = NONE
color_cutoffs = null
- change_glass_color(user, /datum/client_colour/glass_colour/lightblue)
+ change_glass_color(/datum/client_colour/glass_colour/lightblue)
if(MODE_PIPE_CONNECTABLE)
- change_glass_color(user, /datum/client_colour/glass_colour/lightblue)
+ change_glass_color(/datum/client_colour/glass_colour/lightblue)
if(MODE_SHUTTLE)
- change_glass_color(user, /datum/client_colour/glass_colour/red)
+ change_glass_color(/datum/client_colour/glass_colour/red)
if(MODE_NONE)
- change_glass_color(user, initial(glass_colour_type))
+ change_glass_color(initial(glass_colour_type))
if(ishuman(user))
var/mob/living/carbon/human/H = user
diff --git a/code/modules/clothing/glasses/hud.dm b/code/modules/clothing/glasses/hud.dm
index 46900c1caa9a5..4f5668f7bcce7 100644
--- a/code/modules/clothing/glasses/hud.dm
+++ b/code/modules/clothing/glasses/hud.dm
@@ -4,7 +4,7 @@
flags_1 = null //doesn't protect eyes because it's a monocle, duh
var/hud_type = null
- // NOTE: Just because you have a HUD display doesn't mean you should be able to interact with stuff on examine, that's where the associated trait (TRAIT_MEDICAL_HUD, TRAIT_SECURITY_HUD, etc) is necessary.
+ // NOTE: Just because you have a HUD display doesn't mean you should be able to interact with stuff on examine, that's where the associated trait (TRAIT_MEDICAL_HUD, TRAIT_SECURITY_HUD, etc) is necessary.
/obj/item/clothing/glasses/hud/equipped(mob/living/carbon/human/user, slot)
..()
@@ -67,8 +67,14 @@
flash_protect = FLASH_PROTECTION_SENSITIVE
flags_cover = GLASSESCOVERSEYES
// Blue green, dark
- color_cutoffs = list(5, 15, 30)
- glass_colour_type = /datum/client_colour/glass_colour/green
+ color_cutoffs = list(20, 20, 45)
+ glass_colour_type = /datum/client_colour/glass_colour/lightgreen
+ actions_types = list(/datum/action/item_action/toggle_nv)
+ forced_glass_color = TRUE
+
+/obj/item/clothing/glasses/hud/health/night/update_icon_state()
+ . = ..()
+ icon_state = length(color_cutoffs) ? initial(icon_state) : "night_off"
/obj/item/clothing/glasses/hud/health/night/meson
name = "night vision meson health scanner HUD"
@@ -79,7 +85,8 @@
name = "night vision medical science scanner HUD"
desc = "An clandestine medical science heads-up display that allows operatives to find \
dying captains and the perfect poison to finish them off in complete darkness."
- clothing_traits = list(TRAIT_REAGENT_SCANNER)
+ clothing_traits = list(TRAIT_REAGENT_SCANNER, TRAIT_MEDICAL_HUD)
+ forced_glass_color = FALSE
/obj/item/clothing/glasses/hud/health/sunglasses
name = "medical HUDSunglasses"
@@ -94,8 +101,8 @@
. = ..()
var/static/list/slapcraft_recipe_list = list(/datum/crafting_recipe/hudsunmedremoval)
- AddComponent(
- /datum/component/slapcrafting,\
+ AddElement(
+ /datum/element/slapcrafting,\
slapcraft_recipes = slapcraft_recipe_list,\
)
@@ -115,8 +122,14 @@
flash_protect = FLASH_PROTECTION_SENSITIVE
flags_cover = GLASSESCOVERSEYES
// Pale yellow
- color_cutoffs = list(30, 20, 5)
- glass_colour_type = /datum/client_colour/glass_colour/green
+ color_cutoffs = list(25, 15, 5)
+ glass_colour_type = /datum/client_colour/glass_colour/lightyellow
+ actions_types = list(/datum/action/item_action/toggle_nv)
+ forced_glass_color = TRUE
+
+/obj/item/clothing/glasses/hud/diagnostic/night/update_icon_state()
+ . = ..()
+ icon_state = length(color_cutoffs) ? initial(icon_state) : "night_off"
/obj/item/clothing/glasses/hud/diagnostic/sunglasses
name = "diagnostic sunglasses"
@@ -131,8 +144,8 @@
. = ..()
var/static/list/slapcraft_recipe_list = list(/datum/crafting_recipe/hudsundiagremoval)
- AddComponent(
- /datum/component/slapcrafting,\
+ AddElement(
+ /datum/element/slapcrafting,\
slapcraft_recipes = slapcraft_recipe_list,\
)
@@ -175,8 +188,8 @@
. = ..()
var/static/list/slapcraft_recipe_list = list(/datum/crafting_recipe/hudsunsecremoval)
- AddComponent(
- /datum/component/slapcrafting,\
+ AddElement(
+ /datum/element/slapcrafting,\
slapcraft_recipes = slapcraft_recipe_list,\
)
@@ -187,8 +200,14 @@
flash_protect = FLASH_PROTECTION_SENSITIVE
flags_cover = GLASSESCOVERSEYES
// Red with a tint of green
- color_cutoffs = list(35, 5, 5)
- glass_colour_type = /datum/client_colour/glass_colour/green
+ color_cutoffs = list(40, 15, 10)
+ glass_colour_type = /datum/client_colour/glass_colour/lightred
+ actions_types = list(/datum/action/item_action/toggle_nv)
+ forced_glass_color = TRUE
+
+/obj/item/clothing/glasses/hud/security/night/update_icon_state()
+ . = ..()
+ icon_state = length(color_cutoffs) ? initial(icon_state) : "night_off"
/obj/item/clothing/glasses/hud/security/sunglasses/gars
name = "\improper HUD gar glasses"
@@ -257,15 +276,15 @@
if (DATA_HUD_MEDICAL_ADVANCED)
icon_state = "meson"
color_cutoffs = list(5, 15, 5)
- change_glass_color(user, /datum/client_colour/glass_colour/green)
+ change_glass_color(/datum/client_colour/glass_colour/green)
if (DATA_HUD_SECURITY_ADVANCED)
icon_state = "thermal"
color_cutoffs = list(25, 8, 5)
- change_glass_color(user, /datum/client_colour/glass_colour/red)
+ change_glass_color(/datum/client_colour/glass_colour/red)
else
icon_state = "purple"
color_cutoffs = list(15, 0, 25)
- change_glass_color(user, /datum/client_colour/glass_colour/purple)
+ change_glass_color(/datum/client_colour/glass_colour/purple)
user.update_sight()
user.update_worn_glasses()
diff --git a/code/modules/clothing/gloves/boxing.dm b/code/modules/clothing/gloves/boxing.dm
index 03b1cbb5bf782..021d895f69c36 100644
--- a/code/modules/clothing/gloves/boxing.dm
+++ b/code/modules/clothing/gloves/boxing.dm
@@ -13,8 +13,8 @@
. = ..()
var/static/list/slapcraft_recipe_list = list(/datum/crafting_recipe/extendohand_l, /datum/crafting_recipe/extendohand_r)
- AddComponent(
- /datum/component/slapcrafting,\
+ AddElement(
+ /datum/element/slapcrafting,\
slapcraft_recipes = slapcraft_recipe_list,\
)
diff --git a/code/modules/clothing/gloves/color.dm b/code/modules/clothing/gloves/color.dm
index ddb0e07dd9986..bb0e12809955e 100644
--- a/code/modules/clothing/gloves/color.dm
+++ b/code/modules/clothing/gloves/color.dm
@@ -15,8 +15,8 @@
. = ..()
var/static/list/slapcraft_recipe_list = list(/datum/crafting_recipe/radiogloves)
- AddComponent(
- /datum/component/slapcrafting,\
+ AddElement(
+ /datum/element/slapcrafting,\
slapcraft_recipes = slapcraft_recipe_list,\
)
@@ -37,8 +37,8 @@
. = ..()
var/static/list/slapcraft_recipe_list = list(/datum/crafting_recipe/gripperoffbrand)
- AddComponent(
- /datum/component/slapcrafting,\
+ AddElement(
+ /datum/element/slapcrafting,\
slapcraft_recipes = slapcraft_recipe_list,\
)
diff --git a/code/modules/clothing/head/frenchberet.dm b/code/modules/clothing/head/frenchberet.dm
index 40d8abc5b62ce..de63c6fddfdd1 100644
--- a/code/modules/clothing/head/frenchberet.dm
+++ b/code/modules/clothing/head/frenchberet.dm
@@ -6,37 +6,17 @@
greyscale_config_worn = /datum/greyscale_config/beret/worn
greyscale_colors = "#972A2A"
+/obj/item/clothing/head/frenchberet/Initialize(mapload)
+ . = ..()
+ AddComponent(/datum/component/speechmod, replacements = strings("french_replacement.json", "french"), end_string = list(" Honh honh honh!"," Honh!"," Zut Alors!"), end_string_chance = 3, slots = ITEM_SLOT_HEAD)
/obj/item/clothing/head/frenchberet/equipped(mob/M, slot)
. = ..()
if (slot & ITEM_SLOT_HEAD)
- RegisterSignal(M, COMSIG_MOB_SAY, PROC_REF(handle_speech))
ADD_TRAIT(M, TRAIT_GARLIC_BREATH, type)
else
- UnregisterSignal(M, COMSIG_MOB_SAY)
REMOVE_TRAIT(M, TRAIT_GARLIC_BREATH, type)
/obj/item/clothing/head/frenchberet/dropped(mob/M)
. = ..()
- UnregisterSignal(M, COMSIG_MOB_SAY)
REMOVE_TRAIT(M, TRAIT_GARLIC_BREATH, type)
-
-/obj/item/clothing/head/frenchberet/proc/handle_speech(datum/source, list/speech_args)
- SIGNAL_HANDLER
- var/message = speech_args[SPEECH_MESSAGE]
- if(message[1] != "*")
- message = " [message]"
- var/list/french_words = strings("french_replacement.json", "french")
-
- for(var/key in french_words)
- var/value = french_words[key]
- if(islist(value))
- value = pick(value)
-
- message = replacetextEx(message, " [uppertext(key)]", " [uppertext(value)]")
- message = replacetextEx(message, " [capitalize(key)]", " [capitalize(value)]")
- message = replacetextEx(message, " [key]", " [value]")
-
- if(prob(3))
- message += pick(" Honh honh honh!"," Honh!"," Zut Alors!")
- speech_args[SPEECH_MESSAGE] = trim(message)
diff --git a/code/modules/clothing/head/hardhat.dm b/code/modules/clothing/head/hardhat.dm
index 018543d93a33c..fc56f83342346 100644
--- a/code/modules/clothing/head/hardhat.dm
+++ b/code/modules/clothing/head/hardhat.dm
@@ -123,7 +123,7 @@
flash_protect = FLASH_PROTECTION_WELDER
tint = 2
flags_inv = HIDEEYES | HIDEFACE | HIDESNOUT
- flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH
+ flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH | PEPPERPROOF
visor_vars_to_toggle = VISOR_FLASHPROTECT | VISOR_TINT
visor_flags_inv = HIDEEYES | HIDEFACE | HIDESNOUT
visor_flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH | PEPPERPROOF
@@ -158,6 +158,14 @@
if(!up)
. += visor_state
+/obj/item/clothing/head/utility/hardhat/welding/up
+ up = TRUE // for calls to worn_overlays before init (prefs)
+
+/obj/item/clothing/head/utility/hardhat/welding/up/Initialize(mapload)
+ . = ..()
+ up = FALSE
+ visor_toggling()
+
/obj/item/clothing/head/utility/hardhat/welding/orange
icon_state = "hardhat0_orange"
inhand_icon_state = null
@@ -175,6 +183,15 @@
cold_protection = HEAD
min_cold_protection_temperature = FIRE_HELM_MIN_TEMP_PROTECT
+/obj/item/clothing/head/utility/hardhat/welding/white/up
+ up = TRUE // for calls to worn_overlays before init (prefs)
+
+/obj/item/clothing/head/utility/hardhat/welding/white/up/Initialize(mapload)
+ . = ..()
+ up = FALSE
+ visor_toggling()
+
+
/obj/item/clothing/head/utility/hardhat/welding/dblue
icon_state = "hardhat0_dblue"
inhand_icon_state = null
@@ -226,6 +243,10 @@
. = ..()
if(isnull(.))
return
+ if(new_value)
+ AddElement(/datum/element/wearable_client_colour, /datum/client_colour/halloween_helmet, ITEM_SLOT_HEAD, forced = TRUE)
+ else
+ RemoveElement(/datum/element/wearable_client_colour, /datum/client_colour/halloween_helmet, ITEM_SLOT_HEAD, forced = TRUE)
update_icon(UPDATE_OVERLAYS)
/obj/item/clothing/head/utility/hardhat/pumpkinhead/update_overlays()
diff --git a/code/modules/clothing/head/helmet.dm b/code/modules/clothing/head/helmet.dm
index 27be2799b7d2e..4d41d9daa5c76 100644
--- a/code/modules/clothing/head/helmet.dm
+++ b/code/modules/clothing/head/helmet.dm
@@ -4,6 +4,7 @@
icon = 'icons/obj/clothing/head/helmet.dmi'
worn_icon = 'icons/mob/clothing/head/helmet.dmi'
icon_state = "helmet"
+ base_icon_state = "helmet"
inhand_icon_state = "helmet"
armor_type = /datum/armor/head_helmet
cold_protection = HEAD
@@ -31,6 +32,7 @@
AddElement(/datum/element/update_icon_updates_onmob)
/obj/item/clothing/head/helmet/sec
+ var/flipped_visor = FALSE
/obj/item/clothing/head/helmet/sec/Initialize(mapload)
. = ..()
@@ -60,6 +62,19 @@
return ..()
+/obj/item/clothing/head/helmet/sec/click_alt(mob/user)
+ flipped_visor = !flipped_visor
+ balloon_alert(user, "visor flipped")
+ // base_icon_state is modified for seclight attachment component
+ base_icon_state = "[initial(base_icon_state)][flipped_visor ? "-novisor" : ""]"
+ icon_state = base_icon_state
+ if (flipped_visor)
+ flags_cover &= ~HEADCOVERSEYES
+ else
+ flags_cover |= HEADCOVERSEYES
+ update_appearance()
+ return CLICK_ACTION_SUCCESS
+
/obj/item/clothing/head/helmet/alt
name = "bulletproof helmet"
desc = "A bulletproof combat helmet that excels in protecting the wearer against traditional projectile weaponry and explosives to a minor extent."
@@ -395,6 +410,7 @@
armor_type = /datum/armor/helmet_knight
flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDESNOUT
flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH
+ resistance_flags = NONE
strip_delay = 80
dog_fashion = null
clothing_traits = list(TRAIT_HEAD_INJURY_BLOCKED)
diff --git a/code/modules/clothing/head/jobs.dm b/code/modules/clothing/head/jobs.dm
index 5ea7d62313bd4..a5041de7fa0a9 100644
--- a/code/modules/clothing/head/jobs.dm
+++ b/code/modules/clothing/head/jobs.dm
@@ -412,8 +412,8 @@
/datum/crafting_recipe/sturdy_shako,\
)
- AddComponent(
- /datum/component/slapcrafting,\
+ AddElement(
+ /datum/element/slapcrafting,\
slapcraft_recipes = slapcraft_recipe_list,\
)
diff --git a/code/modules/clothing/head/welding.dm b/code/modules/clothing/head/welding.dm
index cb785447174f7..e3f014875dde4 100644
--- a/code/modules/clothing/head/welding.dm
+++ b/code/modules/clothing/head/welding.dm
@@ -2,7 +2,7 @@
name = "welding helmet"
desc = "A head-mounted face cover designed to protect the wearer completely from space-arc eye."
icon_state = "welding"
- flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH
+ flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH | PEPPERPROOF
inhand_icon_state = "welding"
lefthand_file = 'icons/mob/inhands/clothing/masks_lefthand.dmi'
righthand_file = 'icons/mob/inhands/clothing/masks_righthand.dmi'
diff --git a/code/modules/clothing/masks/_masks.dm b/code/modules/clothing/masks/_masks.dm
index c1d29d65c8641..5255b758b6faf 100644
--- a/code/modules/clothing/masks/_masks.dm
+++ b/code/modules/clothing/masks/_masks.dm
@@ -8,7 +8,6 @@
strip_delay = 40
equip_delay_other = 40
visor_vars_to_toggle = NONE
- var/modifies_speech = FALSE
var/adjusted_flags = null
///Did we install a filtering cloth?
var/has_filter = FALSE
@@ -25,31 +24,6 @@
var/status = !(clothing_flags & VOICEBOX_DISABLED)
to_chat(user, span_notice("You turn the voice box in [src] [status ? "on" : "off"]."))
-/obj/item/clothing/mask/equipped(mob/M, slot)
- . = ..()
- if ((slot & ITEM_SLOT_MASK) && modifies_speech)
- RegisterSignal(M, COMSIG_MOB_SAY, PROC_REF(handle_speech))
- else
- UnregisterSignal(M, COMSIG_MOB_SAY)
-
-/obj/item/clothing/mask/dropped(mob/M)
- . = ..()
- UnregisterSignal(M, COMSIG_MOB_SAY)
-
-/obj/item/clothing/mask/vv_edit_var(vname, vval)
- if(vname == NAMEOF(src, modifies_speech) && ismob(loc))
- var/mob/M = loc
- if(M.get_item_by_slot(ITEM_SLOT_MASK) == src)
- if(vval)
- if(!modifies_speech)
- RegisterSignal(M, COMSIG_MOB_SAY, PROC_REF(handle_speech))
- else if(modifies_speech)
- UnregisterSignal(M, COMSIG_MOB_SAY)
- return ..()
-
-/obj/item/clothing/mask/proc/handle_speech()
- SIGNAL_HANDLER
-
/obj/item/clothing/mask/worn_overlays(mutable_appearance/standing, isinhands = FALSE)
. = ..()
if(isinhands)
diff --git a/code/modules/clothing/masks/animal_masks.dm b/code/modules/clothing/masks/animal_masks.dm
index 5df5c6738d8e5..05e5888168e12 100644
--- a/code/modules/clothing/masks/animal_masks.dm
+++ b/code/modules/clothing/masks/animal_masks.dm
@@ -16,7 +16,7 @@ GLOBAL_LIST_INIT(cursed_animal_masks, list(
/obj/item/clothing/mask/animal
w_class = WEIGHT_CLASS_SMALL
clothing_flags = VOICEBOX_TOGGLABLE
- modifies_speech = TRUE
+ var/modifies_speech = TRUE
flags_cover = MASKCOVERSMOUTH
var/animal_type ///what kind of animal the masks represents. used for automatic name and description generation.
@@ -32,6 +32,17 @@ GLOBAL_LIST_INIT(cursed_animal_masks, list(
if(cursed)
make_cursed()
+/obj/item/clothing/mask/animal/equipped(mob/M, slot)
+ . = ..()
+ if ((slot & ITEM_SLOT_MASK) && modifies_speech)
+ RegisterSignal(M, COMSIG_MOB_SAY, PROC_REF(handle_speech))
+ else
+ UnregisterSignal(M, COMSIG_MOB_SAY)
+
+/obj/item/clothing/mask/animal/dropped(mob/M)
+ . = ..()
+ UnregisterSignal(M, COMSIG_MOB_SAY)
+
/obj/item/clothing/mask/animal/vv_edit_var(vname, vval)
if(vname == NAMEOF(src, cursed))
if(vval)
@@ -39,6 +50,14 @@ GLOBAL_LIST_INIT(cursed_animal_masks, list(
make_cursed()
else if(cursed)
clear_curse()
+ if(vname == NAMEOF(src, modifies_speech) && ismob(loc))
+ var/mob/M = loc
+ if(M.get_item_by_slot(ITEM_SLOT_MASK) == src)
+ if(vval)
+ if(!modifies_speech)
+ RegisterSignal(M, COMSIG_MOB_SAY, PROC_REF(handle_speech))
+ else if(modifies_speech)
+ UnregisterSignal(M, COMSIG_MOB_SAY)
return ..()
/obj/item/clothing/mask/animal/examine(mob/user)
@@ -90,7 +109,9 @@ GLOBAL_LIST_INIT(cursed_animal_masks, list(
UnregisterSignal(M, COMSIG_MOB_SAY)
M.update_worn_mask()
-/obj/item/clothing/mask/animal/handle_speech(datum/source, list/speech_args)
+/obj/item/clothing/mask/animal/proc/handle_speech(datum/source, list/speech_args)
+ SIGNAL_HANDLER
+
if(clothing_flags & VOICEBOX_DISABLED)
return
if(!modifies_speech || !LAZYLEN(animal_sounds))
diff --git a/code/modules/clothing/masks/boxing.dm b/code/modules/clothing/masks/boxing.dm
index 468b1272f8604..9ac45c42d9919 100644
--- a/code/modules/clothing/masks/boxing.dm
+++ b/code/modules/clothing/masks/boxing.dm
@@ -34,31 +34,10 @@
inhand_icon_state = null
flags_inv = HIDEFACE|HIDEHAIR|HIDEFACIALHAIR|HIDESNOUT
w_class = WEIGHT_CLASS_SMALL
- modifies_speech = TRUE
-/obj/item/clothing/mask/luchador/handle_speech(datum/source, list/speech_args)
- var/message = speech_args[SPEECH_MESSAGE]
- if(message[1] != "*")
- message = replacetext(message, "captain", "CAPITÁN")
- message = replacetext(message, "station", "ESTACIÓN")
- message = replacetext(message, "sir", "SEÑOR")
- message = replacetext(message, "the ", "el ")
- message = replacetext(message, "my ", "mi ")
- message = replacetext(message, "is ", "es ")
- message = replacetext(message, "it's", "es")
- message = replacetext(message, "friend", "amigo")
- message = replacetext(message, "buddy", "amigo")
- message = replacetext(message, "hello", "hola")
- message = replacetext(message, " hot", " caliente")
- message = replacetext(message, " very ", " muy ")
- message = replacetext(message, "sword", "espada")
- message = replacetext(message, "library", "biblioteca")
- message = replacetext(message, "traitor", "traidor")
- message = replacetext(message, "wizard", "mago")
- message = uppertext(message) //Things end up looking better this way (no mixed cases), and it fits the macho wrestler image.
- if(prob(25))
- message += " OLE!"
- speech_args[SPEECH_MESSAGE] = message
+/obj/item/clothing/head/frenchberet/Initialize(mapload)
+ . = ..()
+ AddComponent(/datum/component/speechmod, replacements = strings("luchador_replacement.json", "luchador"), end_string = " OLE!", end_string_chance = 25, uppercase = TRUE, slots = ITEM_SLOT_MASK)
/obj/item/clothing/mask/luchador/tecnicos
name = "Tecnicos Mask"
diff --git a/code/modules/clothing/masks/gondola.dm b/code/modules/clothing/masks/gondola.dm
index 7a8283293ded2..bfaae3cb3f314 100644
--- a/code/modules/clothing/masks/gondola.dm
+++ b/code/modules/clothing/masks/gondola.dm
@@ -5,18 +5,7 @@
inhand_icon_state = null
flags_inv = HIDEFACE|HIDEHAIR|HIDEFACIALHAIR|HIDESNOUT
w_class = WEIGHT_CLASS_SMALL
- modifies_speech = TRUE
-/obj/item/clothing/mask/gondola/handle_speech(datum/source, list/speech_args)
- var/message = speech_args[SPEECH_MESSAGE]
- if(message[1] != "*")
- message = " [message]"
- var/list/spurdo_words = strings("spurdo_replacement.json", "spurdo")
- for(var/key in spurdo_words)
- var/value = spurdo_words[key]
- if(islist(value))
- value = pick(value)
- message = replacetextEx(message,regex(uppertext(key),"g"), "[uppertext(value)]")
- message = replacetextEx(message,regex(capitalize(key),"g"), "[capitalize(value)]")
- message = replacetextEx(message,regex(key,"g"), "[value]")
- speech_args[SPEECH_MESSAGE] = trim(message)
+/obj/item/clothing/mask/gondola/Initialize(mapload)
+ . = ..()
+ AddComponent(/datum/component/speechmod, replacements = strings("spurdo_replacement.json", "spurdo"), slots = ITEM_SLOT_MASK)
diff --git a/code/modules/clothing/masks/moustache.dm b/code/modules/clothing/masks/moustache.dm
index aaf59be51e4fd..5b71e7a426090 100644
--- a/code/modules/clothing/masks/moustache.dm
+++ b/code/modules/clothing/masks/moustache.dm
@@ -10,23 +10,7 @@
/obj/item/clothing/mask/fakemoustache/italian
name = "italian moustache"
desc = "Made from authentic Italian moustache hairs. Gives the wearer an irresistable urge to gesticulate wildly."
- modifies_speech = TRUE
-/obj/item/clothing/mask/fakemoustache/italian/handle_speech(datum/source, list/speech_args)
- var/message = speech_args[SPEECH_MESSAGE]
- if(message[1] != "*")
- message = " [message]"
- var/list/italian_words = strings("italian_replacement.json", "italian")
-
- for(var/key in italian_words)
- var/value = italian_words[key]
- if(islist(value))
- value = pick(value)
-
- message = replacetextEx(message, " [uppertext(key)]", " [uppertext(value)]")
- message = replacetextEx(message, " [capitalize(key)]", " [capitalize(value)]")
- message = replacetextEx(message, " [key]", " [value]")
-
- if(prob(3))
- message += pick(" Ravioli, ravioli, give me the formuoli!"," Mamma-mia!"," Mamma-mia! That's a spicy meat-ball!", " La la la la la funiculi funicula!")
- speech_args[SPEECH_MESSAGE] = trim(message)
+/obj/item/clothing/mask/fakemoustache/italian/Initialize(mapload)
+ . = ..()
+ AddComponent(/datum/component/speechmod, replacements = strings("italian_replacement.json", "italian"), end_string = list(" Ravioli, ravioli, give me the formuoli!"," Mamma-mia!"," Mamma-mia! That's a spicy meat-ball!", " La la la la la funiculi funicula!"), end_string_chance = 3, slots = ITEM_SLOT_MASK)
diff --git a/code/modules/clothing/masks/muzzle.dm b/code/modules/clothing/masks/muzzle.dm
index 32f29a54a1f43..6154e7762cb52 100644
--- a/code/modules/clothing/masks/muzzle.dm
+++ b/code/modules/clothing/masks/muzzle.dm
@@ -5,11 +5,14 @@
inhand_icon_state = "blindfold"
lefthand_file = 'icons/mob/inhands/clothing/glasses_lefthand.dmi'
righthand_file = 'icons/mob/inhands/clothing/glasses_righthand.dmi'
- clothing_flags = BLOCKS_SPEECH
flags_cover = MASKCOVERSMOUTH
w_class = WEIGHT_CLASS_SMALL
equip_delay_other = 20
+/obj/item/clothing/mask/muzzle/Initialize(mapload)
+ . = ..()
+ AddElement(/datum/element/muffles_speech)
+
/obj/item/clothing/mask/muzzle/attack_paw(mob/user, list/modifiers)
if(iscarbon(user))
var/mob/living/carbon/carbon_user = user
@@ -26,7 +29,7 @@
lefthand_file = 'icons/mob/inhands/clothing/masks_lefthand.dmi'
righthand_file = 'icons/mob/inhands/clothing/masks_righthand.dmi'
body_parts_covered = NONE
- clothing_flags = MASKINTERNALS | BLOCKS_SPEECH
+ clothing_flags = MASKINTERNALS
armor_type = /datum/armor/muzzle_breath
equip_delay_other = 25 // my sprite has 4 straps, a-la a head harness. takes a while to equip, longer than a muzzle
@@ -37,7 +40,7 @@
worn_icon_state = "tape_piece_worn"
inhand_icon_state = null
w_class = WEIGHT_CLASS_TINY
- clothing_flags = INEDIBLE_CLOTHING|BLOCKS_SPEECH
+ clothing_flags = INEDIBLE_CLOTHING
equip_delay_other = 40
strip_delay = 40
greyscale_config = /datum/greyscale_config/tape_piece
diff --git a/code/modules/clothing/outfits/ert.dm b/code/modules/clothing/outfits/ert.dm
index db20562039b23..2aa61b0046070 100644
--- a/code/modules/clothing/outfits/ert.dm
+++ b/code/modules/clothing/outfits/ert.dm
@@ -242,7 +242,7 @@
/obj/item/storage/box/lights/mixed = 1,
)
belt = /obj/item/storage/belt/janitor/full
- glasses = /obj/item/clothing/glasses/night
+ glasses = /obj/item/clothing/glasses/night/colorless
l_pocket = /obj/item/grenade/chem_grenade/cleaner
r_pocket = /obj/item/grenade/chem_grenade/cleaner
l_hand = /obj/item/storage/bag/trash/bluespace
diff --git a/code/modules/clothing/spacesuits/pirate.dm b/code/modules/clothing/spacesuits/pirate.dm
index 946c0c2b66fdd..ca041d68d036f 100644
--- a/code/modules/clothing/spacesuits/pirate.dm
+++ b/code/modules/clothing/spacesuits/pirate.dm
@@ -30,3 +30,15 @@
armor_type = /datum/armor/space_pirate
strip_delay = 40
equip_delay_other = 20
+
+/obj/item/clothing/head/helmet/space/pirate/tophat
+ name = "designer pirate helmet"
+ desc = "A modified EVA helmet with a five-thousand credit Lizzy Vuitton hat affixed to the top, proving that working in deep space is no excuse for being poor."
+ icon_state = "spacetophat"
+
+/obj/item/clothing/suit/space/pirate/silverscale
+ name = "designer pirate suit"
+ desc = "A specially-made Cybersun branded space suit; the fine plastisilk exterior is woven from the coccons of black-market Lümlan mothroaches \
+ and the trim is lined with the ivory of the critically endagered Zanzibarian dwarf elephant. Baby seal leather boots sold seperately."
+ inhand_icon_state = "syndicate-black"
+ icon_state = "syndicate-black-white"
diff --git a/code/modules/clothing/spacesuits/syndi.dm b/code/modules/clothing/spacesuits/syndi.dm
index 67702582ff857..9cca1cfc3af08 100644
--- a/code/modules/clothing/spacesuits/syndi.dm
+++ b/code/modules/clothing/spacesuits/syndi.dm
@@ -28,6 +28,7 @@ GLOBAL_LIST_INIT(syndicate_space_suits_to_helmets,list(
/obj/item/clothing/suit/space/syndicate/black/blue = /obj/item/clothing/head/helmet/space/syndicate/black/blue,
/obj/item/clothing/suit/space/syndicate/black/orange = /obj/item/clothing/head/helmet/space/syndicate/black/orange,
/obj/item/clothing/suit/space/syndicate/black/red = /obj/item/clothing/head/helmet/space/syndicate/black/red,
+ /obj/item/clothing/suit/space/syndicate/black/white = /obj/item/clothing/head/helmet/space/syndicate/black,
/obj/item/clothing/suit/space/syndicate/black/med = /obj/item/clothing/head/helmet/space/syndicate/black/med,
/obj/item/clothing/suit/space/syndicate/black/engie = /obj/item/clothing/head/helmet/space/syndicate/black/engie,
))
@@ -132,6 +133,13 @@ GLOBAL_LIST_INIT(syndicate_space_suits_to_helmets,list(
inhand_icon_state = "syndicate-black-blue"
helmet_type = /obj/item/clothing/head/helmet/space/syndicate/black/blue
+//Black and white syndicate space suit without the medical iconography
+/obj/item/clothing/suit/space/syndicate/black/white
+ name = "black and white space suit"
+ icon_state = "syndicate-black-white"
+ inhand_icon_state = "syndicate-black"
+ helmet_type = /obj/item/clothing/head/helmet/space/syndicate/black
+
//Black medical syndicate space suit
/obj/item/clothing/head/helmet/space/syndicate/black/med
diff --git a/code/modules/clothing/suits/costume.dm b/code/modules/clothing/suits/costume.dm
index 9cf86a396e95a..929e8d931d5ca 100644
--- a/code/modules/clothing/suits/costume.dm
+++ b/code/modules/clothing/suits/costume.dm
@@ -21,6 +21,10 @@
body_parts_covered = HEAD
flags_inv = HIDEHAIR|HIDEEARS|HIDEFACIALHAIR|HIDEFACE|HIDEMASK|HIDESNOUT
+/obj/item/clothing/head/hooded/flashsuit/Initialize(mapload)
+ . = ..()
+ AddElement(/datum/element/wearable_client_colour, /datum/client_colour/flash_hood, ITEM_SLOT_HEAD, forced = TRUE)
+
/obj/item/clothing/suit/costume/pirate
name = "pirate coat"
desc = "Yarr."
diff --git a/code/modules/clothing/suits/jobs.dm b/code/modules/clothing/suits/jobs.dm
index f333ec53917a9..2b8fef5bb5b46 100644
--- a/code/modules/clothing/suits/jobs.dm
+++ b/code/modules/clothing/suits/jobs.dm
@@ -385,27 +385,6 @@
body_parts_covered = HEAD
flags_inv = HIDEHAIR|HIDEEARS
-// Research Director
-
-/obj/item/clothing/suit/jacket/research_director
- name = "research director's coat"
- desc = "A mix between a labcoat and just a regular coat. It's made out of a special anti-bacterial, anti-acidic, and anti-biohazardous synthetic fabric."
- icon_state = "labcoat_rd"
- armor_type = /datum/armor/jacket_research_director
- body_parts_covered = CHEST|GROIN|ARMS
-
-/datum/armor/jacket_research_director
- bio = 75
- fire = 75
- acid = 75
-
-/obj/item/clothing/suit/jacket/research_director/Initialize(mapload)
- . = ..()
- allowed += list(
- /obj/item/storage/bag/xeno,
- /obj/item/melee/baton/telescopic,
- )
-
// Atmos
/obj/item/clothing/suit/atmos_overalls
name = "atmospherics overalls"
diff --git a/code/modules/clothing/suits/labcoat.dm b/code/modules/clothing/suits/labcoat.dm
index f48e49c11b701..e5cda21a78a77 100644
--- a/code/modules/clothing/suits/labcoat.dm
+++ b/code/modules/clothing/suits/labcoat.dm
@@ -69,6 +69,10 @@
greyscale_config_worn = /datum/greyscale_config/labcoat/worn
greyscale_colors = "#EEEEEE#4A77A1#4A77A1#7095C2"
+/obj/item/clothing/suit/toggle/labcoat/genetics/Initialize(mapload)
+ . = ..()
+ allowed += /obj/item/sequence_scanner
+
/obj/item/clothing/suit/toggle/labcoat/chemist
name = "chemist labcoat"
desc = "A suit that protects against minor chemical spills. Has an orange stripe on the shoulder."
@@ -138,3 +142,24 @@
greyscale_config = /datum/greyscale_config/labcoat
greyscale_config_worn = /datum/greyscale_config/labcoat/worn
greyscale_colors = "#EEEEEE#88242D#88242D#39393F"
+
+// Research Director
+
+/obj/item/clothing/suit/toggle/labcoat/research_director
+ name = "research director's coat"
+ desc = "A mix between a labcoat and just a regular coat. It's made out of a special anti-bacterial, anti-acidic, and anti-biohazardous synthetic fabric."
+ icon_state = "labcoat_rd"
+ armor_type = /datum/armor/jacket_research_director
+ body_parts_covered = CHEST|GROIN|ARMS
+
+/datum/armor/jacket_research_director
+ bio = 75
+ fire = 75
+ acid = 75
+
+/obj/item/clothing/suit/toggle/labcoat/research_director/Initialize(mapload)
+ . = ..()
+ allowed += list(
+ /obj/item/storage/bag/xeno,
+ /obj/item/melee/baton/telescopic,
+ )
diff --git a/code/modules/clothing/suits/wintercoats.dm b/code/modules/clothing/suits/wintercoats.dm
index 02c3399db7b4d..dcc33fb65376b 100644
--- a/code/modules/clothing/suits/wintercoats.dm
+++ b/code/modules/clothing/suits/wintercoats.dm
@@ -49,6 +49,7 @@
/obj/item/clothing/suit/hooded/wintercoat/click_alt(mob/user)
zipped = !zipped
+ playsound(src, 'sound/items/zip_up.ogg', 30, TRUE, -3)
worn_icon_state = "[initial(icon_state)][zipped ? "_t" : ""]"
balloon_alert(user, "[zipped ? "" : "un"]zipped")
@@ -502,6 +503,10 @@
inhand_icon_state = null
hoodtype = /obj/item/clothing/head/hooded/winterhood/science/genetics
+/obj/item/clothing/suit/hooded/wintercoat/science/genetics/Initialize(mapload)
+ . = ..()
+ allowed += /obj/item/sequence_scanner
+
/obj/item/clothing/head/hooded/winterhood/science/genetics
desc = "A white winter coat hood. It's warm."
icon_state = "hood_genetics"
diff --git a/code/modules/deathmatch/deathmatch_loadouts.dm b/code/modules/deathmatch/deathmatch_loadouts.dm
index a24663b3ba439..911e5bf6e1b15 100644
--- a/code/modules/deathmatch/deathmatch_loadouts.dm
+++ b/code/modules/deathmatch/deathmatch_loadouts.dm
@@ -673,7 +673,7 @@
/datum/outfit/deathmatch_loadout/battler/clown/upgraded
name = "Deathmatch: Clown (Syndicate Gear)"
- display_name = "Clown"
+ display_name = "Clown Commando"
desc = "They were bound to show up sooner or later."
shoes = /obj/item/clothing/shoes/clown_shoes/combat
@@ -727,7 +727,7 @@
/datum/outfit/deathmatch_loadout/chef/upgraded
name = "Deathmatch: Master Chef"
- display_name = "Chef"
+ display_name = "Master Chef"
desc = "Let him cook."
belt = /obj/item/gun/magic/hook
diff --git a/code/modules/deathmatch/deathmatch_lobby.dm b/code/modules/deathmatch/deathmatch_lobby.dm
index 028653a2f2124..ffc41c887162d 100644
--- a/code/modules/deathmatch/deathmatch_lobby.dm
+++ b/code/modules/deathmatch/deathmatch_lobby.dm
@@ -21,6 +21,8 @@
var/list/modifiers = list()
/// Is the modifiers modal menu open (for the host)
var/mod_menu_open = FALSE
+ /// artificial time padding when we start loading to give lighting a breather (admin starts will set this to 0)
+ var/start_time = 8 SECONDS
/datum/deathmatch_lobby/New(mob/player)
. = ..()
@@ -79,7 +81,7 @@
UnregisterSignal(source, COMSIG_LAZY_TEMPLATE_LOADED)
map.template_in_use = FALSE
- addtimer(CALLBACK(src, PROC_REF(start_game_after_delay)), 8 SECONDS)
+ addtimer(CALLBACK(src, PROC_REF(start_game_after_delay)), start_time)
/datum/deathmatch_lobby/proc/start_game_after_delay()
if (!length(player_spawns) || length(player_spawns) < length(players))
@@ -152,7 +154,14 @@
GLOB.deathmatch_game.modifiers[modifier].apply(new_player, src)
// register death handling.
- RegisterSignals(new_player, list(COMSIG_LIVING_DEATH, COMSIG_MOB_GHOSTIZED, COMSIG_QDELETING), PROC_REF(player_died))
+ register_player_signals(new_player)
+
+/datum/deathmatch_lobby/proc/register_player_signals(new_player)
+ RegisterSignals(new_player, list(COMSIG_LIVING_DEATH, COMSIG_QDELETING, COMSIG_MOB_GHOSTIZED), PROC_REF(player_died))
+ RegisterSignal(new_player, COMSIG_LIVING_ON_WABBAJACKED, PROC_REF(player_wabbajacked))
+
+/datum/deathmatch_lobby/proc/unregister_player_signals(new_player)
+ UnregisterSignal(new_player, list(COMSIG_LIVING_DEATH, COMSIG_QDELETING, COMSIG_MOB_GHOSTIZED, COMSIG_LIVING_ON_WABBAJACKED))
/datum/deathmatch_lobby/proc/game_took_too_long()
if (!location || QDELING(src))
@@ -173,9 +182,9 @@
for(var/ckey in players)
var/mob/loser = players[ckey]["mob"]
- UnregisterSignal(loser, list(COMSIG_MOB_GHOSTIZED, COMSIG_QDELETING))
+ unregister_player_signals(loser)
players[ckey]["mob"] = null
- loser.ghostize()
+ loser.ghostize(can_reenter_corpse = FALSE)
qdel(loser)
for(var/datum/deathmatch_modifier/modifier in modifiers)
@@ -185,12 +194,18 @@
GLOB.deathmatch_game.remove_lobby(host)
log_game("Deathmatch game [host] ended.")
+/datum/deathmatch_lobby/proc/player_wabbajacked(mob/living/player, mob/living/new_mob)
+ SIGNAL_HANDLER
+ unregister_player_signals(player)
+ players[player.ckey]["mob"] = new_mob
+ register_player_signals(new_mob)
+
/datum/deathmatch_lobby/proc/player_died(mob/living/player, gibbed)
SIGNAL_HANDLER
- if(isnull(player) || QDELING(src))
+ if(isnull(player) || QDELING(src) || HAS_TRAIT_FROM(player, TRAIT_NO_TRANSFORM, MAGIC_TRAIT)) //this trait check fixes polymorphing
return
- var/ckey = player.ckey
+ var/ckey = player.ckey ? player.ckey : player.mind?.key
if(!islist(players[ckey])) // potentially the player info could hold a reference to this mob so we can figure the ckey out without worrying about ghosting and suicides n such
for(var/potential_ckey in players)
var/list/player_info = players[potential_ckey]
@@ -209,8 +224,9 @@
announce(span_reallybig("[player.real_name] HAS DIED. [players.len] REMAIN."))
- if(!gibbed && !QDELING(player))
+ if(!gibbed && !QDELING(player) && !isdead(player))
if(!HAS_TRAIT(src, TRAIT_DEATHMATCH_EXPLOSIVE_IMPLANTS))
+ unregister_player_signals(player)
player.dust(TRUE, TRUE, TRUE)
if (players.len <= 1)
end_game()
@@ -338,6 +354,7 @@
.["maps"] = list()
for (var/map_key in GLOB.deathmatch_game.maps)
.["maps"] += map_key
+ .["maps"] = sort_list(.["maps"])
/datum/deathmatch_lobby/ui_data(mob/user)
@@ -499,12 +516,13 @@
if ("admin") // Admin functions
if (!check_rights(R_ADMIN))
- message_admins("[usr.key] has attempted to use admin functions in a deathmatch lobby!")
+ message_admins("[usr.key] has attempted to use admin functions in a deathmatch lobby without being an admin!")
log_admin("[key_name(usr)] tried to use the deathmatch lobby admin functions without authorization.")
return
switch (params["func"])
if ("Force start")
log_admin("[key_name(usr)] force started deathmatch lobby [host].")
+ start_time = 0
start_game()
return FALSE
diff --git a/code/modules/deathmatch/deathmatch_mapping.dm b/code/modules/deathmatch/deathmatch_mapping.dm
index 320b87d2fd15d..9f006e1524295 100644
--- a/code/modules/deathmatch/deathmatch_mapping.dm
+++ b/code/modules/deathmatch/deathmatch_mapping.dm
@@ -2,7 +2,7 @@
name = "Deathmatch Arena"
requires_power = FALSE
has_gravity = STANDARD_GRAVITY
- area_flags = UNIQUE_AREA | NOTELEPORT | EVENT_PROTECTED | QUIET_LOGS
+ area_flags = UNIQUE_AREA | NOTELEPORT | EVENT_PROTECTED | QUIET_LOGS | NO_DEATH_MESSAGE | BINARY_JAMMING
/area/deathmatch/fullbright
static_lighting = FALSE
@@ -12,7 +12,7 @@
name = "Deathmatch Player Spawner"
/area/deathmatch/teleport //Prevent access to cross-z teleportation in the map itself (no wands of safety/teleportation scrolls). Cordons should prevent same-z teleportations outside of the arena.
- area_flags = UNIQUE_AREA | EVENT_PROTECTED | QUIET_LOGS
+ area_flags = /area/deathmatch::area_flags & ~NOTELEPORT
// for the illusion of a moving train
/turf/open/chasm/true/no_smooth/fake_motion_sand
diff --git a/code/modules/events/_event.dm b/code/modules/events/_event.dm
index 358057e1de0d9..0a41f5ffb6c9a 100644
--- a/code/modules/events/_event.dm
+++ b/code/modules/events/_event.dm
@@ -22,7 +22,7 @@
var/holidayID = "" //string which should be in the SSeventss.holidays list if you wish this event to be holiday-specific
//anything with a (non-null) holidayID which does not match holiday, cannot run.
var/wizardevent = FALSE
- var/alert_observers = TRUE //should we let the ghosts and admins know this event is firing
+ var/alert_observers = TRUE //should we let the ghosts know this event is firing
//should be disabled on events that fire a lot
/// Minimum wizard rituals at which to trigger this event, inclusive
@@ -101,15 +101,14 @@
triggering = TRUE
- // We sleep HERE, in pre-event setup (because there's no sense doing it in run_event() since the event is already running!) for the given amount of time to make an admin has enough time to cancel an event un-fitting of the present round.
- if(alert_observers)
- message_admins("Random Event triggering in [DisplayTimeText(RANDOM_EVENT_ADMIN_INTERVENTION_TIME)]: [name]. (CANCEL) (SOMETHING ELSE)")
- sleep(RANDOM_EVENT_ADMIN_INTERVENTION_TIME)
- var/players_amt = get_active_player_count(alive_check = TRUE, afk_check = TRUE, human_check = TRUE)
- if(!can_spawn_event(players_amt))
- message_admins("Second pre-condition check for [name] failed, rerolling...")
- SSevents.spawnEvent(excluded_event = src)
- return EVENT_INTERRUPTED
+ // We sleep HERE, in pre-event setup (because there's no sense doing it in run_event() since the event is already running!) for the given amount of time to make an admin has enough time to cancel an event un-fitting of the present round or at least reroll it.
+ message_admins("Random Event triggering in [DisplayTimeText(RANDOM_EVENT_ADMIN_INTERVENTION_TIME)]: [name]. (CANCEL) (SOMETHING ELSE)")
+ sleep(RANDOM_EVENT_ADMIN_INTERVENTION_TIME)
+ var/players_amt = get_active_player_count(alive_check = TRUE, afk_check = TRUE, human_check = TRUE)
+ if(!can_spawn_event(players_amt))
+ message_admins("Second pre-condition check for [name] failed, rerolling...")
+ SSevents.spawnEvent(excluded_event = src)
+ return EVENT_INTERRUPTED
if(!triggering)
return EVENT_CANCELLED //admin cancelled
diff --git a/code/modules/events/ghost_role/fugitive_event.dm b/code/modules/events/ghost_role/fugitive_event.dm
index 88d63d91bb5fe..9eb792a6f6ab3 100644
--- a/code/modules/events/ghost_role/fugitive_event.dm
+++ b/code/modules/events/ghost_role/fugitive_event.dm
@@ -61,6 +61,7 @@
HUNTER_PACK_RUSSIAN,
HUNTER_PACK_BOUNTY,
HUNTER_PACK_PSYKER,
+ HUNTER_PACK_MI13,
)
addtimer(CALLBACK(src, PROC_REF(check_spawn_hunters), hunter_backstory, 10 MINUTES), 1 MINUTES)
role_name = "fugitive hunter"
@@ -124,6 +125,8 @@
ship = new /datum/map_template/shuttle/hunter/bounty
if(HUNTER_PACK_PSYKER)
ship = new /datum/map_template/shuttle/hunter/psyker
+ if(HUNTER_PACK_MI13)
+ ship = new/datum/map_template/shuttle/hunter/mi13_foodtruck
var/x = rand(TRANSITIONEDGE,world.maxx - TRANSITIONEDGE - ship.width)
var/y = rand(TRANSITIONEDGE,world.maxy - TRANSITIONEDGE - ship.height)
@@ -152,6 +155,38 @@
header = "Spawn Here!",
)
- priority_announce("Unidentified ship detected near the station.")
+ var/list/announcement_text_list = list()
+ var/announcement_title = ""
+ switch(backstory)
+ if(HUNTER_PACK_COPS)
+ announcement_text_list += "Attention Crew of [GLOB.station_name], this is the Police. A wanted criminal has been reported taking refuge on your station."
+ announcement_text_list += "We have a warrant from the SSC authorities to take them into custody. Officers have been dispatched to your location."
+ announcement_text_list += "We demand your cooperation in bringing this criminal to justice."
+ announcement_title += "Spacepol Command"
+ if(HUNTER_PACK_RUSSIAN)
+ announcement_text_list += "Zdraviya zhelaju, [GLOB.station_name] crew. We are coming to your station."
+ announcement_text_list += "There is a criminal aboard. We will arrest them and return them to the gulag. That's good, yes?"
+ announcement_title += "Russian Freighter"
+ if(HUNTER_PACK_BOUNTY)
+ announcement_text_list += "[GLOB.station_name]. One of our bounty marks has ended up on your station. We will be arriving to collect shortly."
+ announcement_text_list += "Let's make this quick. If you don't want trouble, stay the hell out of our way."
+ announcement_title += "Unregistered Signal"
+ if(HUNTER_PACK_PSYKER)
+ announcement_text_list += "HEY, CAN YOU HEAR US? We're coming to your station. There's a bad guy down there, really bad guy. We need to arrest them."
+ announcement_text_list += "We're also offering fortune telling services out of the front door if you have paying customers."
+ announcement_title += "Fortune-Telling Entertainment Shuttle"
+ if(HUNTER_PACK_MI13)
+ announcement_text_list += "Illegal intrusion detected in the crew monitoring network. Central Command has been informed."
+ announcement_text_list += "Please report any suspicious individuals or behaviour to your local security team."
+ announcement_title += "Nanotrasen Intrusion Countermeasures Electronics"
+ if(!length(announcement_text_list))
+ announcement_text_list += "Unidentified ship detected near the station."
+ stack_trace("Fugitive hunter announcement was unable to generate an announcement text based on backstory: [backstory]")
+
+ if(!length(announcement_title))
+ announcement_title += "Unknown Signal"
+ stack_trace("Fugitive hunter announcement was unable to generate an announcement title based on backstory: [backstory]")
+
+ priority_announce(jointext(announcement_text_list, " "), announcement_title)
#undef TEAM_BACKSTORY_SIZE
diff --git a/code/modules/events/space_vines/vine_mutations.dm b/code/modules/events/space_vines/vine_mutations.dm
index 0571f4d0509f3..c2f8e2d41393c 100644
--- a/code/modules/events/space_vines/vine_mutations.dm
+++ b/code/modules/events/space_vines/vine_mutations.dm
@@ -335,6 +335,7 @@
//This specific mutation only covers floors instead of structures, items, mobs and cant tangle mobs
/datum/spacevine_mutation/timid/on_birth(obj/structure/spacevine/holder)
SET_PLANE_IMPLICIT(holder, FLOOR_PLANE)
+ holder.layer = ABOVE_OPEN_TURF_LAYER
holder.light_state = PASS_LIGHT
holder.can_tangle = FALSE
return ..()
diff --git a/code/modules/events/space_vines/vine_structure.dm b/code/modules/events/space_vines/vine_structure.dm
index 4b3a10d9256cc..a7b9aa2fce516 100644
--- a/code/modules/events/space_vines/vine_structure.dm
+++ b/code/modules/events/space_vines/vine_structure.dm
@@ -161,7 +161,7 @@
if(!istype(stepturf))
return
- if(isspaceturf(stepturf) || isopenspaceturf(stepturf) || !stepturf.Enter(src))
+ if(is_space_or_openspace(stepturf) || !stepturf.Enter(src))
return
if(ischasm(stepturf) && !HAS_TRAIT(stepturf, TRAIT_CHASM_STOPPED))
return
diff --git a/code/modules/events/wizard/embeddies.dm b/code/modules/events/wizard/embeddies.dm
index ea8c5fd176bc1..8b4568942154a 100644
--- a/code/modules/events/wizard/embeddies.dm
+++ b/code/modules/events/wizard/embeddies.dm
@@ -43,9 +43,12 @@ GLOBAL_DATUM(global_funny_embedding, /datum/global_funny_embedding)
* Makes every item in the world embed when thrown, but also hooks into global signals for new items created to also bless them with embed-ability(??).
*/
/datum/global_funny_embedding
- var/embed_type = EMBED_POINTY
+ var/embed_type = /datum/embed_data/global_funny
var/prefix = "error"
+/datum/embed_data/global_funny
+ ignore_throwspeed_threshold = TRUE
+
/datum/global_funny_embedding/New()
. = ..()
//second operation takes MUCH longer, so lets set up signals first.
@@ -61,11 +64,11 @@ GLOBAL_DATUM(global_funny_embedding, /datum/global_funny_embedding)
SIGNAL_HANDLER
// this proc says it's for initializing components, but we're initializing elements too because it's you and me against the world >:)
- if(LAZYLEN(created_item.embedding))
- return //already embeds to some degree, so whatever 🐀
- created_item.embedding = embed_type
+ if(created_item.get_embed())
+ return //already embeds to some degree, so whatever // No rat allowed
+
created_item.name = "[prefix] [created_item.name]"
- created_item.updateEmbedding()
+ created_item.set_embed(embed_type)
/**
* ### handle_current_items
@@ -77,17 +80,20 @@ GLOBAL_DATUM(global_funny_embedding, /datum/global_funny_embedding)
CHECK_TICK
if(!(embed_item.flags_1 & INITIALIZED_1))
continue
- if(!embed_item.embedding)
- embed_item.embedding = embed_type
- embed_item.updateEmbedding()
- embed_item.name = "[prefix] [embed_item.name]"
+ if(embed_item.get_embed())
+ continue
+ embed_item.set_embed(embed_type)
+ embed_item.name = "[prefix] [embed_item.name]"
///everything will be... POINTY!!!!
/datum/global_funny_embedding/pointy
- embed_type = EMBED_POINTY
prefix = "pointy"
///everything will be... sticky? sure, why not
/datum/global_funny_embedding/sticky
- embed_type = EMBED_HARMLESS
+ embed_type = /datum/embed_data/global_funny/sticky
prefix = "sticky"
+
+/datum/embed_data/global_funny/sticky
+ pain_mult = 0
+ jostle_pain_mult = 0
diff --git a/code/modules/experisci/experiment/experiments.dm b/code/modules/experisci/experiment/experiments.dm
index cab4c3a582d62..858c0506fb9a7 100644
--- a/code/modules/experisci/experiment/experiments.dm
+++ b/code/modules/experisci/experiment/experiments.dm
@@ -326,8 +326,8 @@
name = "Материалы для экзокостюмов: стресс-тест"
description = "Ваши устройства для изготовления экзокостюмов позволяют быстро производить их в небольших масштабах, но структурная целостность созданных деталей уступает более традиционным средствам."
exp_tag = "Scan"
+ total_requirement = 2
possible_types = list(/obj/vehicle/sealed/mecha)
- total_requirement = 1
///Damage percent that each mech needs to be at for a scan to work.
var/damage_percent
@@ -337,6 +337,21 @@
possible_types = list(/obj/vehicle/sealed/mecha)
total_requirement = 1
+/// Scan a person with any mutation
+/datum/experiment/scanning/people/mutant
+ name = "Human Field Research: Genetic Mutations"
+ description = "Our new research assistants have been drinking random chemicals for science, when one of them mastered telekinesis and another started shooting lasers from the eyes. This could be useful for our studies. Repeat the experiment by making assistants drink unstable mutagen, scan them and report the results."
+ performance_hint = "Scan a person with a random mutation."
+ required_traits_desc = "random mutation"
+
+/datum/experiment/scanning/people/mutant/is_valid_scan_target(mob/living/carbon/human/check, datum/component/experiment_handler/experiment_handler)
+ . = ..()
+ if (!.)
+ return
+ if(!check.dna.mutations.len)
+ return FALSE
+ return TRUE
+
/// Scan for organs you didn't start the round with
/datum/experiment/scanning/people/novel_organs
name = "Исследование людского поля: дивергентная биология"
@@ -380,7 +395,7 @@
/// Scan for cybernetic organs
/datum/experiment/scanning/people/augmented_organs
name = "Human Field Research: Augmented Organs"
- description = "We need to gather data on how cybernetic vital organs integrate with human biology. Conduct a scan on a human with these implants to help us understand their compatibility"
+ description = "We need to gather data on how cybernetic vital organs integrate with human biology. Conduct a scan on a human with these implants to help us understand their compatibility."
performance_hint = "Perform an organ manipulation surgery to replace one of the vital organs with a cybernetic variant."
required_traits_desc = "augmented vital organs"
required_count = 1
@@ -399,11 +414,8 @@
)
for (var/obj/item/organ/organ as anything in check.organs)
- if (IS_ORGANIC_ORGAN(organ))
- continue
- if (!(organ.slot in vital_organ_slots))
- continue
- return TRUE
+ if ((organ.slot in vital_organ_slots) && IS_ROBOTIC_ORGAN(organ))
+ return TRUE
return FALSE
/// Scan for skillchips
@@ -426,12 +438,55 @@
return FALSE
return TRUE
+/// Scan an android
+/datum/experiment/scanning/people/android
+ name = "Human Field Research: Full Augmentation"
+ description = "Perform a full cybernetic augmentation on a crewmate then scan them to test their newfound capabilities and new sensory and cognitive functions."
+ performance_hint = "Achieve full augmentation by performing a set of surgery operations."
+ required_traits_desc = "fully augmented android"
+ required_count = 1
+
+/datum/experiment/scanning/people/android/is_valid_scan_target(mob/living/carbon/human/check, datum/component/experiment_handler/experiment_handler)
+ . = ..()
+ if (!.)
+ return
+ if (isandroid(check))
+ return TRUE
+ if (check.organs < 6 || check.bodyparts < 6)
+ return FALSE
+
+ var/static/list/augmented_organ_slots = list(
+ ORGAN_SLOT_EYES,
+ ORGAN_SLOT_EARS,
+ ORGAN_SLOT_HEART,
+ ORGAN_SLOT_LUNGS,
+ ORGAN_SLOT_LIVER,
+ ORGAN_SLOT_STOMACH,
+ )
+ for (var/obj/item/organ/organ as anything in check.organs)
+ if (!(organ.slot in augmented_organ_slots))
+ continue
+ if (!IS_ROBOTIC_ORGAN(organ))
+ return FALSE
+ for (var/obj/item/bodypart/bodypart as anything in check.bodyparts)
+ if (bodypart.bodytype != BODYTYPE_ROBOTIC)
+ return FALSE
+ return TRUE
+
/datum/experiment/scanning/reagent/cryostylane
name = "Pure Cryostylane Scan"
description = "It appears that the Cryostylane reagent can potentially halt all physiological processes in the human body. Produce Cryostylane with at least 99% purity and scan the beaker."
+ performance_hint = "Keep the temperature as high as possible during the reaction."
required_reagent = /datum/reagent/cryostylane
min_purity = 0.99
+/datum/experiment/scanning/reagent/haloperidol
+ name = "Pure Haloperidol Scan"
+ description = "We require testing related to the long-term treatment of chronic psychiatric disorders. Produce Haloperidol with at least 98% purity and scan the beaker."
+ performance_hint = "Exothermic and consumes hydrogen during reaction."
+ required_reagent = /datum/reagent/medicine/haloperidol
+ min_purity = 0.98
+
/datum/experiment/scanning/points/bluespace_crystal
name = "Bluespace Crystal Sampling"
description = "Investigate the properties of bluespace crystals by scanning either an artificial or naturally occurring variant. This will help us deepen our understanding of bluespace phenomena."
@@ -441,10 +496,16 @@
/obj/item/stack/sheet/bluespace_crystal = 1
)
+/datum/experiment/scanning/points/anomalies
+ name = "Neutralized Anomaly Analysis"
+ description = "We have the power to deal with the anomalies now. Neutralize them with an anomaly neutralizer or refine the raw cores in the refinery and scan the results."
+ required_points = 4
+ required_atoms = list(/obj/item/assembly/signaler/anomaly = 1)
+
/datum/experiment/scanning/points/machinery_tiered_scan/tier2_any
name = "Upgraded Stock Parts Benchmark"
description = "Our newly-designed machinery components require practical application tests for hints at possible further advancements, as well as a general confirmation that we didn't actually design worse parts somehow. Scan any machinery with Upgraded Parts and report the results."
- required_points = 4
+ required_points = 6
required_atoms = list(
/obj/machinery = 1
)
@@ -453,7 +514,7 @@
/datum/experiment/scanning/points/machinery_tiered_scan/tier3_any
name = "Advanced Stock Parts Benchmark"
description = "Our newly-designed machinery components require practical application tests for hints at possible further advancements, as well as a general confirmation that we didn't actually design worse parts somehow. Scan any machinery with Advanced Parts and report the results."
- required_points = 4
+ required_points = 6
required_atoms = list(
/obj/machinery = 1
)
diff --git a/code/modules/experisci/experiment/types/scanning_fish.dm b/code/modules/experisci/experiment/types/scanning_fish.dm
index 76ead7dde9a93..91f0356cf7352 100644
--- a/code/modules/experisci/experiment/types/scanning_fish.dm
+++ b/code/modules/experisci/experiment/types/scanning_fish.dm
@@ -12,7 +12,7 @@ GLOBAL_LIST_EMPTY(scanned_fish_by_techweb)
performance_hint = "Сканируйте рыбу. Осмотрите сканер, чтобы просмотреть прогресс. Разблокируйте новые порталы для рыбалки."
allowed_experimentors = list(/obj/item/experi_scanner, /obj/machinery/destructive_scanner, /obj/item/fishing_rod/tech, /obj/item/fish_analyzer)
traits = EXPERIMENT_TRAIT_TYPECACHE
- points_reward = list(TECHWEB_POINT_TYPE_GENERIC = 750)
+ points_reward = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_2_POINTS )
required_atoms = list(/obj/item/fish = 4)
scan_message = "Сканируйте разные виды рыб"
///Further experiments added to the techweb when this one is completed.
@@ -80,7 +80,7 @@ GLOBAL_LIST_EMPTY(scanned_fish_by_techweb)
/datum/experiment/scanning/fish/second
name = "Эксперимент по скану рыбы (2)"
description = "Эксперимент, требующий отсканировать больше видов рыб, чтобы разблокировать настройку \"Бездна\" для рыболовного портала."
- points_reward = list(TECHWEB_POINT_TYPE_GENERIC = 1500)
+ points_reward = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_3_POINTS )
required_atoms = list(/obj/item/fish = 8)
next_experiments = list(/datum/experiment/scanning/fish/third)
fish_source_reward = /datum/fish_source/portal/chasm
@@ -88,7 +88,7 @@ GLOBAL_LIST_EMPTY(scanned_fish_by_techweb)
/datum/experiment/scanning/fish/third
name = "Эксперимент по скану рыбы (3)"
description = "Эксперимент, требующий отсканировать еще больше видов рыб, чтобы разблокировать настройку \"Океан\" для рыболовного портала."
- points_reward = list(TECHWEB_POINT_TYPE_GENERIC = 2500)
+ points_reward = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_4_POINTS )
required_atoms = list(/obj/item/fish = 14)
next_experiments = list(/datum/experiment/scanning/fish/fourth, /datum/experiment/scanning/fish/holographic)
fish_source_reward = /datum/fish_source/portal/ocean
@@ -97,7 +97,7 @@ GLOBAL_LIST_EMPTY(scanned_fish_by_techweb)
name = "Эксперимент по скану голорыбы"
description = "Чтобы разблокировать настройку \"Рандомизатор\" для рыболовного портала, потребуется голографическая рыба."
performance_hint = "Загрузите шаблон \"Пляж\" на Голодэке, чтобы половить голорыбу."
- points_reward = list(TECHWEB_POINT_TYPE_GENERIC = 500)
+ points_reward = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_1_POINTS )
required_atoms = list(/obj/item/fish/holo = 4)
scan_message = "Сканирование различных видов голографических рыб"
next_experiments = null
@@ -110,7 +110,7 @@ GLOBAL_LIST_EMPTY(scanned_fish_by_techweb)
/datum/experiment/scanning/fish/fourth
name = "Эксперимент по скану рыбы (4)"
description = "Эксперимент, требующий много видов рыб, чтобы разблокировать настройку \"Гиперпространство\" для рыболовного портала."
- points_reward = list(TECHWEB_POINT_TYPE_GENERIC = 3250)
+ points_reward = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_5_POINTS )
required_atoms = list(/obj/item/fish = 21)
next_experiments = null
fish_source_reward = /datum/fish_source/portal/hyperspace
diff --git a/code/modules/experisci/experiment/types/scanning_reagent.dm b/code/modules/experisci/experiment/types/scanning_reagent.dm
index 1a39e2941efeb..992c28d148d44 100644
--- a/code/modules/experisci/experiment/types/scanning_reagent.dm
+++ b/code/modules/experisci/experiment/types/scanning_reagent.dm
@@ -31,5 +31,5 @@
return TRUE
/datum/experiment/scanning/reagent/serialize_progress_stage(atom/target, list/seen_instances)
- return EXPERIMENT_PROG_INT("Scan a reagent container with [required_reagent::name] of at least [PERCENT(min_purity)] purity.", \
+ return EXPERIMENT_PROG_INT("Scan a reagent container with [required_reagent::name] of at least [PERCENT(min_purity)]% purity.", \
seen_instances.len, required_atoms[target])
diff --git a/code/modules/experisci/experiment/types/scanning_vatgrown.dm b/code/modules/experisci/experiment/types/scanning_vatgrown.dm
index c3bf79333e74d..8c664e6bfbd73 100644
--- a/code/modules/experisci/experiment/types/scanning_vatgrown.dm
+++ b/code/modules/experisci/experiment/types/scanning_vatgrown.dm
@@ -1,14 +1,17 @@
-/datum/experiment/scanning/random/cytology
+/datum/experiment/scanning/cytology
name = "Эксперемент по скану цитологии"
- description = "Базовый эксперимент для сканирования атомов, выращенных в чашке петри."
exp_tag = "Скан цитологии"
- total_requirement = 1
- possible_types = list(/mob/living/basic/slime)
- traits = EXPERIMENT_TRAIT_DESTRUCTIVE
-/datum/experiment/scanning/random/cytology/final_contributing_index_checks(datum/component/experiment_handler/experiment_handler, atom/target, typepath)
+/datum/experiment/scanning/cytology/final_contributing_index_checks(datum/component/experiment_handler/experiment_handler, atom/target, typepath)
return ..() && HAS_TRAIT(target, TRAIT_VATGROWN)
-/datum/experiment/scanning/random/cytology/serialize_progress_stage(atom/target, list/seen_instances)
- return EXPERIMENT_PROG_INT("Скан образцов [initial(target.name)], выращенных в лаборатории", \
- traits & EXPERIMENT_TRAIT_DESTRUCTIVE ? scanned[target] : seen_instances.len, required_atoms[target])
+/datum/experiment/scanning/cytology/serialize_progress_stage(atom/target, list/seen_instances)
+ return EXPERIMENT_PROG_INT("Скан образцов [initial(target.name)], выращенных в лаборатории", seen_instances.len, required_atoms[target])
+
+/datum/experiment/scanning/cytology/slime
+ name = "Сканирование слаймов выращенных в лаборатории"
+ description = "Видели слаймов в ксенобиологическом загоне? Они появились, когда наши исследователи бросили в резервуар заплесневелый кусок хлеба. Вырастите еще одного и сообщите о результатах."
+ performance_hint = "Соберите клеточные линии слаймов из заплесневелого хлеба или возьмите биопсийный образец существующих слаймов. И выращивайте их в загоне."
+ required_atoms = list(/mob/living/basic/slime = 1)
+
+
diff --git a/code/modules/fishing/aquarium/aquarium.dm b/code/modules/fishing/aquarium/aquarium.dm
index 0a836eadafdda..cd05d0bd34baf 100644
--- a/code/modules/fishing/aquarium/aquarium.dm
+++ b/code/modules/fishing/aquarium/aquarium.dm
@@ -53,6 +53,9 @@
/// /obj/item/fish in the aquarium, sorted by type - does not include things with aquarium visuals that are not fish
var/list/tracked_fish_by_type
+ /// Var used to keep track of the current beauty of the aquarium, which can be throughfully changed by aquarium content.
+ var/current_beauty = 150
+
/obj/structure/aquarium/Initialize(mapload)
. = ..()
update_appearance()
@@ -62,6 +65,8 @@
create_reagents(6, SEALED_CONTAINER)
RegisterSignal(reagents, COMSIG_REAGENTS_NEW_REAGENT, PROC_REF(start_autofeed))
AddComponent(/datum/component/plumbing/aquarium)
+ if(current_beauty)
+ AddElement(/datum/element/beauty, current_beauty)
ADD_KEEP_TOGETHER(src, INNATE_TRAIT)
/obj/structure/aquarium/proc/track_if_fish(atom/source, atom/initialized)
@@ -192,9 +197,9 @@
var/obj/item/stack/sheet/glass/glass = item
if(istype(glass))
if(glass.get_amount() < 2)
- to_chat(user, span_warning("You need two glass sheets to fix the case!"))
+ balloon_alert(user, "it needs two sheets!")
return
- to_chat(user, span_notice("You start fixing [src]..."))
+ balloon_alert(user, "fixing the aquarium...")
if(do_after(user, 2 SECONDS, target = src))
glass.use(2)
broken = FALSE
@@ -202,10 +207,18 @@
update_appearance()
return TRUE
else
- var/datum/component/aquarium_content/content_component = item.GetComponent(/datum/component/aquarium_content)
- if(content_component && content_component.is_ready_to_insert(src) && user.transferItemToLoc(item, src))
- update_appearance()
- return TRUE
+ var/insert_attempt = SEND_SIGNAL(item, COMSIG_TRY_INSERTING_IN_AQUARIUM, src)
+ switch(insert_attempt)
+ if(COMSIG_CAN_INSERT_IN_AQUARIUM)
+ if(!user.transferItemToLoc(item, src))
+ user.balloon_alert(user, "stuck to your hand!")
+ return TRUE
+ balloon_alert(user, "added to aquarium")
+ update_appearance()
+ return TRUE
+ if(COMSIG_CANNOT_INSERT_IN_AQUARIUM)
+ balloon_alert(user, "cannot add to aquarium!")
+ return TRUE
if(istype(item, /obj/item/fish_feed) && !panel_open)
if(!item.reagents.total_volume)
@@ -270,24 +283,27 @@
///Apply mood bonus depending on aquarium status
/obj/structure/aquarium/proc/admire(mob/living/user)
- to_chat(user,span_notice("You take a moment to watch [src]."))
- if(do_after(user, 5 SECONDS, target = src))
- var/alive_fish = 0
- var/dead_fish = 0
- var/list/tracked_fish = get_fishes()
- for(var/obj/item/fish/fish in tracked_fish)
- if(fish.status == FISH_ALIVE)
- alive_fish++
- else
- dead_fish++
- //Check if there are live fish - good mood
- //All fish dead - bad mood.
- //No fish - nothing.
- if(alive_fish > 0)
- user.add_mood_event("aquarium", /datum/mood_event/aquarium_positive)
- else if(dead_fish > 0)
- user.add_mood_event("aquarium", /datum/mood_event/aquarium_negative)
- // Could maybe scale power of this mood with number/types of fish
+ user.balloon_alert(user, "admiring aquarium...")
+ if(!do_after(user, 5 SECONDS, target = src))
+ return
+ var/alive_fish = 0
+ var/dead_fish = 0
+ var/list/tracked_fish = get_fishes()
+ for(var/obj/item/fish/fish in tracked_fish)
+ if(fish.status == FISH_ALIVE)
+ alive_fish++
+ else
+ dead_fish++
+
+ var/morb = HAS_TRAIT(user, TRAIT_MORBID)
+ //Check if there are live fish - good mood
+ //All fish dead - bad mood.
+ //No fish - nothing.
+ if(alive_fish > 0)
+ user.add_mood_event("aquarium", morb ? /datum/mood_event/morbid_aquarium_bad : /datum/mood_event/aquarium_positive)
+ else if(dead_fish > 0)
+ user.add_mood_event("aquarium", morb ? /datum/mood_event/morbid_aquarium_good : /datum/mood_event/aquarium_negative)
+ // Could maybe scale power of this mood with number/types of fish
/obj/structure/aquarium/ui_data(mob/user)
. = ..()
diff --git a/code/modules/fishing/aquarium/aquarium_kit.dm b/code/modules/fishing/aquarium/aquarium_kit.dm
index 30c9300323126..1161648f7d15f 100644
--- a/code/modules/fishing/aquarium/aquarium_kit.dm
+++ b/code/modules/fishing/aquarium/aquarium_kit.dm
@@ -100,7 +100,8 @@
/obj/item/aquarium_kit/Initialize(mapload)
. = ..()
- AddComponent(/datum/component/slapcrafting, /datum/crafting_recipe/aquarium)
+ var/static/list/recipes = list(/datum/crafting_recipe/aquarium)
+ AddElement(/datum/element/slapcrafting, recipes)
/obj/item/aquarium_prop
name = "generic aquarium prop"
@@ -109,10 +110,11 @@
w_class = WEIGHT_CLASS_TINY
var/layer_mode = AQUARIUM_LAYER_MODE_BOTTOM
+ var/beauty = 150
/obj/item/aquarium_prop/Initialize(mapload)
. = ..()
- AddComponent(/datum/component/aquarium_content, icon)
+ AddComponent(/datum/component/aquarium_content, icon, beauty = beauty)
/obj/item/aquarium_prop/rocks
name = "rocks"
diff --git a/code/modules/fishing/aquarium/aquarium_upgrades.dm b/code/modules/fishing/aquarium/aquarium_upgrades.dm
index 140777d34f0ae..c73e6e9d230ae 100644
--- a/code/modules/fishing/aquarium/aquarium_upgrades.dm
+++ b/code/modules/fishing/aquarium/aquarium_upgrades.dm
@@ -24,6 +24,8 @@
icon_state = "bioelec_map"
icon_prefix = "bioelec"
+ current_beauty = 0
+
/obj/structure/aquarium/bioelec_gen/zap_act(power, zap_flags)
var/explosive = zap_flags & ZAP_MACHINE_EXPLOSIVE
if(!explosive)
diff --git a/code/modules/fishing/fish/_fish.dm b/code/modules/fishing/fish/_fish.dm
index a12ae7e7397a9..c4be35817b7ac 100644
--- a/code/modules/fishing/fish/_fish.dm
+++ b/code/modules/fishing/fish/_fish.dm
@@ -143,9 +143,12 @@
/// power of the tesla zap created by the fish in a bioelectric generator
var/electrogenesis_power = 10 MEGA JOULES
+ /// The beauty this fish provides to the aquarium it's inserted in.
+ var/beauty = FISH_BEAUTY_GENERIC
+
/obj/item/fish/Initialize(mapload, apply_qualities = TRUE)
. = ..()
- AddComponent(/datum/component/aquarium_content, icon, PROC_REF(get_aquarium_animation), list(COMSIG_FISH_STATUS_CHANGED,COMSIG_FISH_STIRRED))
+ AddComponent(/datum/component/aquarium_content, icon, PROC_REF(get_aquarium_animation), list(COMSIG_FISH_STIRRED), beauty)
RegisterSignal(src, COMSIG_ATOM_ON_LAZARUS_INJECTOR, PROC_REF(use_lazarus))
if(do_flop_animation)
diff --git a/code/modules/fishing/fish/fish_types.dm b/code/modules/fishing/fish/fish_types.dm
index f61cb43b22424..cc001560ee0a9 100644
--- a/code/modules/fishing/fish/fish_types.dm
+++ b/code/modules/fishing/fish/fish_types.dm
@@ -19,6 +19,8 @@
desc = "A great rubber duck tool for Lawyers who can't get a grasp over their case."
stable_population = 1
random_case_rarity = FISH_RARITY_NOPE
+ show_in_catalog = FALSE
+ beauty = FISH_BEAUTY_GOOD
/obj/item/fish/angelfish
name = "angelfish"
@@ -77,6 +79,7 @@
)
required_temperature_min = MIN_AQUARIUM_TEMP+12
required_temperature_max = MIN_AQUARIUM_TEMP+30
+ beauty = FISH_BEAUTY_GOOD
// Saltwater fish below
@@ -107,6 +110,7 @@
evolution_types = null
compatible_types = list(/obj/item/fish/clownfish)
food = /datum/reagent/lube
+ beauty = FISH_BEAUTY_GREAT
/obj/item/fish/cardinal
name = "cardinalfish"
@@ -163,8 +167,9 @@
stable_population = 3
required_temperature_min = MIN_AQUARIUM_TEMP+23
required_temperature_max = MIN_AQUARIUM_TEMP+28
-
fish_traits = list(/datum/fish_trait/heavy, /datum/fish_trait/toxic)
+ beauty = FISH_BEAUTY_GOOD
+
/obj/item/fish/lanternfish
name = "lanternfish"
@@ -182,6 +187,7 @@
fish_traits = list(/datum/fish_trait/nocturnal)
required_temperature_min = MIN_AQUARIUM_TEMP+2 //My source is that the water at a depth 6600 feet is pretty darn cold.
required_temperature_max = MIN_AQUARIUM_TEMP+18
+ beauty = FISH_BEAUTY_NULL
//Tiziran Fish
/obj/item/fish/dwarf_moonfish
@@ -195,6 +201,7 @@
average_weight = 2000
required_temperature_min = MIN_AQUARIUM_TEMP+20
required_temperature_max = MIN_AQUARIUM_TEMP+30
+ beauty = FISH_BEAUTY_GOOD
/obj/item/fish/gunner_jellyfish
name = "gunner jellyfish"
@@ -205,6 +212,7 @@
fillet_type = /obj/item/food/fishmeat/gunner_jellyfish
required_temperature_min = MIN_AQUARIUM_TEMP+24
required_temperature_max = MIN_AQUARIUM_TEMP+32
+ beauty = FISH_BEAUTY_GOOD
/obj/item/fish/needlefish
name = "needlefish"
@@ -261,6 +269,7 @@
)
evolution_types = list(/datum/fish_evolution/ice_chrab)
compatible_types = list(/obj/item/fish/chasm_crab/ice)
+ beauty = FISH_BEAUTY_GOOD
/obj/item/fish/chasm_crab/ice
name = "arctic chrab"
@@ -271,13 +280,7 @@
required_temperature_max = MIN_AQUARIUM_TEMP+15
evolution_types = list(/datum/fish_evolution/chasm_chrab)
compatible_types = list(/obj/item/fish/chasm_crab)
-
-/obj/item/storage/box/fish_debug
- name = "box full of fish"
-
-/obj/item/storage/box/fish_debug/PopulateContents()
- for(var/fish_type in subtypesof(/obj/item/fish))
- new fish_type(src)
+ beauty = FISH_BEAUTY_GREAT
/obj/item/fish/donkfish
name = "donk co. company patent donkfish"
@@ -290,6 +293,7 @@
fish_traits = list(/datum/fish_trait/yucky)
required_temperature_min = MIN_AQUARIUM_TEMP+15
required_temperature_max = MIN_AQUARIUM_TEMP+28
+ beauty = FISH_BEAUTY_EXCELLENT
/obj/item/fish/emulsijack
name = "toxic emulsijack"
@@ -301,6 +305,7 @@
fish_traits = list(/datum/fish_trait/emulsijack)
required_temperature_min = MIN_AQUARIUM_TEMP+5
required_temperature_max = MIN_AQUARIUM_TEMP+40
+ beauty = FISH_BEAUTY_BAD
/obj/item/fish/jumpercable
name = "monocloning jumpercable"
@@ -322,6 +327,7 @@
/datum/fish_trait/mixotroph,
/datum/fish_trait/electrogenesis,
)
+ beauty = FISH_BEAUTY_UGLY
/obj/item/fish/ratfish
name = "ratfish"
@@ -341,6 +347,7 @@
"Value" = DAIRY
)
)
+ beauty = FISH_BEAUTY_DISGUSTING
/obj/item/fish/ratfish/Initialize(mapload)
. = ..()
@@ -364,6 +371,7 @@
required_temperature_min = MIN_AQUARIUM_TEMP+10
required_temperature_max = MIN_AQUARIUM_TEMP+40
evolution_types = list(/datum/fish_evolution/purple_sludgefish)
+ beauty = FISH_BEAUTY_NULL
/obj/item/fish/sludgefish/purple
name = "purple sludgefish"
@@ -401,6 +409,7 @@
),
)
required_temperature_min = MIN_AQUARIUM_TEMP+20
+ beauty = FISH_BEAUTY_GREAT
/obj/item/fish/boned
name = "unmarine bonemass"
@@ -423,6 +432,7 @@
average_weight = 2000
death_text = "%SRC stops moving." //It's dead... or is it?
evolution_types = list(/datum/fish_evolution/mastodon)
+ beauty = FISH_BEAUTY_UGLY
/obj/item/fish/mastodon
name = "unmarine mastodon"
@@ -451,6 +461,7 @@
average_weight = 5000
death_text = "%SRC stops moving."
fish_traits = list(/datum/fish_trait/heavy, /datum/fish_trait/amphibious, /datum/fish_trait/revival, /datum/fish_trait/carnivore, /datum/fish_trait/predator, /datum/fish_trait/aggressive)
+ beauty = FISH_BEAUTY_BAD
/obj/item/fish/holo
name = "holographic goldfish"
@@ -501,6 +512,7 @@
sprite_height = 8
average_size = 60
average_weight = 1000
+ beauty = FISH_BEAUTY_GOOD
/obj/item/fish/holo/angel
name = "holographic angelfish"
@@ -524,6 +536,7 @@
icon_state = "checkered" //it's a meta joke, buddy.
dedicated_in_aquarium_icon_state = "checkered_small"
sprite_width = 4
+ beauty = FISH_BEAUTY_NULL
/obj/item/fish/holo/halffish
name = "holographic half-fish"
@@ -533,6 +546,7 @@
sprite_height = 4
sprite_width = 10
average_size = 50
+ beauty = FISH_BEAUTY_UGLY
/obj/item/fish/starfish
name = "cosmostarfish"
@@ -554,6 +568,7 @@
grind_results = list(/datum/reagent/bluespace = 10, /datum/reagent/consumable/liquidgibs = 5)
fillet_type = null
fish_traits = list(/datum/fish_trait/antigrav, /datum/fish_trait/mixotroph)
+ beauty = FISH_BEAUTY_GREAT
/obj/item/fish/starfish/Initialize(mapload)
. = ..()
@@ -589,6 +604,7 @@
)
hitsound = null
throwforce = 5
+ beauty = FISH_BEAUTY_GOOD
///maximum bonus damage when winded up
var/maximum_bonus = 25
@@ -662,3 +678,4 @@
)
//anxiety naturally limits the amount of zipzaps per tank, so they are stronger alone
electrogenesis_power = 20 MEGA JOULES
+ beauty = FISH_BEAUTY_GOOD
diff --git a/code/modules/fishing/fish_catalog.dm b/code/modules/fishing/fish_catalog.dm
index 3e1cb6cfcfa6b..49a84413ded06 100644
--- a/code/modules/fishing/fish_catalog.dm
+++ b/code/modules/fishing/fish_catalog.dm
@@ -37,6 +37,25 @@
else
fish_data["feed"] = "[AQUARIUM_COMPANY] Fish Feed"
fish_data["fishing_tips"] = build_fishing_tips(fish)
+ var/beauty_score = initial(fish.beauty)
+ switch(beauty_score)
+ if(-INFINITY to FISH_BEAUTY_DISGUSTING)
+ beauty_score = "OH HELL NAW!"
+ if(FISH_BEAUTY_DISGUSTING to FISH_BEAUTY_UGLY)
+ beauty_score = "☆☆☆☆☆"
+ if(FISH_BEAUTY_UGLY to FISH_BEAUTY_BAD)
+ beauty_score = "★☆☆☆☆"
+ if(FISH_BEAUTY_BAD to FISH_BEAUTY_NULL)
+ beauty_score = "★★☆☆☆"
+ if(FISH_BEAUTY_NULL to FISH_BEAUTY_GENERIC)
+ beauty_score = "★★★☆☆"
+ if(FISH_BEAUTY_GENERIC to FISH_BEAUTY_GOOD)
+ beauty_score = "★★★★☆"
+ if(FISH_BEAUTY_GOOD to FISH_BEAUTY_GREAT)
+ beauty_score = "★★★★★"
+ if(FISH_BEAUTY_GREAT to INFINITY)
+ beauty_score = "★★★★★★"
+ fish_data["beauty"] = beauty_score
fish_info += list(fish_data)
// TODO: Custom entries for unusual stuff
diff --git a/code/modules/fishing/fishing_equipment.dm b/code/modules/fishing/fishing_equipment.dm
index df6c4eee9c254..f6b49a9b52314 100644
--- a/code/modules/fishing/fishing_equipment.dm
+++ b/code/modules/fishing/fishing_equipment.dm
@@ -304,5 +304,12 @@
new /obj/item/fishing_line/reinforced(src)
new /obj/item/fishing_line/cloaked(src)
+/obj/item/storage/box/fish_debug
+ name = "box full of fish"
+
+/obj/item/storage/box/fish_debug/PopulateContents()
+ for(var/fish_type in subtypesof(/obj/item/fish))
+ new fish_type(src)
+
#undef MAGNET_HOOK_BONUS_MULTIPLIER
#undef RESCUE_HOOK_FISH_MULTIPLIER
diff --git a/code/modules/fishing/sources/_fish_source.dm b/code/modules/fishing/sources/_fish_source.dm
index 66d2bf0a880e3..3c94ff8277d94 100644
--- a/code/modules/fishing/sources/_fish_source.dm
+++ b/code/modules/fishing/sources/_fish_source.dm
@@ -239,6 +239,21 @@ GLOBAL_LIST(fishing_property_cache)
/// Builds a fish weights table modified by bait/rod/user properties
/datum/fish_source/proc/get_modified_fish_table(obj/item/fishing_rod/rod, mob/fisherman)
var/obj/item/bait = rod.bait
+ ///An exponent used to level out the difference in probabilities between fishes/mobs on the table depending on bait quality.
+ var/leveling_exponent = 0
+ ///Multiplier used to make fishes more common compared to everything else.
+ var/result_multiplier = 1
+
+ if(bait)
+ if(HAS_TRAIT(bait, TRAIT_GREAT_QUALITY_BAIT))
+ result_multiplier = 9
+ leveling_exponent = 0.5
+ else if(HAS_TRAIT(bait, TRAIT_GOOD_QUALITY_BAIT))
+ result_multiplier = 3.5
+ leveling_exponent = 0.25
+ else if(HAS_TRAIT(bait, TRAIT_BASIC_QUALITY_BAIT))
+ result_multiplier = 2
+ leveling_exponent = 0.1
var/list/fish_list_properties = collect_fish_properties()
@@ -246,17 +261,13 @@ GLOBAL_LIST(fishing_property_cache)
for(var/result in final_table)
final_table[result] *= rod.hook?.get_hook_bonus_multiplicative(result)
final_table[result] += rod.hook?.get_hook_bonus_additive(result)//Decide on order here so it can be multiplicative
+
if(ispath(result, /obj/item/fish))
//Modify fish roll chance
var/obj/item/fish/caught_fish = result
if(bait)
- if(HAS_TRAIT(bait, TRAIT_GREAT_QUALITY_BAIT))
- final_table[result] *= 10
- else if(HAS_TRAIT(bait, TRAIT_GOOD_QUALITY_BAIT))
- final_table[result] = round(final_table[result] * 3.5, 1)
- else if(HAS_TRAIT(bait, TRAIT_BASIC_QUALITY_BAIT))
- final_table[result] *= 2
+ final_table[result] = round(final_table[result] * result_multiplier, 1)
if(!HAS_TRAIT(bait, TRAIT_OMNI_BAIT))
//Bait matching likes doubles the chance
var/list/fav_bait = fish_list_properties[result][NAMEOF(caught_fish, favorite_bait)]
@@ -286,4 +297,22 @@ GLOBAL_LIST(fishing_property_cache)
if(final_table[result] <= 0)
final_table -= result
+
+ ///here we even out the chances of fishie based on bait quality: better baits lead rarer fishes being more common.
+ if(leveling_exponent)
+ var/highest_fish_weight
+ var/list/collected_fish_weights = list()
+ for(var/fishable in final_table)
+ if(ispath(fishable, /obj/item/fish))
+ var/fish_weight = fish_table[fishable]
+ collected_fish_weights[fishable] = fish_weight
+ if(fish_weight > highest_fish_weight)
+ highest_fish_weight = fish_weight
+
+ for(var/fish in collected_fish_weights)
+ var/difference = collected_fish_weights[fish] - highest_fish_weight
+ if(!difference)
+ continue
+ final_table[fish] += round(difference**leveling_exponent, 1)
+
return final_table
diff --git a/code/modules/food_and_drinks/machinery/icecream_vat.dm b/code/modules/food_and_drinks/machinery/icecream_vat.dm
index eba5ff63f3f8c..8b0c8eb14a5ca 100644
--- a/code/modules/food_and_drinks/machinery/icecream_vat.dm
+++ b/code/modules/food_and_drinks/machinery/icecream_vat.dm
@@ -95,6 +95,8 @@
context[SCREENTIP_CONTEXT_RMB] = "Transfer beaker reagents"
else if(istype(held_item, /obj/item/food/icecream))
context[SCREENTIP_CONTEXT_LMB] = "Take scoop of [selected_flavour] ice cream"
+ else if(istype(held_item, /obj/item/kitchen/spoon) || istype(held_item, /obj/item/kitchen/spoon/soup_ladle))
+ context[SCREENTIP_CONTEXT_RMB] = "Spill reagent"
return CONTEXTUAL_SCREENTIP_SET
switch(vat_mode)
@@ -106,10 +108,20 @@
context[SCREENTIP_CONTEXT_RMB] = "Change mode to flavors"
return CONTEXTUAL_SCREENTIP_SET
-/obj/machinery/icecream_vat/attackby(obj/item/reagent_containers/beaker, mob/user, params)
+/obj/machinery/icecream_vat/examine(mob/user)
+ . = ..()
+ . += "You can use a [EXAMINE_HINT("spoon")] or [EXAMINE_HINT("soup ladle")] to spill reagents."
+
+/obj/machinery/icecream_vat/attackby(obj/item/weapon, mob/user, params)
. = ..()
if(.)
return
+
+ if(istype(weapon, /obj/item/kitchen/spoon) || istype(weapon, /obj/item/kitchen/spoon/soup_ladle))
+ spill_reagents(user)
+ return TRUE
+
+ var/obj/item/reagent_containers/beaker = weapon
if(!istype(beaker) || !beaker.reagents || (beaker.item_flags & ABSTRACT) || !beaker.is_open_container())
return
@@ -204,6 +216,14 @@
if(cone)
make_cone(user, choice, cone.ingredients)
+///Lets the user select a reagent in the vat to spill out.
+/obj/machinery/icecream_vat/proc/spill_reagents(mob/living/user)
+ var/datum/reagent/reagent_to_remove = tgui_input_list(user, "Select a reagent to purge from the vat.", "Remove reagent", reagents.reagent_list, ui_state = GLOB.conscious_state)
+ if(isnull(reagent_to_remove) || !user.can_perform_action(src, action_bitflags = ALLOW_RESTING))
+ return
+ balloon_alert(user, "spilled [reagent_to_remove.name]")
+ reagents.remove_reagent(reagent_to_remove.type, reagent_to_remove.volume)
+
/obj/machinery/icecream_vat/proc/make_ice_cream_color(datum/ice_cream_flavour/flavor)
if(!flavor.color)
return
diff --git a/code/modules/food_and_drinks/machinery/microwave.dm b/code/modules/food_and_drinks/machinery/microwave.dm
index 88256eaf1d27e..bc5adfbd0f951 100644
--- a/code/modules/food_and_drinks/machinery/microwave.dm
+++ b/code/modules/food_and_drinks/machinery/microwave.dm
@@ -114,6 +114,7 @@
QDEL_LIST(ingredients)
QDEL_NULL(wires)
QDEL_NULL(soundloop)
+ QDEL_NULL(particles)
if(!isnull(cell))
QDEL_NULL(cell)
return ..()
@@ -366,8 +367,10 @@
return ITEM_INTERACT_SUCCESS
/obj/machinery/microwave/tool_act(mob/living/user, obj/item/tool, list/modifiers)
+ if(!tool.tool_behaviour)
+ return ..()
if(operating)
- return ITEM_INTERACT_SKIP_TO_ATTACK // Don't use tools if we're dirty
+ return ITEM_INTERACT_SKIP_TO_ATTACK // Don't use tools if we're operating
if(dirty >= MAX_MICROWAVE_DIRTINESS)
return ITEM_INTERACT_SKIP_TO_ATTACK // Don't insert items if we're dirty
if(panel_open && is_wire_tool(tool))
@@ -375,15 +378,13 @@
return ITEM_INTERACT_SUCCESS
return ..()
-/obj/machinery/microwave/attackby(obj/item/item, mob/living/user, params)
+/obj/machinery/microwave/item_interaction(mob/living/user, obj/item/item, list/modifiers)
if(operating)
- return
+ return NONE
if(broken > NOT_BROKEN)
- if(IS_EDIBLE(item))
- balloon_alert(user, "it's broken!")
- return TRUE
- return ..()
+ balloon_alert(user, "it's broken!")
+ return ITEM_INTERACT_BLOCKING
if(istype(item, /obj/item/stock_parts/power_store/cell) && cell_powered)
var/swapped = FALSE
@@ -395,27 +396,23 @@
swapped = TRUE
if(!user.transferItemToLoc(item, src))
update_appearance()
- return TRUE
+ return ITEM_INTERACT_BLOCKING
cell = item
balloon_alert(user, "[swapped ? "swapped" : "inserted"] cell")
update_appearance()
- return TRUE
+ return ITEM_INTERACT_SUCCESS
if(!anchored)
- if(IS_EDIBLE(item))
- balloon_alert(user, "not secured!")
- return TRUE
- return ..()
+ balloon_alert(user, "not secured!")
+ return ITEM_INTERACT_BLOCKING
if(dirty >= MAX_MICROWAVE_DIRTINESS) // The microwave is all dirty so can't be used!
- if(IS_EDIBLE(item))
- balloon_alert(user, "it's too dirty!")
- return TRUE
- return ..()
+ balloon_alert(user, "it's too dirty!")
+ return ITEM_INTERACT_BLOCKING
if(vampire_charging_capable && istype(item, /obj/item/modular_computer) && ingredients.len > 0)
balloon_alert(user, "max 1 device!")
- return FALSE
+ return ITEM_INTERACT_BLOCKING
if(istype(item, /obj/item/storage))
var/obj/item/storage/tray = item
@@ -425,14 +422,14 @@
// Non-tray dumping requires a do_after
to_chat(user, span_notice("You start dumping out the contents of [item] into [src]..."))
if(!do_after(user, 2 SECONDS, target = tray))
- return
+ return ITEM_INTERACT_BLOCKING
for(var/obj/tray_item in tray.contents)
if(!IS_EDIBLE(tray_item))
continue
if(ingredients.len >= max_n_of_items)
balloon_alert(user, "it's full!")
- return TRUE
+ return ITEM_INTERACT_BLOCKING
if(tray.atom_storage.attempt_remove(tray_item, src))
loaded++
ingredients += tray_item
@@ -440,23 +437,21 @@
open(autoclose = 0.6 SECONDS)
to_chat(user, span_notice("You insert [loaded] items into \the [src]."))
update_appearance()
- return
+ return ITEM_INTERACT_SUCCESS
- if(item.w_class <= WEIGHT_CLASS_NORMAL && !istype(item, /obj/item/storage) && !user.combat_mode)
+ if(item.w_class <= WEIGHT_CLASS_NORMAL && !user.combat_mode)
if(ingredients.len >= max_n_of_items)
balloon_alert(user, "it's full!")
- return TRUE
+ return ITEM_INTERACT_BLOCKING
if(!user.transferItemToLoc(item, src))
balloon_alert(user, "it's stuck to your hand!")
- return FALSE
+ return ITEM_INTERACT_BLOCKING
ingredients += item
open(autoclose = 0.6 SECONDS)
user.visible_message(span_notice("[user] adds \a [item] to \the [src]."), span_notice("You add [item] to \the [src]."))
update_appearance()
- return
-
- return ..()
+ return ITEM_INTERACT_SUCCESS
/obj/machinery/microwave/attack_hand_secondary(mob/user, list/modifiers)
if(user.can_perform_action(src, ALLOW_SILICON_REACH))
@@ -541,8 +536,9 @@
/obj/machinery/microwave/proc/eject()
var/atom/drop_loc = drop_location()
- for(var/atom/movable/movable_ingredient as anything in ingredients)
- movable_ingredient.forceMove(drop_loc)
+ for(var/obj/item/item_ingredient as anything in ingredients)
+ item_ingredient.forceMove(drop_loc)
+ item_ingredient.dropped() //Mob holders can be on the ground if we don't do this
open(autoclose = 1.4 SECONDS)
/obj/machinery/microwave/proc/start_cycle(mob/user)
@@ -674,10 +670,34 @@
if(MICROWAVE_PRE)
pre_success(cooker)
return
+
+ if(cycles == 1) //Only needs to try to shock mobs once, towards the end of the loop
+ var/successful_shock
+ var/list/microwave_contents = list()
+ microwave_contents += get_all_contents() //Mobs are often hid inside of mob holders, which could be fried and made into a burger...
+ for(var/mob/living/victim in microwave_contents)
+ if(victim.electrocute_act(shock_damage = 100, source = src, siemens_coeff = 1, flags = SHOCK_NOGLOVES))
+ successful_shock = TRUE
+ if(victim.stat == DEAD) //This is mostly so humans that can_be_held don't get gibbed from one microwave run alone, but mice become burnt messes
+ victim.gib()
+ muck()
+ if(successful_shock) //We only want to give feedback once, regardless of how many mobs got shocked
+ var/list/cant_smell = list()
+ for(var/mob/smeller in get_hearers_in_view(DEFAULT_MESSAGE_RANGE, src))
+ if(HAS_TRAIT(smeller, TRAIT_ANOSMIA))
+ cant_smell += smeller
+ visible_message(span_danger("You smell a burnt smell coming from [src]!"), ignored_mobs = cant_smell)
+ particles = new /particles/smoke()
+ addtimer(CALLBACK(src, PROC_REF(remove_smoke)), 10 SECONDS)
+ Shake(duration = 1 SECONDS)
+
cycles--
use_energy(active_power_usage)
addtimer(CALLBACK(src, PROC_REF(cook_loop), type, cycles, wait, cooker), wait)
+/obj/machinery/microwave/proc/remove_smoke()
+ QDEL_NULL(particles)
+
/obj/machinery/microwave/power_change()
. = ..()
if(cell_powered)
diff --git a/code/modules/food_and_drinks/machinery/smartfridge.dm b/code/modules/food_and_drinks/machinery/smartfridge.dm
index 392d716965d6a..114d7d020f6c9 100644
--- a/code/modules/food_and_drinks/machinery/smartfridge.dm
+++ b/code/modules/food_and_drinks/machinery/smartfridge.dm
@@ -647,7 +647,7 @@
return istype(weapon, /obj/item/petri_dish)
/obj/machinery/smartfridge/petri/preloaded
- initial_contents = list(/obj/item/petri_dish = 5)
+ initial_contents = list(/obj/item/petri_dish/random = 3)
// -------------------------
// Organ Surgery Smartfridge
diff --git a/code/modules/food_and_drinks/recipes/tablecraft/recipes_sandwich.dm b/code/modules/food_and_drinks/recipes/tablecraft/recipes_sandwich.dm
index 7761a57fcfdbd..742e8c1f3c89b 100644
--- a/code/modules/food_and_drinks/recipes/tablecraft/recipes_sandwich.dm
+++ b/code/modules/food_and_drinks/recipes/tablecraft/recipes_sandwich.dm
@@ -10,6 +10,7 @@
name = "Sandwich"
reqs = list(
/obj/item/food/breadslice/plain = 2,
+ /obj/item/food/grown/cabbage = 1,
/obj/item/food/meat/steak = 1,
/obj/item/food/cheese/wedge = 1
)
diff --git a/code/modules/hallucination/body.dm b/code/modules/hallucination/body.dm
index 2d017f969679d..8cd34cfcd4071 100644
--- a/code/modules/hallucination/body.dm
+++ b/code/modules/hallucination/body.dm
@@ -10,7 +10,7 @@
/// Whether we apply the floating anim to the body
var/body_floats = FALSE
/// The layer this body will be drawn on, in case we want to bypass lighting
- var/body_layer = TURF_LAYER
+ var/body_layer = LOW_FLOOR_LAYER
/// if TRUE, spawns the body under the hallucinator instead of somewhere in view
var/spawn_under_hallucinator = FALSE
diff --git a/code/modules/hallucination/bubblegum_attack.dm b/code/modules/hallucination/bubblegum_attack.dm
index 5ee39ed8e8649..529d67dcd3551 100644
--- a/code/modules/hallucination/bubblegum_attack.dm
+++ b/code/modules/hallucination/bubblegum_attack.dm
@@ -36,7 +36,7 @@
if(hallucinator.client)
- fake_broken_wall = image('icons/turf/floors.dmi', wall_source, "plating", layer = TURF_LAYER)
+ fake_broken_wall = image('icons/turf/floors.dmi', wall_source, "plating", layer = LOW_FLOOR_LAYER)
SET_PLANE_EXPLICIT(fake_broken_wall, FLOOR_PLANE, wall_source)
fake_broken_wall.override = TRUE
fake_rune = image('icons/effects/96x96.dmi', target_landing_image_turf, "landing", layer = ABOVE_OPEN_TURF_LAYER)
diff --git a/code/modules/hallucination/hazard.dm b/code/modules/hallucination/hazard.dm
index 1dfdfd23970e7..34bcee62f6a87 100644
--- a/code/modules/hallucination/hazard.dm
+++ b/code/modules/hallucination/hazard.dm
@@ -29,7 +29,8 @@
/// These hallucination effects cause side effects when the hallucinator walks into them.
/obj/effect/client_image_holder/hallucination/danger
- image_layer = TURF_LAYER
+ image_layer = LOW_FLOOR_LAYER
+ image_plane = FLOOR_PLANE
/obj/effect/client_image_holder/hallucination/danger/Initialize(mapload, list/mobs_which_see_us, datum/hallucination/parent)
. = ..()
diff --git a/code/modules/holodeck/items.dm b/code/modules/holodeck/items.dm
index 3d80ef6d968c0..7a27aecb4bcfe 100644
--- a/code/modules/holodeck/items.dm
+++ b/code/modules/holodeck/items.dm
@@ -14,7 +14,7 @@
throw_speed = 2
block_chance = 0
throwforce = 0
- embedding = null
+ embed_type = null
sword_color_icon = null
active_throwforce = 0
diff --git a/code/modules/holodeck/turfs.dm b/code/modules/holodeck/turfs.dm
index 7432de695175e..ee62a12b3d0e1 100644
--- a/code/modules/holodeck/turfs.dm
+++ b/code/modules/holodeck/turfs.dm
@@ -127,6 +127,7 @@
desc = "Space-looking floor. Thankfully, the deadly aspects of space are not emulated here."
icon = 'icons/turf/space.dmi'
icon_state = "space"
+ layer = SPACE_LAYER
plane = PLANE_SPACE
/turf/open/floor/holofloor/hyperspace
@@ -164,7 +165,7 @@
/turf/open/floor/holofloor/carpet/update_icon(updates=ALL)
. = ..()
- if((updates & UPDATE_SMOOTHING) && overfloor_placed && smoothing_flags & (SMOOTH_CORNERS|SMOOTH_BITMASK))
+ if((updates & UPDATE_SMOOTHING) && overfloor_placed && smoothing_flags & USES_SMOOTHING)
QUEUE_SMOOTH(src)
/turf/open/floor/holofloor/wood
diff --git a/code/modules/hydroponics/hydroitemdefines.dm b/code/modules/hydroponics/hydroitemdefines.dm
index 5c8a7049eb825..5f72806ca3635 100644
--- a/code/modules/hydroponics/hydroitemdefines.dm
+++ b/code/modules/hydroponics/hydroitemdefines.dm
@@ -478,13 +478,18 @@
throwforce = 15
throw_speed = 4
throw_range = 7
- embedding = list("pain_mult" = 4, "embed_chance" = 35, "fall_chance" = 10)
+ embed_type = /datum/embed_data/hatchet
custom_materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT*7.5)
attack_verb_continuous = list("chops", "tears", "lacerates", "cuts")
attack_verb_simple = list("chop", "tear", "lacerate", "cut")
hitsound = 'sound/weapons/bladeslice.ogg'
sharpness = SHARP_EDGED
+/datum/embed_data/hatchet
+ pain_mult = 4
+ embed_chance = 35
+ fall_chance = 10
+
/obj/item/hatchet/Initialize(mapload)
. = ..()
AddComponent(/datum/component/butchering, \
diff --git a/code/modules/hydroponics/hydroponics.dm b/code/modules/hydroponics/hydroponics.dm
index 791b7ac51a253..34fb9fc04622e 100644
--- a/code/modules/hydroponics/hydroponics.dm
+++ b/code/modules/hydroponics/hydroponics.dm
@@ -249,7 +249,7 @@
// So we'll let it leak in, and move the water over.
set_recipient_reagents_holder(nutri_reagents)
reagents = nutri_reagents
- process_request(dir = dir)
+ process_request(dir = dir, round_robin = FALSE)
// Move the leaked water from nutrients to... water
var/leaking_water_amount = nutri_reagents.get_reagent_amount(/datum/reagent/water)
@@ -267,7 +267,7 @@
process_request(
amount = extra_water_to_gather,
reagent = /datum/reagent/water,
- dir = dir,
+ dir = dir
)
// Now transfer all remaining water in that buffer and clear it out.
diff --git a/code/modules/hydroponics/plant_genes.dm b/code/modules/hydroponics/plant_genes.dm
index c5861140e4e56..6fc671ccd4c94 100644
--- a/code/modules/hydroponics/plant_genes.dm
+++ b/code/modules/hydroponics/plant_genes.dm
@@ -859,12 +859,15 @@
return
var/obj/item/seeds/our_seed = our_plant.get_plant_seed()
- if(our_seed.get_gene(/datum/plant_gene/trait/stinging))
- our_plant.embedding = EMBED_POINTY
- else
- our_plant.embedding = EMBED_HARMLESS
- our_plant.updateEmbedding()
our_plant.throwforce = (our_seed.potency/20)
+ if (!our_plant.get_embed())
+ return
+
+ if(our_seed.get_gene(/datum/plant_gene/trait/stinging))
+ our_plant.set_embed(our_plant.get_embed().generate_with_values(ignore_throwspeed_threshold = TRUE))
+ return
+
+ our_plant.set_embed(our_plant.get_embed().generate_with_values(ignore_throwspeed_threshold = TRUE, pain_mult = 0, jostle_pain_mult = 0))
/**
* This trait automatically heats up the plant's chemical contents when harvested.
diff --git a/code/modules/jobs/job_types/chaplain/chaplain.dm b/code/modules/jobs/job_types/chaplain/chaplain.dm
index 7b587fe06fbf5..9138b335a544b 100644
--- a/code/modules/jobs/job_types/chaplain/chaplain.dm
+++ b/code/modules/jobs/job_types/chaplain/chaplain.dm
@@ -78,8 +78,8 @@
else
holy_bible.deity_name = pick("Gay Space Jesus", "Gandalf", "Dumbledore")
human_spawned.adjustOrganLoss(ORGAN_SLOT_BRAIN, 100) // starts off brain damaged as fuck
- if("lol", "wtf", "poo", "badmin", "shitmin", "deadmin", "meme", "memes")
- new_bible = pick("Woody's Got Wood: The Aftermath", "Sweet Bro and Hella Jeff: Expanded Edition","F.A.T.A.L. Rulebook")
+ if("lol", "wtf", "poo", "badmin", "shitmin", "deadmin", "meme", "memes", "skibidi")
+ new_bible = pick("Woody's Got Wood: The Aftermath", "Sweet Bro and Hella Jeff: Expanded Edition","F.A.T.A.L. Rulebook", "Toilet Humor")
switch(new_bible)
if("Woody's Got Wood: The Aftermath")
holy_bible.deity_name = pick("Woody", "Andy", "Cherry Flavored Lube")
@@ -87,6 +87,8 @@
holy_bible.deity_name = pick("Sweet Bro", "Hella Jeff", "Stairs", "AH")
if("F.A.T.A.L. Rulebook")
holy_bible.deity_name = "Twenty Ten-Sided Dice"
+ if("Toilet Humor")
+ holy_bible.deity_name = pick("Skibidi Toilet", "Skibidi Wizard", "Skibidi Bathtub", "John Skibidi", "Skibidi Skibidi", "G-Toilet 1.0", "John Freeman")
human_spawned.adjustOrganLoss(ORGAN_SLOT_BRAIN, 100) // also starts off brain damaged as fuck
if("servicianism", "partying")
holy_bible.desc = "Happy, Full, Clean. Live it and give it."
diff --git a/code/modules/jobs/job_types/chaplain/chaplain_nullrod.dm b/code/modules/jobs/job_types/chaplain/chaplain_nullrod.dm
index f204518acc53b..df658d71dddc2 100644
--- a/code/modules/jobs/job_types/chaplain/chaplain_nullrod.dm
+++ b/code/modules/jobs/job_types/chaplain/chaplain_nullrod.dm
@@ -48,6 +48,7 @@
Allows you to behead targets for empowered strikes. \
Harms you if you dismiss the scythe without first causing harm to a creature. \
The shard also causes you to become Morbid, shifting your interests towards the macabre."
+ rods[/obj/item/melee/skateboard/holyboard] = "A skateboard that grants you flight and anti-magic abilities while ridden. Fits in your bag."
AddComponent(/datum/component/subtype_picker, rods, CALLBACK(src, PROC_REF(on_holy_weapon_picked)))
/obj/item/nullrod/proc/on_holy_weapon_picked(obj/item/nullrod/holy_weapon_type)
diff --git a/code/modules/jobs/job_types/chief_engineer.dm b/code/modules/jobs/job_types/chief_engineer.dm
index 5d007522baf6c..3a62a4b446a56 100644
--- a/code/modules/jobs/job_types/chief_engineer.dm
+++ b/code/modules/jobs/job_types/chief_engineer.dm
@@ -71,7 +71,7 @@
belt = /obj/item/storage/belt/utility/chief/full
ears = /obj/item/radio/headset/heads/ce
gloves = /obj/item/clothing/gloves/color/black
- head = /obj/item/clothing/head/utility/hardhat/white
+ head = /obj/item/clothing/head/utility/hardhat/welding/white/up
shoes = /obj/item/clothing/shoes/sneakers/brown
l_pocket = /obj/item/modular_computer/pda/heads/ce
diff --git a/code/modules/jobs/job_types/detective.dm b/code/modules/jobs/job_types/detective.dm
index ceb4a279d0b84..3dcc5ac005fec 100644
--- a/code/modules/jobs/job_types/detective.dm
+++ b/code/modules/jobs/job_types/detective.dm
@@ -73,6 +73,8 @@
)
implants = list(/obj/item/implant/mindshield)
+ skillchips = list(/obj/item/skillchip/job/detectives_taste)
+
/datum/outfit/job/detective/pre_equip(mob/living/carbon/human/human, visualsOnly = FALSE)
. = ..()
if (human.age < AGE_MINOR)
diff --git a/code/modules/jobs/job_types/research_director.dm b/code/modules/jobs/job_types/research_director.dm
index c78999b3b73bc..22ef734958326 100644
--- a/code/modules/jobs/job_types/research_director.dm
+++ b/code/modules/jobs/job_types/research_director.dm
@@ -58,7 +58,7 @@
id = /obj/item/card/id/advanced/silver
id_trim = /datum/id_trim/job/research_director
uniform = /obj/item/clothing/under/rank/rnd/research_director/turtleneck
- suit = /obj/item/clothing/suit/jacket/research_director
+ suit = /obj/item/clothing/suit/toggle/labcoat/research_director
backpack_contents = list(
/obj/item/melee/baton/telescopic = 1,
)
diff --git a/code/modules/jobs/job_types/shaft_miner.dm b/code/modules/jobs/job_types/shaft_miner.dm
index 9738f64a482ba..d273092a1eaa1 100644
--- a/code/modules/jobs/job_types/shaft_miner.dm
+++ b/code/modules/jobs/job_types/shaft_miner.dm
@@ -112,8 +112,8 @@
var/obj/item/stack/sheet/animalhide/goliath_hide/plating = new()
explorer_suit.hood.attackby(plating)
for(var/obj/item/gun/energy/recharge/kinetic_accelerator/accelerator in miner_contents)
- var/obj/item/knife/combat/survival/knife = new(accelerator)
- accelerator.bayonet = knife
+ var/datum/component/bayonet_attachable/bayonet = accelerator.GetComponent(/datum/component/bayonet_attachable)
+ bayonet.add_bayonet(new /obj/item/knife/combat/survival(accelerator))
var/obj/item/flashlight/seclite/flashlight = new()
var/datum/component/seclite_attachable/light_component = accelerator.GetComponent(/datum/component/seclite_attachable)
light_component.add_light(flashlight)
diff --git a/code/modules/jobs/job_types/station_engineer.dm b/code/modules/jobs/job_types/station_engineer.dm
index faa01a28d21d5..4506a2ae22535 100644
--- a/code/modules/jobs/job_types/station_engineer.dm
+++ b/code/modules/jobs/job_types/station_engineer.dm
@@ -48,7 +48,7 @@
uniform = /obj/item/clothing/under/rank/engineering/engineer
belt = /obj/item/storage/belt/utility/full/engi
ears = /obj/item/radio/headset/headset_eng
- head = /obj/item/clothing/head/utility/hardhat
+ head = /obj/item/clothing/head/utility/hardhat/welding/up
shoes = /obj/item/clothing/shoes/workboots
l_pocket = /obj/item/modular_computer/pda/engineering
r_pocket = /obj/item/t_scanner
diff --git a/code/modules/library/skill_learning/job_skillchips/detective.dm b/code/modules/library/skill_learning/job_skillchips/detective.dm
new file mode 100644
index 0000000000000..427b8566e7e1f
--- /dev/null
+++ b/code/modules/library/skill_learning/job_skillchips/detective.dm
@@ -0,0 +1,9 @@
+/obj/item/skillchip/job/detectives_taste
+ name = "DET.ekt skillchip"
+ desc = "Detective \"Encyclopedic Knowledge of Tastes\" v1.21"
+ auto_traits = list(TRAIT_DETECTIVES_TASTE)
+ skill_name = "Detective's Taste"
+ skill_description = "Deduce the minute chemical compositions of any liquid substance just by swishing it around your mouth for a bit."
+ skill_icon = "vial"
+ activate_message = span_notice("An explosion of flavors hit your mouth as you remember the secret tastebuds long forgotten.")
+ deactivate_message = span_notice("Your mouth dulls to the hidden tastes of the world.")
diff --git a/code/modules/lootpanel/contents.dm b/code/modules/lootpanel/contents.dm
index 4bb255b15611b..44f4acd47f24c 100644
--- a/code/modules/lootpanel/contents.dm
+++ b/code/modules/lootpanel/contents.dm
@@ -18,6 +18,9 @@
for(var/atom/thing as anything in source_turf.contents)
// validate
+ if(!istype(thing))
+ stack_trace("Non-atom in the contents of [source_turf]!")
+ continue
if(thing.mouse_opacity == MOUSE_OPACITY_TRANSPARENT)
continue
if(thing.IsObscured())
diff --git a/code/modules/lootpanel/search_object.dm b/code/modules/lootpanel/search_object.dm
index 4de60b04a678a..149be76e71064 100644
--- a/code/modules/lootpanel/search_object.dm
+++ b/code/modules/lootpanel/search_object.dm
@@ -26,6 +26,9 @@
if(isturf(item))
RegisterSignal(item, COMSIG_TURF_CHANGE, PROC_REF(on_turf_change))
else
+ // Lest we find ourselves here again, this is intentionally stupid.
+ // It tracks items going out and user actions, otherwise they can refresh the lootpanel.
+ // If this is to be made to track everything, we'll need to make a new signal to specifically create/delete a search object
RegisterSignals(item, list(
COMSIG_ITEM_PICKUP,
COMSIG_MOVABLE_MOVED,
diff --git a/code/modules/mapfluff/ruins/icemoonruin_code/lavaland_incursion.dm b/code/modules/mapfluff/ruins/icemoonruin_code/lavaland_incursion.dm
new file mode 100644
index 0000000000000..6674123c4089a
--- /dev/null
+++ b/code/modules/mapfluff/ruins/icemoonruin_code/lavaland_incursion.dm
@@ -0,0 +1,20 @@
+/mob/living/basic/mining/legion/cult_skeleton
+ corpse_type = /obj/effect/mob_spawn/corpse/human/skeleton/cultist
+
+/obj/effect/mob_spawn/corpse/human/skeleton/cultist
+ name = "skeleton cultist"
+ outfit = /datum/outfit/skeleton_cultist
+
+/datum/outfit/skeleton_cultist
+ name = "Skeleton Cultist"
+ uniform = /obj/item/clothing/under/rank/civilian/chaplain
+ suit = /obj/item/clothing/suit/hooded/cultrobes
+ shoes = /obj/item/clothing/shoes/cult
+
+/obj/item/paper/crumpled/bloody/ruins/lavaland_incursion
+ name = "blood-written note"
+ default_raw_text = "
Here, far above Indecipheres, shrouded in snow and ice, I sign the death warrant of the last of a species. The Dragons kneel beneath us."
+
+/obj/item/paper/crumpled/bloody/ruins/lavaland_incursion/last
+ name = "blood-written note"
+ default_raw_text = "
SHE WAS WRONG. KILL IT? WE COULD BARELY CONTAIN IT. AND NOW, INDECIPHERES WANTS IT TO COME HOME"
diff --git a/code/modules/mapfluff/ruins/icemoonruin_code/library.dm b/code/modules/mapfluff/ruins/icemoonruin_code/library.dm
index 1de9ce2dc728c..2f3b6381f0484 100644
--- a/code/modules/mapfluff/ruins/icemoonruin_code/library.dm
+++ b/code/modules/mapfluff/ruins/icemoonruin_code/library.dm
@@ -5,6 +5,18 @@
puzzle_id = "library"
open_message = "The door opens with a loud creak."
+/obj/machinery/door/puzzle/keycard/library/animation_length(animation)
+ switch(animation)
+ if(DOOR_OPENING_ANIMATION)
+ return 1.2 SECONDS
+
+/obj/machinery/door/puzzle/keycard/library/animation_segment_delay(animation)
+ switch(animation)
+ if(DOOR_OPENING_PASSABLE)
+ return 1.0 SECONDS
+ if(DOOR_OPENING_FINISHED)
+ return 1.2 SECONDS
+
/obj/item/keycard/library
name = "golden key"
desc = "A dull, golden key."
diff --git a/code/modules/mapfluff/ruins/objects_and_mobs/museum.dm b/code/modules/mapfluff/ruins/objects_and_mobs/museum.dm
index 1c0a5392fb9b2..57f20abb1aa19 100644
--- a/code/modules/mapfluff/ruins/objects_and_mobs/museum.dm
+++ b/code/modules/mapfluff/ruins/objects_and_mobs/museum.dm
@@ -78,15 +78,15 @@
desc = /obj/machinery/atmospherics/components/unary/vent_scrubber::desc
icon = /obj/machinery/atmospherics/components/unary/vent_scrubber::icon
layer = /obj/machinery/atmospherics/components/unary/vent_scrubber::layer
- plane = FLOOR_PLANE
+ plane = /obj/machinery/atmospherics/components/unary/vent_scrubber::plane
icon_state = "scrub_on"
/obj/structure/fluff/fake_vent
name = /obj/machinery/atmospherics/components/unary/vent_pump::name
desc = /obj/machinery/atmospherics/components/unary/vent_pump::desc
icon = /obj/machinery/atmospherics/components/unary/vent_pump::icon
- layer = /obj/machinery/atmospherics/components/unary/vent_scrubber::layer
- plane = FLOOR_PLANE
+ layer = /obj/machinery/atmospherics/components/unary/vent_pump::layer
+ plane = /obj/machinery/atmospherics/components/unary/vent_pump::plane
icon_state = "vent_out"
/turf/open/mirage
diff --git a/code/modules/mapping/mapping_helpers.dm b/code/modules/mapping/mapping_helpers.dm
index e59b04e6c7efa..e830718e12ed6 100644
--- a/code/modules/mapping/mapping_helpers.dm
+++ b/code/modules/mapping/mapping_helpers.dm
@@ -107,6 +107,12 @@
return
return ..(ceiling)
+///Used for marking mapping errors. These should only be created by cases explicitly caught by unit tests, and should NEVER actually appear in production.
+/obj/effect/mapping_error
+ name = "I AM ERROR"
+ desc = "IF YOU SEE ME, YELL AT A MAPPER!!!"
+ icon = 'icons/effects/mapping_helpers.dmi'
+ icon_state = "mapping_error"
/obj/effect/mapping_helpers
icon = 'icons/effects/mapping_helpers.dmi'
@@ -1183,7 +1189,7 @@ INITIALIZE_IMMEDIATE(/obj/effect/mapping_helpers/no_atoms_ontop)
if(response.errored || response.status_code != 200)
query_in_progress = FALSE
CRASH("Failed to fetch mapped custom json from url [json_url], code: [response.status_code], error: [response.error]")
- var/json_data = response["body"]
+ var/json_data = response.body
json_cache[json_url] = json_data
query_in_progress = FALSE
return json_data
diff --git a/code/modules/meteors/meteor_changeling.dm b/code/modules/meteors/meteor_changeling.dm
index 5733e2dbe93e2..9c25f8b6776a6 100644
--- a/code/modules/meteors/meteor_changeling.dm
+++ b/code/modules/meteors/meteor_changeling.dm
@@ -30,7 +30,7 @@
return TRUE
//If the meteor misses the station and deletes itself, we make absolutely sure the changeling reaches the station.
-/obj/effect/meteor/meaty/changeling/handle_stopping()
+/obj/effect/meteor/meaty/changeling/moved_off_z()
if(!landing_target)
//If our destination turf is gone for some reason, we chuck them at the observer_start landmark (usually at the center of the station) as a last resort.
landing_target = locate(/obj/effect/landmark/observer_start) in GLOB.landmarks_list
diff --git a/code/modules/meteors/meteor_dark_matteor.dm b/code/modules/meteors/meteor_dark_matteor.dm
index f965605048204..f72bbcff6fa13 100644
--- a/code/modules/meteors/meteor_dark_matteor.dm
+++ b/code/modules/meteors/meteor_dark_matteor.dm
@@ -60,7 +60,7 @@
qdel(defender)
return FALSE
-/obj/effect/meteor/dark_matteor/handle_stopping()
+/obj/effect/meteor/dark_matteor/moved_off_z()
. = ..()
if(previous_security_level && SSsecurity_level.get_current_level_as_number() != SEC_LEVEL_DELTA)
SSsecurity_level.set_level(previous_security_level)
diff --git a/code/modules/meteors/meteor_types.dm b/code/modules/meteors/meteor_types.dm
index 9096ab24afec8..199f6517abb1e 100644
--- a/code/modules/meteors/meteor_types.dm
+++ b/code/modules/meteors/meteor_types.dm
@@ -60,8 +60,7 @@
get_hit()
if(z != z_original || loc == get_turf(dest))
- qdel(src)
- return
+ moved_off_z()
/obj/effect/meteor/Process_Spacemove(movement_dir = 0, continuous_move = FALSE)
return TRUE //Keeps us from drifting for no reason
@@ -80,13 +79,9 @@
if(!new_loop)
return
- RegisterSignal(new_loop, COMSIG_QDELETING, PROC_REF(handle_stopping))
-
///Deals with what happens when we stop moving, IE we die
-/obj/effect/meteor/proc/handle_stopping()
- SIGNAL_HANDLER
- if(!QDELETED(src))
- qdel(src)
+/obj/effect/meteor/proc/moved_off_z()
+ qdel(src)
/obj/effect/meteor/proc/ram_turf(turf/T)
//first yell at mobs about them dying horribly
@@ -459,8 +454,8 @@
/obj/effect/meteor/pumpkin
name = "PUMPKING"
desc = "THE PUMPKING'S COMING!"
- icon = 'icons/obj/meteor_spooky.dmi'
- icon_state = "pumpkin"
+ icon = 'icons/obj/meteor.dmi'
+ icon_state = "spooky"
hits = 10
heavy = TRUE
dropamt = 1
diff --git a/code/modules/mining/boulder_processing/boulder_types.dm b/code/modules/mining/boulder_processing/boulder_types.dm
index 1ffce8737bdd4..8f6889b7c8470 100644
--- a/code/modules/mining/boulder_processing/boulder_types.dm
+++ b/code/modules/mining/boulder_processing/boulder_types.dm
@@ -4,7 +4,7 @@
desc = "This boulder is brimming with strange energy. Cracking it open could contain something unusual for science."
icon_state = "boulder_artifact"
/// This is the type of item that will be inside the boulder. Default is a strange object.
- var/artifact_type = /obj/item/relic
+ var/artifact_type = /obj/item/relic/lavaland
/// References to the relic inside the boulder, if any.
var/obj/item/artifact_inside
diff --git a/code/modules/mining/fulton.dm b/code/modules/mining/fulton.dm
index 56b65c456197b..649acabfcae4a 100644
--- a/code/modules/mining/fulton.dm
+++ b/code/modules/mining/fulton.dm
@@ -97,13 +97,10 @@ GLOBAL_LIST_EMPTY(total_extraction_beacons)
if(uses_left <= 0)
user.transferItemToLoc(src, thing, TRUE)
- var/mutable_appearance/balloon
- var/mutable_appearance/balloon2
- var/mutable_appearance/balloon3
-
if(isliving(thing))
var/mob/living/creature = thing
creature.Paralyze(32 SECONDS) // Keep them from moving during the duration of the extraction
+ ADD_TRAIT(creature, TRAIT_FORCED_STANDING, FULTON_PACK_TRAIT) // Prevents animation jank from happening
if(creature.buckled)
creature.buckled.unbuckle_mob(creature, TRUE) // Unbuckle them to prevent anchoring problems
else
@@ -113,14 +110,15 @@ GLOBAL_LIST_EMPTY(total_extraction_beacons)
var/obj/effect/extraction_holder/holder_obj = new(get_turf(thing))
holder_obj.appearance = thing.appearance
thing.forceMove(holder_obj)
- balloon2 = mutable_appearance('icons/effects/fulton_balloon.dmi', "fulton_expand")
+ var/mutable_appearance/balloon2 = mutable_appearance('icons/effects/fulton_balloon.dmi', "fulton_expand", layer = VEHICLE_LAYER)
balloon2.pixel_y = 10
balloon2.appearance_flags = RESET_COLOR | RESET_ALPHA | RESET_TRANSFORM
holder_obj.add_overlay(balloon2)
+ addtimer(CALLBACK(src, PROC_REF(create_balloon), thing, user, holder_obj, balloon2), 0.4 SECONDS)
+ return ITEM_INTERACT_SUCCESS
- sleep(0.4 SECONDS)
-
- balloon = mutable_appearance('icons/effects/fulton_balloon.dmi', "fulton_balloon")
+/obj/item/extraction_pack/proc/create_balloon(atom/movable/thing, mob/living/user, obj/effect/extraction_holder/holder_obj, mutable_appearance/balloon2)
+ var/mutable_appearance/balloon = mutable_appearance('icons/effects/fulton_balloon.dmi', "fulton_balloon", layer = VEHICLE_LAYER)
balloon.pixel_y = 10
balloon.appearance_flags = RESET_COLOR | RESET_ALPHA | RESET_TRANSFORM
holder_obj.cut_overlay(balloon2)
@@ -132,6 +130,7 @@ GLOBAL_LIST_EMPTY(total_extraction_beacons)
animate(pixel_z = -5, time = 1 SECONDS, flags = ANIMATION_RELATIVE)
animate(pixel_z = 5, time = 1 SECONDS, flags = ANIMATION_RELATIVE)
animate(pixel_z = -5, time = 1 SECONDS, flags = ANIMATION_RELATIVE)
+
sleep(6 SECONDS)
playsound(holder_obj.loc, 'sound/items/fultext_launch.ogg', vol = 50, vary = TRUE, extrarange = -3)
@@ -146,7 +145,7 @@ GLOBAL_LIST_EMPTY(total_extraction_beacons)
sleep(3 SECONDS)
var/turf/flooring_near_beacon = list()
- var/turf/beacon_turf = get_turf(beacon)
+ var/turf/beacon_turf = get_turf(beacon_ref.resolve())
for(var/turf/floor as anything in RANGE_TURFS(1, beacon_turf))
if(!floor.is_blocked_turf())
flooring_near_beacon += floor
@@ -159,26 +158,28 @@ GLOBAL_LIST_EMPTY(total_extraction_beacons)
animate(holder_obj, pixel_z = -990, time = 5 SECONDS, flags = ANIMATION_RELATIVE)
animate(pixel_z = 5, time = 1 SECONDS, flags = ANIMATION_RELATIVE)
animate(pixel_z = -5, time = 1 SECONDS, flags = ANIMATION_RELATIVE)
+
sleep(7 SECONDS)
- balloon3 = mutable_appearance('icons/effects/fulton_balloon.dmi', "fulton_retract")
+ var/mutable_appearance/balloon3 = mutable_appearance('icons/effects/fulton_balloon.dmi', "fulton_retract", layer = VEHICLE_LAYER)
balloon3.pixel_y = 10
balloon3.appearance_flags = RESET_COLOR | RESET_ALPHA | RESET_TRANSFORM
holder_obj.cut_overlay(balloon)
holder_obj.add_overlay(balloon3)
+
sleep(0.4 SECONDS)
holder_obj.cut_overlay(balloon3)
+ if (isliving(thing))
+ REMOVE_TRAIT(thing, TRAIT_FORCED_STANDING, FULTON_PACK_TRAIT)
thing.set_anchored(FALSE) // An item has to be unanchored to be extracted in the first place.
thing.set_density(initial(thing.density))
animate(holder_obj, pixel_z = -10, time = 0.5 SECONDS, flags = ANIMATION_RELATIVE)
sleep(0.5 SECONDS)
-
thing.forceMove(holder_obj.loc)
qdel(holder_obj)
if(uses_left <= 0)
qdel(src)
- return ITEM_INTERACT_SUCCESS
/obj/item/fulton_core
name = "extraction beacon assembly kit"
diff --git a/code/modules/mining/machine_stacking.dm b/code/modules/mining/machine_stacking.dm
index a52107ce8b260..385afb0f81f3e 100644
--- a/code/modules/mining/machine_stacking.dm
+++ b/code/modules/mining/machine_stacking.dm
@@ -31,13 +31,10 @@
machine = null
return ..()
-/obj/machinery/mineral/stacking_unit_console/multitool_act(mob/living/user, obj/item/I)
- if(!multitool_check_buffer(user, I))
- return
- var/obj/item/multitool/M = I
+/obj/machinery/mineral/stacking_unit_console/multitool_act(mob/living/user, obj/item/multitool/M)
M.set_buffer(src)
balloon_alert(user, "saved to multitool buffer")
- return TRUE
+ return ITEM_INTERACT_SUCCESS
/obj/machinery/mineral/stacking_unit_console/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
diff --git a/code/modules/mob/dead/observer/observer.dm b/code/modules/mob/dead/observer/observer.dm
index 1a7b362ec2e73..c1a81b9d33847 100644
--- a/code/modules/mob/dead/observer/observer.dm
+++ b/code/modules/mob/dead/observer/observer.dm
@@ -359,6 +359,12 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
abstract_move(destination) // move like the wind
return TRUE
+/mob/dead/observer/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change)
+ . = ..()
+ var/area/new_area = get_area(src)
+ if(new_area != ambience_tracked_area)
+ update_ambience_area(new_area)
+
/mob/dead/observer/verb/reenter_corpse()
set category = "Ghost"
set name = "Re-enter Corpse"
diff --git a/code/modules/mob/inventory.dm b/code/modules/mob/inventory.dm
index 7a63d0fc8e622..2d22c11ee71f8 100644
--- a/code/modules/mob/inventory.dm
+++ b/code/modules/mob/inventory.dm
@@ -405,22 +405,6 @@
items -= held_items
return items
-/**
- * Used to return a list of equipped items on a human mob; does not by default include held items, see include_flags
- *
- * Argument(s):
- * * Optional - include_flags, (see obj.flags.dm) describes which optional things to include or not (pockets, accessories, held items)
- */
-
-/mob/living/carbon/human/get_equipped_items(include_flags = NONE)
- var/list/items = ..()
- if(!(include_flags & INCLUDE_POCKETS))
- items -= list(l_store, r_store, s_store)
- if((include_flags & INCLUDE_ACCESSORIES) && w_uniform)
- var/obj/item/clothing/under/worn_under = w_uniform
- items += worn_under.attached_accessories
- return items
-
/**
* Returns the items that were succesfully unequipped.
*/
@@ -439,36 +423,88 @@
dropped_items |= return_val
return dropped_items
-/mob/living/carbon/proc/check_obscured_slots(transparent_protection)
- var/obscured = NONE
- var/hidden_slots = NONE
-
- for(var/obj/item/equipped_item in get_equipped_items())
- hidden_slots |= equipped_item.flags_inv
- if(transparent_protection)
- hidden_slots |= equipped_item.transparent_protection
-
- if(hidden_slots & HIDENECK)
- obscured |= ITEM_SLOT_NECK
- if(hidden_slots & HIDEMASK)
- obscured |= ITEM_SLOT_MASK
- if(hidden_slots & HIDEEYES)
- obscured |= ITEM_SLOT_EYES
- if(hidden_slots & HIDEEARS)
- obscured |= ITEM_SLOT_EARS
- if(hidden_slots & HIDEGLOVES)
- obscured |= ITEM_SLOT_GLOVES
- if(hidden_slots & HIDEJUMPSUIT)
- obscured |= ITEM_SLOT_ICLOTHING
- if(hidden_slots & HIDESHOES)
- obscured |= ITEM_SLOT_FEET
- if(hidden_slots & HIDESUITSTORAGE)
- obscured |= ITEM_SLOT_SUITSTORE
- if(hidden_slots & HIDEHEADGEAR)
- obscured |= ITEM_SLOT_HEAD
-
- return obscured
+/**
+ * Try to equip an item to a slot on the mob
+ *
+ * This is a SAFE proc. Use this instead of equip_to_slot()!
+ *
+ * set qdel_on_fail to have it delete W if it fails to equip
+ *
+ * set disable_warning to disable the 'you are unable to equip that' warning.
+ *
+ * unset redraw_mob to prevent the mob icons from being redrawn at the end.
+ *
+ * Initial is used to indicate whether or not this is the initial equipment (job datums etc) or just a player doing it
+ *
+ * set indirect_action to allow insertions into "soft" locked objects, things that are easily opened by the owning mob
+ */
+/mob/proc/equip_to_slot_if_possible(obj/item/W, slot, qdel_on_fail = FALSE, disable_warning = FALSE, redraw_mob = TRUE, bypass_equip_delay_self = FALSE, initial = FALSE, indirect_action = FALSE)
+ if(!istype(W) || QDELETED(W)) //This qdeleted is to prevent stupid behavior with things that qdel during init, like say stacks
+ return FALSE
+ if(!W.mob_can_equip(src, slot, disable_warning, bypass_equip_delay_self, indirect_action = indirect_action))
+ if(qdel_on_fail)
+ qdel(W)
+ else if(!disable_warning)
+ to_chat(src, span_warning("You are unable to equip that!"))
+ return FALSE
+ equip_to_slot(W, slot, initial, redraw_mob, indirect_action = indirect_action) //This proc should not ever fail.
+ return TRUE
+
+/**
+ * Actually equips an item to a slot (UNSAFE)
+ *
+ * This is an UNSAFE proc. It merely handles the actual job of equipping. All the checks on
+ * whether you can or can't equip need to be done before! Use mob_can_equip() for that task.
+ *
+ *In most cases you will want to use equip_to_slot_if_possible()
+ */
+/mob/proc/equip_to_slot(obj/item/equipping, slot, initial = FALSE, redraw_mob = FALSE, indirect_action = FALSE)
+ return
+
+/**
+ * Equip an item to the slot or delete
+ *
+ * This is just a commonly used configuration for the equip_to_slot_if_possible() proc, used to
+ * equip people when the round starts and when events happen and such.
+ *
+ * Also bypasses equip delay checks, since the mob isn't actually putting it on.
+ * Initial is used to indicate whether or not this is the initial equipment (job datums etc) or just a player doing it
+ * set indirect_action to allow insertions into "soft" locked objects, things that are easily opened by the owning mob
+ */
+/mob/proc/equip_to_slot_or_del(obj/item/W, slot, initial = FALSE, indirect_action = FALSE)
+ return equip_to_slot_if_possible(W, slot, TRUE, TRUE, FALSE, TRUE, initial, indirect_action)
+
+/**
+ * Auto equip the passed in item the appropriate slot based on equipment priority
+ *
+ * puts the item "W" into an appropriate slot in a human's inventory
+ *
+ * returns 0 if it cannot, 1 if successful
+ */
+/mob/proc/equip_to_appropriate_slot(obj/item/W, qdel_on_fail = FALSE, indirect_action = FALSE)
+ if(!istype(W))
+ return FALSE
+ var/slot_priority = W.slot_equipment_priority
+
+ if(!slot_priority)
+ slot_priority = list( \
+ ITEM_SLOT_BACK, ITEM_SLOT_ID,\
+ ITEM_SLOT_ICLOTHING, ITEM_SLOT_OCLOTHING,\
+ ITEM_SLOT_MASK, ITEM_SLOT_HEAD, ITEM_SLOT_NECK,\
+ ITEM_SLOT_FEET, ITEM_SLOT_GLOVES,\
+ ITEM_SLOT_EARS, ITEM_SLOT_EYES,\
+ ITEM_SLOT_BELT, ITEM_SLOT_SUITSTORE,\
+ ITEM_SLOT_LPOCKET, ITEM_SLOT_RPOCKET,\
+ ITEM_SLOT_DEX_STORAGE\
+ )
+ for(var/slot in slot_priority)
+ if(equip_to_slot_if_possible(W, slot, disable_warning = TRUE, redraw_mob = TRUE, indirect_action = indirect_action))
+ return TRUE
+
+ if(qdel_on_fail)
+ qdel(W)
+ return FALSE
/// Tries to equip an item, store it in open storage, or in next best storage
/obj/item/proc/equip_to_best_slot(mob/user)
@@ -549,27 +585,6 @@
if(hud_used)
hud_used.build_hand_slots()
-/mob/living/carbon/human/change_number_of_hands(amt)
- var/old_limbs = held_items.len
- if(amt < old_limbs)
- for(var/i in hand_bodyparts.len to amt step -1)
- var/obj/item/bodypart/BP = hand_bodyparts[i]
- BP.dismember()
- hand_bodyparts[i] = null
- hand_bodyparts.len = amt
- else if(amt > old_limbs)
- hand_bodyparts.len = amt
- for(var/i in old_limbs+1 to amt)
- var/path = /obj/item/bodypart/arm/left
- if(!(i % 2))
- path = /obj/item/bodypart/arm/right
-
- var/obj/item/bodypart/BP = new path ()
- BP.held_index = i
- BP.try_attach_limb(src, TRUE)
- hand_bodyparts[i] = BP
- ..() //Don't redraw hands until we have organs for them
-
//GetAllContents that is reasonable and not stupid
/mob/living/proc/get_all_gear()
var/list/processing_list = get_equipped_items(INCLUDE_POCKETS | INCLUDE_ACCESSORIES | INCLUDE_HELD)
diff --git a/code/modules/mob/living/basic/jungle/seedling/seedling.dm b/code/modules/mob/living/basic/jungle/seedling/seedling.dm
index 5a958d3ca7c18..7a853b8a9d086 100644
--- a/code/modules/mob/living/basic/jungle/seedling/seedling.dm
+++ b/code/modules/mob/living/basic/jungle/seedling/seedling.dm
@@ -30,6 +30,7 @@
lighting_cutoff_green = 20
lighting_cutoff_blue = 25
mob_size = MOB_SIZE_LARGE
+ faction = list(FACTION_PLANTS)
attack_sound = 'sound/weapons/bladeslice.ogg'
attack_vis_effect = ATTACK_EFFECT_SLASH
ai_controller = /datum/ai_controller/basic_controller/seedling
@@ -206,7 +207,7 @@
/mob/living/basic/seedling/meanie
maxHealth = 400
health = 400
- faction = list(FACTION_JUNGLE)
+ faction = list(FACTION_JUNGLE, FACTION_PLANTS)
ai_controller = /datum/ai_controller/basic_controller/seedling/meanie
seedling_commands = list(
/datum/pet_command/idle,
diff --git a/code/modules/mob/living/basic/lavaland/goliath/tentacle.dm b/code/modules/mob/living/basic/lavaland/goliath/tentacle.dm
index 030ae6d64b837..ba6346ea19159 100644
--- a/code/modules/mob/living/basic/lavaland/goliath/tentacle.dm
+++ b/code/modules/mob/living/basic/lavaland/goliath/tentacle.dm
@@ -20,7 +20,7 @@
if (ismineralturf(loc))
var/turf/closed/mineral/floor = loc
floor.gets_drilled()
- if (!isopenturf(loc) || isspaceturf(loc) || isopenspaceturf(loc))
+ if (!isopenturf(loc) || is_space_or_openspace(loc))
return INITIALIZE_HINT_QDEL
for (var/obj/effect/goliath_tentacle/tentacle in loc)
if (tentacle != src)
diff --git a/code/modules/mob/living/basic/lavaland/raptor/_raptor.dm b/code/modules/mob/living/basic/lavaland/raptor/_raptor.dm
index 8cd3c567a7d2d..784f5dd369907 100644
--- a/code/modules/mob/living/basic/lavaland/raptor/_raptor.dm
+++ b/code/modules/mob/living/basic/lavaland/raptor/_raptor.dm
@@ -309,4 +309,8 @@ GLOBAL_LIST_EMPTY(raptor_population)
animated = FALSE
insert_on_attack = FALSE
+/datum/storage/raptor_storage/on_mousedropped_onto(datum/source, obj/item/dropping, mob/user)
+ ..()
+ return NONE
+
#undef HAPPINESS_BOOST_DAMPENER
diff --git a/code/modules/mob/living/basic/lavaland/watcher/watcher_overwatch.dm b/code/modules/mob/living/basic/lavaland/watcher/watcher_overwatch.dm
index 541a4ed9e1c7e..a61ea0743f9a4 100644
--- a/code/modules/mob/living/basic/lavaland/watcher/watcher_overwatch.dm
+++ b/code/modules/mob/living/basic/lavaland/watcher/watcher_overwatch.dm
@@ -76,7 +76,7 @@
COMSIG_MOB_ITEM_ATTACK,
COMSIG_MOB_THROW,
COMSIG_MOB_USED_MECH_EQUIPMENT,
- COMSIG_MOB_USED_MECH_MELEE,
+ COMSIG_MOB_USED_CLICK_MECH_MELEE,
COMSIG_MOVABLE_MOVED,
)
diff --git a/code/modules/mob/living/basic/pets/parrot/parrot_ai/parrot_hoarding.dm b/code/modules/mob/living/basic/pets/parrot/parrot_ai/parrot_hoarding.dm
index 7484cbfe6751c..ad9a56b3acfdd 100644
--- a/code/modules/mob/living/basic/pets/parrot/parrot_ai/parrot_hoarding.dm
+++ b/code/modules/mob/living/basic/pets/parrot/parrot_ai/parrot_hoarding.dm
@@ -29,7 +29,7 @@
/datum/ai_behavior/find_and_set/hoard_location/search_tactic(datum/ai_controller/controller, locate_path, search_range)
for(var/turf/open/candidate in oview(search_range, controller.pawn))
- if(isspaceturf(candidate) || isopenspaceturf(candidate))
+ if(is_space_or_openspace(candidate))
continue
if(candidate.is_blocked_turf(source_atom = controller.pawn))
continue
diff --git a/code/modules/mob/living/basic/ruin_defender/flesh.dm b/code/modules/mob/living/basic/ruin_defender/flesh.dm
index 6f46e69070038..f59ef674e860d 100644
--- a/code/modules/mob/living/basic/ruin_defender/flesh.dm
+++ b/code/modules/mob/living/basic/ruin_defender/flesh.dm
@@ -71,7 +71,7 @@
continue
if(movable == victim)
continue
- if(!victim.CanReach(movable))
+ if(!victim.CanReach(movable) || victim.invisibility)
continue
candidates += movable
if(!length(candidates))
@@ -80,7 +80,7 @@
if(isnull(candidate))
return
victim.start_pulling(candidate, supress_message = TRUE)
- victim.visible_message(span_warning("[victim][victim.p_s()] [current_bodypart] instinctually starts feeling [candidate]!"))
+ victim.visible_message(span_warning("[victim]'s [current_bodypart.name] instinctively starts feeling [candidate]!"))
return
if(HAS_TRAIT(victim, TRAIT_IMMOBILIZED))
@@ -127,7 +127,11 @@
if(BODY_ZONE_R_LEG)
part_type = /obj/item/bodypart/leg/right/flesh
- target.visible_message(span_danger("[src] [target_part ? "tears off and attaches itself" : "attaches itself"] to where [target][target.p_s()] limb used to be!"))
+ if (!isnull(target_part))
+ target.visible_message(span_danger("[src] tears off [target]'s [target_part.plaintext_zone] and attaches itself in [target_part.p_their()] place!"), span_userdanger("[src] tears off your [target_part.plaintext_zone] and attaches itself in [target_part.p_their()] place!"))
+ else
+ target.visible_message(span_danger("[src] attaches itself to where [target]'s [target.parse_zone_with_bodypart(target_zone)] used to be!"), span_userdanger("[src] attaches itself to where your [target.parse_zone_with_bodypart(target_zone)] used to be!"))
+
var/obj/item/bodypart/new_bodypart = new part_type()
forceMove(new_bodypart)
new_bodypart.replace_limb(target, TRUE)
diff --git a/code/modules/mob/living/basic/ruin_defender/living_floor.dm b/code/modules/mob/living/basic/ruin_defender/living_floor.dm
index 851a0873657a0..105838f0c55dd 100644
--- a/code/modules/mob/living/basic/ruin_defender/living_floor.dm
+++ b/code/modules/mob/living/basic/ruin_defender/living_floor.dm
@@ -35,7 +35,7 @@
move_resist = INFINITY
density = FALSE
combat_mode = TRUE
- layer = TURF_LAYER
+ layer = LOW_FLOOR_LAYER
plane = FLOOR_PLANE
faction = list(FACTION_HOSTILE)
melee_damage_lower = 20
diff --git a/code/modules/mob/living/basic/slime/defense.dm b/code/modules/mob/living/basic/slime/defense.dm
index b747c24201971..a3242525170c7 100644
--- a/code/modules/mob/living/basic/slime/defense.dm
+++ b/code/modules/mob/living/basic/slime/defense.dm
@@ -19,7 +19,7 @@
return
attacker.visible_message(span_warning("[attacker] manages to wrestle \the [defender_slime.name] off!"), span_notice("You manage to wrestle \the [defender_slime.name] off!"))
- playsound(loc, 'sound/weapons/thudswoosh.ogg', 50, TRUE, -1)
+ playsound(loc, 'sound/weapons/shove.ogg', 50, TRUE, -1)
defender_slime.discipline_slime()
diff --git a/code/modules/mob/living/basic/space_fauna/meteor_heart/meteor_eyeball.dm b/code/modules/mob/living/basic/space_fauna/meteor_heart/meteor_eyeball.dm
index 44775ea7217ce..0318b044a21ce 100644
--- a/code/modules/mob/living/basic/space_fauna/meteor_heart/meteor_eyeball.dm
+++ b/code/modules/mob/living/basic/space_fauna/meteor_heart/meteor_eyeball.dm
@@ -10,7 +10,7 @@ GLOBAL_LIST_EMPTY(meteor_eyeballs)
desc = "An eyeball growing out of the ground, gross."
icon_state = "eyeball"
max_integrity = 15
- layer = LOW_OBJ_LAYER
+ layer = ABOVE_OPEN_TURF_LAYER
plane = FLOOR_PLANE
/obj/structure/meateor_fluff/eyeball/Initialize(mapload)
diff --git a/code/modules/mob/living/basic/vermin/mouse.dm b/code/modules/mob/living/basic/vermin/mouse.dm
index 3cce5e3481784..724833af16f2a 100644
--- a/code/modules/mob/living/basic/vermin/mouse.dm
+++ b/code/modules/mob/living/basic/vermin/mouse.dm
@@ -117,11 +117,19 @@
. = ..(TRUE)
// Now if we were't ACTUALLY gibbed, spawn the dead mouse
if(!gibbed)
- var/obj/item/food/deadmouse/mouse = new(loc)
- mouse.copy_corpse(src)
- if(HAS_TRAIT(src, TRAIT_BEING_SHOCKED))
- mouse.desc = "They're toast."
- mouse.add_atom_colour("#3A3A3A", FIXED_COLOUR_PRIORITY)
+ var/make_a_corpse = TRUE
+ var/place_to_make_corpse = loc
+ if(istype(loc, /obj/item/clothing/head/mob_holder))//If our mouse is dying in place holder we want to put the dead mouse where the place holder was
+ var/obj/item/clothing/head/mob_holder/found_holder = loc
+ place_to_make_corpse = found_holder.loc
+ if(istype(found_holder.loc, /obj/machinery/microwave))//Microwaves gib things that die when cooked, so we don't need to make a dead body too
+ make_a_corpse = FALSE
+ if(make_a_corpse)
+ var/obj/item/food/deadmouse/mouse = new(place_to_make_corpse)
+ mouse.copy_corpse(src)
+ if(HAS_TRAIT(src, TRAIT_BEING_SHOCKED))
+ mouse.desc = "They're toast."
+ mouse.add_atom_colour("#3A3A3A", FIXED_COLOUR_PRIORITY)
qdel(src)
/mob/living/basic/mouse/UnarmedAttack(atom/attack_target, proximity_flag, list/modifiers)
diff --git a/code/modules/mob/living/blood.dm b/code/modules/mob/living/blood.dm
index a5162dea0dc42..5ac120fcee3f3 100644
--- a/code/modules/mob/living/blood.dm
+++ b/code/modules/mob/living/blood.dm
@@ -103,15 +103,15 @@
investigate_log("has died of bloodloss.", INVESTIGATE_DEATHS)
death()
- // Blood ratio! if you have 230 blood, this equals 0.5 as that's half of the current value, 560.
- var/effective_blood_ratio = blood_volume/BLOOD_VOLUME_NORMAL
+ // Blood ratio! if you have 280 blood, this equals 0.5 as that's half of the current value, 560.
+ var/effective_blood_ratio = blood_volume / BLOOD_VOLUME_NORMAL
- // If your ratio is less than one (you're missing any blood) and your oxyloss is under that ratio %, start getting oxy damage.
+ // If your ratio is less than one (you're missing any blood) and your oxyloss is under missing blood %, start getting oxy damage.
// This damage accrues faster the less blood you have.
// If KO or in hardcrit, the damage accrues even then to prevent being perma-KO.
- if(((effective_blood_ratio < 1) && (getOxyLoss() < (effective_blood_ratio * 100))) || (stat in list(UNCONSCIOUS, HARD_CRIT)))
+ if(((effective_blood_ratio < 1) && (getOxyLoss() < ((1 - effective_blood_ratio) * 100))) || (stat in list(UNCONSCIOUS, HARD_CRIT)))
// At roughly half blood this equals to 3 oxyloss per tick. At 90% blood it's close to 0.5
- var/rounded_oxyloss = round(0.01 * (BLOOD_VOLUME_NORMAL - blood_volume) * seconds_per_tick, 0.25)
+ var/rounded_oxyloss = round(0.01 * (BLOOD_VOLUME_NORMAL - blood_volume), 0.25) * seconds_per_tick
adjustOxyLoss(rounded_oxyloss, updating_health = TRUE)
/// Has each bodypart update its bleed/wound overlay icon states
@@ -227,7 +227,7 @@
****************************************************/
//Gets blood from mob to a container or other mob, preserving all data in it.
-/mob/living/proc/transfer_blood_to(atom/movable/AM, amount, forced)
+/mob/living/proc/transfer_blood_to(atom/movable/AM, amount, forced, ignore_incompatibility)
if(!blood_volume || !AM.reagents)
return FALSE
if(blood_volume < BLOOD_VOLUME_BAD && !forced)
@@ -254,7 +254,7 @@
if((D.spread_flags & DISEASE_SPREAD_SPECIAL) || (D.spread_flags & DISEASE_SPREAD_NON_CONTAGIOUS))
continue
C.ForceContractDisease(D)
- if(!(blood_data["blood_type"] in get_safe_blood(C.dna.blood_type)))
+ if(!(blood_data["blood_type"] in get_safe_blood(C.dna.blood_type)) && !(ignore_incompatibility))
C.reagents.add_reagent(/datum/reagent/toxin, amount * 0.5)
return TRUE
@@ -349,6 +349,15 @@
if(safe)
. = safe
+/**
+ * Returns TRUE if src is compatible with donor's blood, otherwise FALSE.
+ * * donor: Carbon mob, the one that is donating blood.
+ */
+/mob/living/carbon/proc/get_blood_compatibility(mob/living/carbon/donor)
+ var/patient_blood_data = get_blood_data(get_blood_id())
+ var/donor_blood_data = donor.get_blood_data(donor.get_blood_id())
+ return donor_blood_data["blood_type"] in get_safe_blood(patient_blood_data["blood_type"])
+
//to add a splatter of blood or other mob liquid.
/mob/living/proc/add_splatter_floor(turf/T, small_drip)
if(get_blood_id() != /datum/reagent/blood)
diff --git a/code/modules/mob/living/carbon/alien/adult/alien_powers.dm b/code/modules/mob/living/carbon/alien/adult/alien_powers.dm
index f3cfbfda8a9ee..999ec07f41a65 100644
--- a/code/modules/mob/living/carbon/alien/adult/alien_powers.dm
+++ b/code/modules/mob/living/carbon/alien/adult/alien_powers.dm
@@ -251,7 +251,8 @@ Doesn't work on other aliens/AI.*/
plasma_cost = 50
/datum/action/cooldown/alien/acid/neurotoxin/IsAvailable(feedback = FALSE)
- if(owner.is_muzzled())
+ var/mob/living/carbon/as_carbon = owner
+ if(istype(as_carbon) && as_carbon.is_mouth_covered(ITEM_SLOT_MASK))
return FALSE
if(!isturf(owner.loc))
return FALSE
diff --git a/code/modules/mob/living/carbon/alien/alien.dm b/code/modules/mob/living/carbon/alien/alien.dm
index f8210c77368cc..a995eafbb3ac0 100644
--- a/code/modules/mob/living/carbon/alien/alien.dm
+++ b/code/modules/mob/living/carbon/alien/alien.dm
@@ -76,6 +76,7 @@
return pick (list("xltrails_1", "xltrails2"))
else
return pick (list("xttrails_1", "xttrails2"))
+
/*----------------------------------------
Proc: AddInfectionImages()
Des: Gives the client of the alien an image on each infected mob.
diff --git a/code/modules/mob/living/carbon/alien/special/facehugger.dm b/code/modules/mob/living/carbon/alien/special/facehugger.dm
index d068610b28f7a..d1ec6d7e88038 100644
--- a/code/modules/mob/living/carbon/alien/special/facehugger.dm
+++ b/code/modules/mob/living/carbon/alien/special/facehugger.dm
@@ -24,6 +24,7 @@
layer = MOB_LAYER
max_integrity = 100
item_flags = XENOMORPH_HOLDABLE
+ slowdown = 2
var/stat = CONSCIOUS //UNCONSCIOUS is the idle state in this case
var/sterile = FALSE
@@ -39,6 +40,7 @@
)
AddElement(/datum/element/connect_loc, loc_connections)
AddElement(/datum/element/atmos_sensitive, mapload)
+ AddElement(/datum/element/muffles_speech)
RegisterSignal(src, COMSIG_LIVING_TRYING_TO_PULL, PROC_REF(react_to_mob))
@@ -122,7 +124,7 @@
/obj/item/clothing/mask/facehugger/proc/valid_to_attach(mob/living/hit_mob)
// valid targets: carbons except aliens and devils
- // facehugger state early exit checks
+ // facehugger state early exit checks (Note: Melbert does not want dead people to be huggable)
if(stat != CONSCIOUS)
return FALSE
if(attached)
@@ -171,9 +173,12 @@
log_combat(target, src, "was facehugged by")
return TRUE // time for a smoke
-/obj/item/clothing/mask/facehugger/proc/Attach(mob/living/M)
- if(!valid_to_attach(M))
+/obj/item/clothing/mask/facehugger/proc/Attach(mob/living/victim)
+ if(!valid_to_attach(victim))
return
+
+ if(victim.stat < UNCONSCIOUS) //sorry bro you gotta be awake
+ victim.say("AAAA!!") //triggers muffled speech and also visual feedback i guess
// early returns and validity checks done: attach.
attached++
//ensure we detach once we no longer need to be attached
@@ -181,12 +186,12 @@
if(!sterile)
- M.take_bodypart_damage(strength,0) //done here so that humans in helmets take damage
- M.Unconscious(MAX_IMPREGNATION_TIME/0.3) //something like 25 ticks = 20 seconds with the default settings
-
+ victim.take_bodypart_damage(strength,0) //done here so that humans in helmets take damage
+ if(real && !sterile)
+ victim.Knockdown(5 SECONDS)
GoIdle() //so it doesn't jump the people that tear it off
- addtimer(CALLBACK(src, PROC_REF(Impregnate), M), rand(MIN_IMPREGNATION_TIME, MAX_IMPREGNATION_TIME))
+ addtimer(CALLBACK(src, PROC_REF(Impregnate), victim), rand(MIN_IMPREGNATION_TIME, MAX_IMPREGNATION_TIME))
/obj/item/clothing/mask/facehugger/proc/detach()
attached = 0
@@ -249,6 +254,29 @@
visible_message(span_danger("[src] curls up into a ball!"))
+ // chest maybe because getting slammed in the chest would knock it off your face while dead
+ AddComponent(/datum/component/knockoff, knockoff_chance = 40, target_zones = list(BODY_ZONE_HEAD, BODY_ZONE_CHEST), slots_knockoffable = slot_flags)
+
+/obj/item/clothing/mask/facehugger/allow_attack_hand_drop(mob/living/carbon/human/user)
+ if(!real || sterile || user.get_organ_by_type(/obj/item/organ/internal/body_egg/alien_embryo))
+ return ..()
+ if(istype(user) && ishuman(loc) && stat != DEAD)
+ if(user == loc && user.get_item_by_slot(slot_flags) == src)
+ to_chat(user, span_userdanger("[src] is latched on too tight! Get help or wait for it to let go!"))
+ return FALSE
+ return ..()
+
+/obj/item/clothing/mask/facehugger/mouse_drop_dragged(atom/over, mob/user, src_location, over_location, params)
+ var/mob/living/carbon/human/wearer = loc
+ if(!istype(wearer) || user != wearer)
+ return
+ if(!real || sterile || user.get_organ_by_type(/obj/item/organ/internal/body_egg/alien_embryo))
+ return ..()
+ if(wearer.get_item_by_slot(slot_flags) == src && stat != DEAD)
+ to_chat(user, span_userdanger("[src] is latched on too tight! Get help or wait for it to let go!"))
+ return
+ return ..()
+
/proc/CanHug(mob/living/M)
if(!istype(M))
return FALSE
@@ -268,6 +296,7 @@
name = "Lamarr"
desc = "The Research Director's pet, a domesticated and debeaked xenomorph facehugger. Friendly, but may still try to couple with your head."
sterile = TRUE
+ slowdown = 1.5 //lamarr is too fat after being fed in captivity to effectively slow people down or something
/obj/item/clothing/mask/facehugger/dead
icon_state = "facehugger_dead"
@@ -287,6 +316,7 @@
real = FALSE
sterile = TRUE
tint = 3 //Makes it feel more authentic when it latches on
+ slowdown = 0
/obj/item/clothing/mask/facehugger/toy/Die()
return
diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm
index 539dfd64ce353..8965bccd0007c 100644
--- a/code/modules/mob/living/carbon/carbon.dm
+++ b/code/modules/mob/living/carbon/carbon.dm
@@ -6,6 +6,7 @@
GLOB.carbon_list += src
ADD_TRAIT(src, TRAIT_CAN_HOLD_ITEMS, INNATE_TRAIT) // Carbons are assumed to be innately capable of having arms, we check their arms count instead
+ breathing_loop = new(src)
/mob/living/carbon/Destroy()
//This must be done first, so the mob ghosts correctly before DNA etc is nulled
@@ -21,6 +22,7 @@
qdel(scar)
remove_from_all_data_huds()
QDEL_NULL(dna)
+ QDEL_NULL(breathing_loop)
GLOB.carbon_list -= src
/mob/living/carbon/item_interaction(mob/living/user, obj/item/tool, list/modifiers)
@@ -228,13 +230,6 @@
. = ..()
loc.handle_fall(src)//it's loc so it doesn't call the mob's handle_fall which does nothing
-/mob/living/carbon/is_muzzled()
- for (var/obj/item/clothing/clothing in get_equipped_items())
- if(clothing.clothing_flags & BLOCKS_SPEECH)
- return TRUE
- return FALSE
-
-
/mob/living/carbon/resist_buckle()
if(!HAS_TRAIT(src, TRAIT_RESTRAINED))
buckled.user_unbuckle_mob(src, src)
diff --git a/code/modules/mob/living/carbon/carbon_defense.dm b/code/modules/mob/living/carbon/carbon_defense.dm
index d6dc119de58a3..8ef981b189da4 100644
--- a/code/modules/mob/living/carbon/carbon_defense.dm
+++ b/code/modules/mob/living/carbon/carbon_defense.dm
@@ -415,7 +415,7 @@
// this way, we only visibly try to examine ourselves if we have something embedded, otherwise we'll still hug ourselves :)
visible_message(span_notice("[src] examines [p_them()]self."), \
span_notice("You check yourself for shrapnel."))
- if(I.isEmbedHarmless())
+ if(I.is_embed_harmless())
to_chat(src, "\t There is \a [I] stuck to your [LB.name]!")
else
to_chat(src, "\t There is \a [I] embedded in your [LB.name]!")
@@ -696,7 +696,7 @@
/mob/living/carbon/get_shove_flags(mob/living/shover, obj/item/weapon)
. = ..()
. |= SHOVE_CAN_STAGGER
- if(IsKnockdown() && !IsParalyzed())
+ if(IsKnockdown() && !IsParalyzed() && HAS_TRAIT(src, TRAIT_STUN_ON_NEXT_SHOVE))
. |= SHOVE_CAN_KICK_SIDE
if(HAS_TRAIT(src, TRAIT_NO_SIDE_KICK)) // added as an extra check, just in case
. &= ~SHOVE_CAN_KICK_SIDE
diff --git a/code/modules/mob/living/carbon/carbon_defines.dm b/code/modules/mob/living/carbon/carbon_defines.dm
index dcab69f531ee3..dbfa3849b25ad 100644
--- a/code/modules/mob/living/carbon/carbon_defines.dm
+++ b/code/modules/mob/living/carbon/carbon_defines.dm
@@ -53,6 +53,8 @@
///This is used to determine if the mob failed a breath. If they did fail a breath, they will attempt to breathe each tick, otherwise just once per 4 ticks.
var/failed_last_breath = FALSE
+ ///Sound loop for breathing when using internals
+ var/datum/looping_sound/breathing/breathing_loop
/// Used in [carbon/proc/check_breath] and [lungs/proc/check_breath]]
var/co2overloadtime = null
diff --git a/code/modules/mob/living/carbon/death.dm b/code/modules/mob/living/carbon/death.dm
index 5fdd3498f1559..8d4e66bccda6e 100644
--- a/code/modules/mob/living/carbon/death.dm
+++ b/code/modules/mob/living/carbon/death.dm
@@ -3,7 +3,7 @@
return
losebreath = 0
-
+ breathing_loop.stop() //This would've happened eventually but it's nice to make it stop immediatelly in this case
if(!gibbed)
add_memory_in_range(src, 7, /datum/memory/witnessed_death, protagonist = src)
reagents.end_metabolization(src)
diff --git a/code/modules/mob/living/carbon/emote.dm b/code/modules/mob/living/carbon/emote.dm
index 856622203927a..00699196ae9b6 100644
--- a/code/modules/mob/living/carbon/emote.dm
+++ b/code/modules/mob/living/carbon/emote.dm
@@ -20,7 +20,6 @@
key = "clap"
key_third_person = "claps"
message = "claps."
- muzzle_ignore = TRUE
hands_use_check = TRUE
emote_type = EMOTE_AUDIBLE | EMOTE_VISIBLE
audio_cooldown = 5 SECONDS
@@ -185,7 +184,6 @@
message_param = "snaps their fingers at %t."
emote_type = EMOTE_AUDIBLE | EMOTE_VISIBLE
hands_use_check = TRUE
- muzzle_ignore = TRUE
/datum/emote/living/carbon/snap/get_sound(mob/living/user)
if(ishuman(user))
diff --git a/code/modules/mob/living/carbon/examine.dm b/code/modules/mob/living/carbon/examine.dm
index e37048ca32fee..6d6d7ada73ceb 100644
--- a/code/modules/mob/living/carbon/examine.dm
+++ b/code/modules/mob/living/carbon/examine.dm
@@ -36,7 +36,7 @@
var/list/msg = list("")
for(var/obj/item/bodypart/bodypart as anything in bodyparts)
for(var/obj/item/embedded_item as anything in bodypart.embedded_objects)
- if(embedded_item.isEmbedHarmless())
+ if(embedded_item.is_embed_harmless())
msg += "[t_He] [t_has] [icon2html(embedded_item, user)] \a [embedded_item] stuck to [t_his] [bodypart.name]!\n"
else
msg += "[t_He] [t_has] [icon2html(embedded_item, user)] \a [embedded_item] embedded in [t_his] [bodypart.name]!\n"
diff --git a/code/modules/mob/living/carbon/human/dummy.dm b/code/modules/mob/living/carbon/human/dummy.dm
index 3340e34064052..f13e90719c1dc 100644
--- a/code/modules/mob/living/carbon/human/dummy.dm
+++ b/code/modules/mob/living/carbon/human/dummy.dm
@@ -76,7 +76,7 @@ INITIALIZE_IMMEDIATE(/mob/living/carbon/human/dummy)
cut_overlays(TRUE)
/mob/living/carbon/human/dummy/setup_human_dna()
- randomize_human(src, randomize_mutations = FALSE)
+ randomize_human_normie(src, randomize_mutations = FALSE)
/mob/living/carbon/human/dummy/log_mob_tag(text)
return
diff --git a/code/modules/mob/living/carbon/human/examine.dm b/code/modules/mob/living/carbon/human/examine.dm
index bf01bfda6f119..b229898cae1db 100644
--- a/code/modules/mob/living/carbon/human/examine.dm
+++ b/code/modules/mob/living/carbon/human/examine.dm
@@ -138,7 +138,7 @@
disabled += body_part
missing -= body_part.body_zone
for(var/obj/item/I in body_part.embedded_objects)
- if(I.isEmbedHarmless())
+ if(I.is_embed_harmless())
msg += "[t_He] [t_has] [icon2html(I, user)] \a [I] stuck to [t_his] [body_part.name]!\n"
else
msg += "[t_He] [t_has] [icon2html(I, user)] \a [I] embedded in [t_his] [body_part.name]!\n"
diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm
index 9d3d8c2d5a81b..1dd47f8060426 100644
--- a/code/modules/mob/living/carbon/human/human.dm
+++ b/code/modules/mob/living/carbon/human/human.dm
@@ -56,7 +56,7 @@
ADD_TRAIT(src, TRAIT_AGEUSIA, NO_TONGUE_TRAIT)
/mob/living/carbon/human/proc/setup_human_dna()
- randomize_human(src, randomize_mutations = TRUE)
+ randomize_human_normie(src, randomize_mutations = TRUE)
/mob/living/carbon/human/Destroy()
QDEL_NULL(physiology)
diff --git a/code/modules/mob/living/carbon/human/human_defense.dm b/code/modules/mob/living/carbon/human/human_defense.dm
index aa2daa1675e91..976d7475c813b 100644
--- a/code/modules/mob/living/carbon/human/human_defense.dm
+++ b/code/modules/mob/living/carbon/human/human_defense.dm
@@ -146,8 +146,10 @@
return
if(!(shove_flags & SHOVE_KNOCKDOWN_BLOCKED))
target.Knockdown(SHOVE_KNOCKDOWN_HUMAN)
+ target.apply_status_effect(/datum/status_effect/next_shove_stuns)
if(!HAS_TRAIT(src, TRAIT_BRAWLING_KNOCKDOWN_BLOCKED))
Knockdown(SHOVE_KNOCKDOWN_COLLATERAL)
+ apply_status_effect(/datum/status_effect/next_shove_stuns)
target.visible_message(span_danger("[shover] shoves [target.name] into [name]!"),
span_userdanger("You're shoved into [name] by [shover]!"), span_hear("You hear aggressive shuffling followed by a loud thud!"), COMBAT_MESSAGE_RANGE, src)
to_chat(src, span_danger("You shove [target.name] into [name]!"))
diff --git a/code/modules/mob/living/carbon/human/human_movement.dm b/code/modules/mob/living/carbon/human/human_movement.dm
index 14003b86da496..fda6d7a9142ea 100644
--- a/code/modules/mob/living/carbon/human/human_movement.dm
+++ b/code/modules/mob/living/carbon/human/human_movement.dm
@@ -26,8 +26,9 @@
/mob/living/carbon/human/Move(NewLoc, direct)
. = ..()
- if(shoes && body_position == STANDING_UP && loc == NewLoc && has_gravity(loc))
- SEND_SIGNAL(shoes, COMSIG_SHOES_STEP_ACTION)
+ if(shoes && body_position == STANDING_UP && has_gravity(loc))
+ if((. && !moving_diagonally) || (!. && moving_diagonally == SECOND_DIAG_STEP))
+ SEND_SIGNAL(shoes, COMSIG_SHOES_STEP_ACTION)
/mob/living/carbon/human/Process_Spacemove(movement_dir = 0, continuous_move = FALSE)
if(movement_type & FLYING || HAS_TRAIT(src, TRAIT_FREE_FLOAT_MOVEMENT))
diff --git a/code/modules/mob/living/carbon/human/human_say.dm b/code/modules/mob/living/carbon/human/human_say.dm
index 8bf21e3c809a6..0ce34ffa27205 100644
--- a/code/modules/mob/living/carbon/human/human_say.dm
+++ b/code/modules/mob/living/carbon/human/human_say.dm
@@ -70,6 +70,9 @@
var/obj/item/radio/headset/dongle = ears
if(!istype(dongle))
return FALSE
+ var/area/our_area = get_area(src)
+ if(our_area.area_flags & BINARY_JAMMING)
+ return FALSE
return dongle.translate_binary
/mob/living/carbon/human/radio(message, list/message_mods = list(), list/spans, language) //Poly has a copy of this, lazy bastard
diff --git a/code/modules/mob/living/carbon/human/inventory.dm b/code/modules/mob/living/carbon/human/inventory.dm
index b1efa734321c5..a24e9cd070d86 100644
--- a/code/modules/mob/living/carbon/human/inventory.dm
+++ b/code/modules/mob/living/carbon/human/inventory.dm
@@ -1,3 +1,20 @@
+
+/**
+ * Used to return a list of equipped items on a human mob; does not by default include held items, see include_flags
+ *
+ * Argument(s):
+ * * Optional - include_flags, (see obj.flags.dm) describes which optional things to include or not (pockets, accessories, held items)
+ */
+
+/mob/living/carbon/human/get_equipped_items(include_flags = NONE)
+ var/list/items = ..()
+ if(!(include_flags & INCLUDE_POCKETS))
+ items -= list(l_store, r_store, s_store)
+ if((include_flags & INCLUDE_ACCESSORIES) && w_uniform)
+ var/obj/item/clothing/under/worn_under = w_uniform
+ items += worn_under.attached_accessories
+ return items
+
/mob/living/carbon/human/can_equip(obj/item/equip_target, slot, disable_warning = FALSE, bypass_equip_delay_self = FALSE, ignore_equipped = FALSE, indirect_action = FALSE)
if(SEND_SIGNAL(src, COMSIG_HUMAN_EQUIPPING_ITEM, equip_target, slot) == COMPONENT_BLOCK_EQUIP)
return FALSE
@@ -139,8 +156,6 @@
if(glasses)
return
glasses = equipping
- if(glasses.glass_colour_type)
- update_glasses_color(glasses, 1)
if(glasses.vision_flags || glasses.invis_override || glasses.invis_view || !isnull(glasses.lighting_cutoff))
update_sight()
update_worn_glasses()
@@ -233,10 +248,8 @@
update_worn_gloves()
else if(I == glasses)
glasses = null
- var/obj/item/clothing/glasses/G = I
- if(G.glass_colour_type)
- update_glasses_color(G, 0)
- if(G.vision_flags || G.invis_override || G.invis_view || !isnull(G.lighting_cutoff))
+ var/obj/item/clothing/glasses/old_glasses = I
+ if(old_glasses.vision_flags || old_glasses.invis_override || old_glasses.invis_view || !isnull(old_glasses.lighting_cutoff))
update_sight()
if(!QDELETED(src))
update_worn_glasses()
@@ -390,3 +403,24 @@
return
stored.attack_hand(src) // take out thing from item in storage slot
return
+
+/mob/living/carbon/human/change_number_of_hands(amt)
+ var/old_limbs = held_items.len
+ if(amt < old_limbs)
+ for(var/i in hand_bodyparts.len to amt step -1)
+ var/obj/item/bodypart/BP = hand_bodyparts[i]
+ BP.dismember()
+ hand_bodyparts[i] = null
+ hand_bodyparts.len = amt
+ else if(amt > old_limbs)
+ hand_bodyparts.len = amt
+ for(var/i in old_limbs+1 to amt)
+ var/path = /obj/item/bodypart/arm/left
+ if(!(i % 2))
+ path = /obj/item/bodypart/arm/right
+
+ var/obj/item/bodypart/BP = new path ()
+ BP.held_index = i
+ BP.try_attach_limb(src, TRUE)
+ hand_bodyparts[i] = BP
+ ..() //Don't redraw hands until we have organs for them
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 7d29260616f41..b37a4de5f6de5 100644
--- a/code/modules/mob/living/carbon/human/species_types/plasmamen.dm
+++ b/code/modules/mob/living/carbon/human/species_types/plasmamen.dm
@@ -62,6 +62,7 @@
/datum/outfit/syndicate/reinforcement/mi13 = /datum/outfit/syndicate/reinforcement/plasmaman,
/datum/outfit/syndicate/reinforcement/waffle = /datum/outfit/syndicate/reinforcement/plasmaman,
/datum/outfit/syndicate/support = /datum/outfit/syndicate/support/plasmaman,
+ /datum/outfit/syndicate/full/loneop = /datum/outfit/syndicate/full/plasmaman/loneop,
)
/// If the bones themselves are burning clothes won't help you much
diff --git a/code/modules/mob/living/carbon/inventory.dm b/code/modules/mob/living/carbon/inventory.dm
index de6a2692c9b6f..e59a6328aa72e 100644
--- a/code/modules/mob/living/carbon/inventory.dm
+++ b/code/modules/mob/living/carbon/inventory.dm
@@ -1,3 +1,33 @@
+/mob/living/carbon/proc/check_obscured_slots(transparent_protection)
+ var/obscured = NONE
+ var/hidden_slots = NONE
+
+ for(var/obj/item/equipped_item in get_equipped_items())
+ hidden_slots |= equipped_item.flags_inv
+ if(transparent_protection)
+ hidden_slots |= equipped_item.transparent_protection
+
+ if(hidden_slots & HIDENECK)
+ obscured |= ITEM_SLOT_NECK
+ if(hidden_slots & HIDEMASK)
+ obscured |= ITEM_SLOT_MASK
+ if(hidden_slots & HIDEEYES)
+ obscured |= ITEM_SLOT_EYES
+ if(hidden_slots & HIDEEARS)
+ obscured |= ITEM_SLOT_EARS
+ if(hidden_slots & HIDEGLOVES)
+ obscured |= ITEM_SLOT_GLOVES
+ if(hidden_slots & HIDEJUMPSUIT)
+ obscured |= ITEM_SLOT_ICLOTHING
+ if(hidden_slots & HIDESHOES)
+ obscured |= ITEM_SLOT_FEET
+ if(hidden_slots & HIDESUITSTORAGE)
+ obscured |= ITEM_SLOT_SUITSTORE
+ if(hidden_slots & HIDEHEADGEAR)
+ obscured |= ITEM_SLOT_HEAD
+
+ return obscured
+
/mob/living/carbon/get_item_by_slot(slot_id)
switch(slot_id)
if(ITEM_SLOT_BACK)
@@ -280,6 +310,8 @@
internal = null
target_tank.after_internals_closed(src)
update_mob_action_buttons()
+ //To make sure it stops at a timely manner when you turn off internals
+ breathing_loop.stop()
return TRUE
/// Close the the currently open external (that's EX-ternal) air tank. Returns TRUE if successful.
diff --git a/code/modules/mob/living/carbon/life.dm b/code/modules/mob/living/carbon/life.dm
index 7fb92c26e6183..01531b77d63a9 100644
--- a/code/modules/mob/living/carbon/life.dm
+++ b/code/modules/mob/living/carbon/life.dm
@@ -64,6 +64,7 @@
// Second link in a breath chain, calls [carbon/proc/check_breath()]
/mob/living/carbon/proc/breathe(seconds_per_tick, times_fired)
var/obj/item/organ/internal/lungs = get_organ_slot(ORGAN_SLOT_LUNGS)
+ var/is_on_internals = FALSE
if(SEND_SIGNAL(src, COMSIG_CARBON_ATTEMPT_BREATHE, seconds_per_tick, times_fired) & COMSIG_CARBON_BLOCK_BREATH)
return
@@ -108,15 +109,26 @@
breath = loc.remove_air(breath_moles)
else //Breathe from loc as obj again
+ is_on_internals = TRUE
+
if(isobj(loc))
var/obj/loc_as_obj = loc
loc_as_obj.handle_internal_lifeform(src,0)
- check_breath(breath)
+ if(check_breath(breath) && is_on_internals)
+ try_breathing_sound(breath)
if(breath)
loc.assume_air(breath)
+//Tries to play the carbon a breathing sound when using internals, also invokes check_breath
+/mob/living/carbon/proc/try_breathing_sound(breath)
+ var/should_be_on = canon_client?.prefs?.read_preference(/datum/preference/toggle/sound_breathing)
+ if(should_be_on && !breathing_loop.timer_id)
+ breathing_loop.start()
+ else if(!should_be_on && breathing_loop.timer_id)
+ breathing_loop.stop()
+
/mob/living/carbon/proc/has_smoke_protection()
if(HAS_TRAIT(src, TRAIT_NOBREATH))
return TRUE
diff --git a/code/modules/mob/living/carbon/status_procs.dm b/code/modules/mob/living/carbon/status_procs.dm
index eb1e95ad9db5d..b1ca17337115e 100644
--- a/code/modules/mob/living/carbon/status_procs.dm
+++ b/code/modules/mob/living/carbon/status_procs.dm
@@ -5,19 +5,6 @@
/mob/living/carbon/IsParalyzed(include_stamcrit = TRUE)
return ..() || (include_stamcrit && HAS_TRAIT_FROM(src, TRAIT_INCAPACITATED, STAMINA))
-/mob/living/carbon/proc/enter_stamcrit()
- if(HAS_TRAIT_FROM(src, TRAIT_INCAPACITATED, STAMINA)) //Already in stamcrit
- return
- if(check_stun_immunity(CANKNOCKDOWN))
- return
- if (SEND_SIGNAL(src, COMSIG_CARBON_ENTER_STAMCRIT) & STAMCRIT_CANCELLED)
- return
-
- to_chat(src, span_notice("You're too exhausted to keep going..."))
- add_traits(list(TRAIT_INCAPACITATED, TRAIT_IMMOBILIZED, TRAIT_FLOORED), STAMINA)
- if(getStaminaLoss() < 120) // Puts you a little further into the initial stamcrit, makes stamcrit harder to outright counter with chems.
- adjustStaminaLoss(30, FALSE)
-
/mob/living/carbon/adjust_disgust(amount, max = DISGUST_LEVEL_MAXEDOUT)
disgust = clamp(disgust + amount, 0, max)
diff --git a/code/modules/mob/living/living_defense.dm b/code/modules/mob/living/living_defense.dm
index 3ee43801a7621..d3dfa7e55f605 100644
--- a/code/modules/mob/living/living_defense.dm
+++ b/code/modules/mob/living/living_defense.dm
@@ -411,7 +411,7 @@
if(!user.get_bodypart(BODY_ZONE_HEAD))
return FALSE
- if(user.is_muzzled() || user.is_mouth_covered(ITEM_SLOT_MASK))
+ if(user.is_mouth_covered(ITEM_SLOT_MASK))
to_chat(user, span_warning("You can't bite with your mouth covered!"))
return FALSE
@@ -659,7 +659,7 @@
playsound(target, 'sound/effects/glassbash.ogg', 50, TRUE, -1)
else
do_attack_animation(target, ATTACK_EFFECT_DISARM)
- playsound(target, 'sound/weapons/thudswoosh.ogg', 50, TRUE, -1)
+ playsound(target, 'sound/weapons/shove.ogg', 50, TRUE, -1)
if (ishuman(target) && isnull(weapon))
var/mob/living/carbon/human/human_target = target
human_target.w_uniform?.add_fingerprint(src)
@@ -700,6 +700,7 @@
return
if((shove_flags & SHOVE_BLOCKED) && !(shove_flags & (SHOVE_KNOCKDOWN_BLOCKED|SHOVE_CAN_KICK_SIDE)))
target.Knockdown(SHOVE_KNOCKDOWN_SOLID)
+ target.apply_status_effect(/datum/status_effect/next_shove_stuns)
target.visible_message(span_danger("[name] shoves [target.name], knocking [target.p_them()] down!"),
span_userdanger("You're knocked down from a shove by [name]!"), span_hear("You hear aggressive shuffling followed by a loud thud!"), COMBAT_MESSAGE_RANGE, src)
to_chat(src, span_danger("You shove [target.name], knocking [target.p_them()] down!"))
@@ -708,6 +709,7 @@
if(shove_flags & SHOVE_CAN_KICK_SIDE) //KICK HIM IN THE NUTS
target.Paralyze(SHOVE_CHAIN_PARALYZE)
+ target.apply_status_effect(/datum/status_effect/no_side_kick)
target.visible_message(span_danger("[name] kicks [target.name] onto [target.p_their()] side!"),
span_userdanger("You're kicked onto your side by [name]!"), span_hear("You hear aggressive shuffling followed by a loud thud!"), COMBAT_MESSAGE_RANGE, src)
to_chat(src, span_danger("You kick [target.name] onto [target.p_their()] side!"))
@@ -720,10 +722,8 @@
//Take their lunch money
var/target_held_item = target.get_active_held_item()
var/append_message = weapon ? " with [weapon]" : ""
- if(!is_type_in_typecache(target_held_item, GLOB.shove_disarming_types)) //It's too expensive we'll get caught
- target_held_item = null
-
- if(target_held_item && target.get_timed_status_effect_duration(/datum/status_effect/staggered))
+ // If it's in our typecache, they're staggered and it exists, disarm. If they're knocked down, disarm too.
+ if(target_held_item && target.get_timed_status_effect_duration(/datum/status_effect/staggered) && is_type_in_typecache(target_held_item, GLOB.shove_disarming_types) || target_held_item && target.body_position == LYING_DOWN)
target.dropItemToGround(target_held_item)
append_message = "causing [target.p_them()] to drop [target_held_item]"
target.visible_message(span_danger("[target.name] drops \the [target_held_item]!"),
diff --git a/code/modules/mob/living/living_defines.dm b/code/modules/mob/living/living_defines.dm
index 0b844a2362afb..6ec18d0d5392c 100644
--- a/code/modules/mob/living/living_defines.dm
+++ b/code/modules/mob/living/living_defines.dm
@@ -3,6 +3,7 @@
hud_possible = list(HEALTH_HUD,STATUS_HUD,ANTAG_HUD)
pressure_resistance = 10
hud_type = /datum/hud/living
+ interaction_flags_click = ALLOW_RESTING
interaction_flags_mouse_drop = ALLOW_RESTING
///Tracks the current size of the mob in relation to its original size. Use update_transform(resize) to change it.
diff --git a/code/modules/mob/living/silicon/ai/vox_sounds.dm b/code/modules/mob/living/silicon/ai/vox_sounds.dm
index dacb1eeb8131c..d69bb2e1cc3b8 100644
--- a/code/modules/mob/living/silicon/ai/vox_sounds.dm
+++ b/code/modules/mob/living/silicon/ai/vox_sounds.dm
@@ -8,12 +8,15 @@
// For vim
// :%s/\(\(.*\)\.ogg\)/"\2" = 'sound\/vox_fem\/\1',/g
GLOBAL_LIST_INIT(vox_sounds, list(
- "abduction" = 'sound/vox_fem/abduction.ogg',
"," = 'sound/vox_fem/,.ogg',
"." = 'sound/vox_fem/..ogg',
"a" = 'sound/vox_fem/a.ogg',
+ "abduction" = 'sound/vox_fem/abduction.ogg',
"abortions" = 'sound/vox_fem/abortions.ogg',
"above" = 'sound/vox_fem/above.ogg',
+ "absorb" = 'sound/vox_fem/absorb.ogg',
+ "absorbed" = 'sound/vox_fem/absorbed.ogg',
+ "absorbing" = 'sound/vox_fem/absorbing.ogg',
"abstain" = 'sound/vox_fem/abstain.ogg',
"accelerating" = 'sound/vox_fem/accelerating.ogg',
"accelerator" = 'sound/vox_fem/accelerator.ogg',
@@ -26,11 +29,17 @@ GLOBAL_LIST_INIT(vox_sounds, list(
"across" = 'sound/vox_fem/across.ogg',
"activate" = 'sound/vox_fem/activate.ogg',
"activated" = 'sound/vox_fem/activated.ogg',
+ "activating" = 'sound/vox_fem/activating.ogg',
+ "activation" = 'sound/vox_fem/activation.ogg',
+ "active" = 'sound/vox_fem/active.ogg',
"activity" = 'sound/vox_fem/activity.ogg',
"adios" = 'sound/vox_fem/adios.ogg',
"administration" = 'sound/vox_fem/administration.ogg',
"advanced" = 'sound/vox_fem/advanced.ogg',
"advised" = 'sound/vox_fem/advised.ogg',
+ "affect" = 'sound/vox_fem/affect.ogg',
+ "affected" = 'sound/vox_fem/affected.ogg',
+ "affecting" = 'sound/vox_fem/affecting.ogg',
"aft" = 'sound/vox_fem/aft.ogg',
"after" = 'sound/vox_fem/after.ogg',
"agent" = 'sound/vox_fem/agent.ogg',
@@ -40,44 +49,59 @@ GLOBAL_LIST_INIT(vox_sounds, list(
"alarm" = 'sound/vox_fem/alarm.ogg',
"alarmed" = 'sound/vox_fem/alarmed.ogg',
"alarming" = 'sound/vox_fem/alarming.ogg',
+ "alcohol" = 'sound/vox_fem/alcohol.ogg',
"alert" = 'sound/vox_fem/alert.ogg',
"alerted" = 'sound/vox_fem/alerted.ogg',
"alerting" = 'sound/vox_fem/alerting.ogg',
"alien" = 'sound/vox_fem/alien.ogg',
+ "align" = 'sound/vox_fem/align.ogg',
"aligned" = 'sound/vox_fem/aligned.ogg',
"all" = 'sound/vox_fem/all.ogg',
+ "allow" = 'sound/vox_fem/allow.ogg',
+ "alongside" = 'sound/vox_fem/alongside.ogg',
"alpha" = 'sound/vox_fem/alpha.ogg',
"also" = 'sound/vox_fem/also.ogg',
"am" = 'sound/vox_fem/am.ogg',
"amigo" = 'sound/vox_fem/amigo.ogg',
"ammunition" = 'sound/vox_fem/ammunition.ogg',
+ "amount" = 'sound/vox_fem/amount.ogg',
"an" = 'sound/vox_fem/an.ogg',
"and" = 'sound/vox_fem/and.ogg',
"animal" = 'sound/vox_fem/animal.ogg',
+ "annihilate" = 'sound/vox_fem/annihilate.ogg',
+ "annihilated" = 'sound/vox_fem/annihilated.ogg',
+ "annihilating" = 'sound/vox_fem/annihilating.ogg',
+ "annihilation" = 'sound/vox_fem/annihilation.ogg',
"announcement" = 'sound/vox_fem/announcement.ogg',
"anomalous" = 'sound/vox_fem/anomalous.ogg',
"answer" = 'sound/vox_fem/answer.ogg',
"antenna" = 'sound/vox_fem/antenna.ogg',
+ "anti-noblium" = 'sound/vox_fem/anti-noblium.ogg',
"any" = 'sound/vox_fem/any.ogg',
"apc" = 'sound/vox_fem/apc.ogg',
"apprehend" = 'sound/vox_fem/apprehend.ogg',
"approach" = 'sound/vox_fem/approach.ogg',
+ "arc" = 'sound/vox_fem/arc.ogg',
+ "arcs" = 'sound/vox_fem/arcs.ogg',
"are" = 'sound/vox_fem/are.ogg',
"area" = 'sound/vox_fem/area.ogg',
"arm" = 'sound/vox_fem/arm.ogg',
"armed" = 'sound/vox_fem/armed.ogg',
"armor" = 'sound/vox_fem/armor.ogg',
"armory" = 'sound/vox_fem/armory.ogg',
+ "around" = 'sound/vox_fem/around.ogg',
"array" = 'sound/vox_fem/array.ogg',
"arrest" = 'sound/vox_fem/arrest.ogg',
"artillery" = 'sound/vox_fem/artillery.ogg',
"asimov" = 'sound/vox_fem/asimov.ogg',
+ "ask" = 'sound/vox_fem/ask.ogg',
"ass" = 'sound/vox_fem/ass.ogg',
"asshole" = 'sound/vox_fem/asshole.ogg',
"assholes" = 'sound/vox_fem/assholes.ogg',
"assistance" = 'sound/vox_fem/assistance.ogg',
"assistant" = 'sound/vox_fem/assistant.ogg',
"at" = 'sound/vox_fem/at.ogg',
+ "ate" = 'sound/vox_fem/ate.ogg',
"atmosphere" = 'sound/vox_fem/atmosphere.ogg',
"atmospheric" = 'sound/vox_fem/atmospheric.ogg',
"atmospherics" = 'sound/vox_fem/atmospherics.ogg',
@@ -101,10 +125,14 @@ GLOBAL_LIST_INIT(vox_sounds, list(
"base" = 'sound/vox_fem/base.ogg',
"bay" = 'sound/vox_fem/bay.ogg',
"be" = 'sound/vox_fem/be.ogg',
+ "beaker" = 'sound/vox_fem/beaker.ogg',
"beam" = 'sound/vox_fem/beam.ogg',
"been" = 'sound/vox_fem/been.ogg',
"beep" = 'sound/vox_fem/beep.ogg',
"before" = 'sound/vox_fem/before.ogg',
+ "began" = 'sound/vox_fem/began.ogg',
+ "begin" = 'sound/vox_fem/begin.ogg',
+ "begins" = 'sound/vox_fem/begins.ogg',
"below" = 'sound/vox_fem/below.ogg',
"beside" = 'sound/vox_fem/beside.ogg',
"beware" = 'sound/vox_fem/beware.ogg',
@@ -133,6 +161,7 @@ GLOBAL_LIST_INIT(vox_sounds, list(
"bone" = 'sound/vox_fem/bone.ogg',
"botanist" = 'sound/vox_fem/botanist.ogg',
"botany" = 'sound/vox_fem/botany.ogg',
+ "bottle" = 'sound/vox_fem/bottle.ogg',
"bottom" = 'sound/vox_fem/bottom.ogg',
"bravo" = 'sound/vox_fem/bravo.ogg',
"breach" = 'sound/vox_fem/breach.ogg',
@@ -140,6 +169,11 @@ GLOBAL_LIST_INIT(vox_sounds, list(
"break" = 'sound/vox_fem/break.ogg',
"bridge" = 'sound/vox_fem/bridge.ogg',
"brig" = 'sound/vox_fem/brig.ogg',
+ "broke" = 'sound/vox_fem/broke.ogg',
+ "broken" = 'sound/vox_fem/broken.ogg',
+ "bump" = 'sound/vox_fem/bump.ogg',
+ "bumped" = 'sound/vox_fem/bumped.ogg',
+ "bumps" = 'sound/vox_fem/bumps.ogg',
"bust" = 'sound/vox_fem/bust.ogg',
"but" = 'sound/vox_fem/but.ogg',
"button" = 'sound/vox_fem/button.ogg',
@@ -148,6 +182,7 @@ GLOBAL_LIST_INIT(vox_sounds, list(
"cable" = 'sound/vox_fem/cable.ogg',
"call" = 'sound/vox_fem/call.ogg',
"called" = 'sound/vox_fem/called.ogg',
+ "can" = 'sound/vox_fem/can.ogg',
"canal" = 'sound/vox_fem/canal.ogg',
"canister" = 'sound/vox_fem/canister.ogg',
"cap" = 'sound/vox_fem/cap.ogg',
@@ -155,7 +190,12 @@ GLOBAL_LIST_INIT(vox_sounds, list(
"capture" = 'sound/vox_fem/capture.ogg',
"carbon" = 'sound/vox_fem/carbon.ogg',
"cargo" = 'sound/vox_fem/cargo.ogg',
+ "cascade" = 'sound/vox_fem/cascade.ogg',
"cat" = 'sound/vox_fem/cat.ogg',
+ "cause" = 'sound/vox_fem/cause.ogg',
+ "caused" = 'sound/vox_fem/caused.ogg',
+ "causes" = 'sound/vox_fem/causes.ogg',
+ "causing" = 'sound/vox_fem/causing.ogg',
"ce" = 'sound/vox_fem/ce.ogg',
"cease" = 'sound/vox_fem/cease.ogg',
"ceiling" = 'sound/vox_fem/ceiling.ogg',
@@ -171,6 +211,7 @@ GLOBAL_LIST_INIT(vox_sounds, list(
"changeling" = 'sound/vox_fem/changeling.ogg',
"chapel" = 'sound/vox_fem/chapel.ogg',
"chaplain" = 'sound/vox_fem/chaplain.ogg',
+ "charge" = 'sound/vox_fem/charge.ogg',
"charlie" = 'sound/vox_fem/charlie.ogg',
"check" = 'sound/vox_fem/check.ogg',
"checkpoint" = 'sound/vox_fem/checkpoint.ogg',
@@ -185,35 +226,51 @@ GLOBAL_LIST_INIT(vox_sounds, list(
"clear" = 'sound/vox_fem/clear.ogg',
"clearance" = 'sound/vox_fem/clearance.ogg',
"clockwork" = 'sound/vox_fem/clockwork.ogg',
+ "clog" = 'sound/vox_fem/clog.ogg',
"close" = 'sound/vox_fem/close.ogg',
"closed" = 'sound/vox_fem/closed.ogg',
"closing" = 'sound/vox_fem/closing.ogg',
+ "clothing" = 'sound/vox_fem/clothing.ogg',
"clown" = 'sound/vox_fem/clown.ogg',
"clowning" = 'sound/vox_fem/clowning.ogg',
"cmo" = 'sound/vox_fem/cmo.ogg',
"code" = 'sound/vox_fem/code.ogg',
"coded" = 'sound/vox_fem/coded.ogg',
+ "coil" = 'sound/vox_fem/coil.ogg',
+ "coils" = 'sound/vox_fem/coils.ogg',
"cold" = 'sound/vox_fem/cold.ogg',
"collider" = 'sound/vox_fem/collider.ogg',
+ "combat" = 'sound/vox_fem/combat.ogg',
+ "combatant" = 'sound/vox_fem/combatant.ogg',
"come" = 'sound/vox_fem/come.ogg',
"command" = 'sound/vox_fem/command.ogg',
"communication" = 'sound/vox_fem/communication.ogg',
+ "complete" = 'sound/vox_fem/complete.ogg',
+ "completed" = 'sound/vox_fem/completed.ogg',
+ "completion" = 'sound/vox_fem/completion.ogg',
"complex" = 'sound/vox_fem/complex.ogg',
"comply" = 'sound/vox_fem/comply.ogg',
"computer" = 'sound/vox_fem/computer.ogg',
"condition" = 'sound/vox_fem/condition.ogg',
+ "conditions" = 'sound/vox_fem/conditions.ogg',
"condom" = 'sound/vox_fem/condom.ogg',
+ "configure" = 'sound/vox_fem/configure.ogg',
+ "configured" = 'sound/vox_fem/configured.ogg',
+ "configuring" = 'sound/vox_fem/configuring.ogg',
"confirmed" = 'sound/vox_fem/confirmed.ogg',
"connor" = 'sound/vox_fem/connor.ogg',
"console" = 'sound/vox_fem/console.ogg',
"console2" = 'sound/vox_fem/console2.ogg',
"construct" = 'sound/vox_fem/construct.ogg',
+ "container" = 'sound/vox_fem/container.ogg',
"containment" = 'sound/vox_fem/containment.ogg',
"contamination" = 'sound/vox_fem/contamination.ogg',
"contraband" = 'sound/vox_fem/contraband.ogg',
"control" = 'sound/vox_fem/control.ogg',
"cook" = 'sound/vox_fem/cook.ogg',
+ "cool" = 'sound/vox_fem/cool.ogg',
"coolant" = 'sound/vox_fem/coolant.ogg',
+ "cooling" = 'sound/vox_fem/cooling.ogg',
"coomer" = 'sound/vox_fem/coomer.ogg',
"core" = 'sound/vox_fem/core.ogg',
"corgi" = 'sound/vox_fem/corgi.ogg',
@@ -221,10 +278,15 @@ GLOBAL_LIST_INIT(vox_sounds, list(
"correct" = 'sound/vox_fem/correct.ogg',
"corridor" = 'sound/vox_fem/corridor.ogg',
"corridors" = 'sound/vox_fem/corridors.ogg',
+ "could" = 'sound/vox_fem/could.ogg',
+ "couldnt" = 'sound/vox_fem/couldnt.ogg',
+ "countdown" = 'sound/vox_fem/countdown.ogg',
"coward" = 'sound/vox_fem/coward.ogg',
"cowards" = 'sound/vox_fem/cowards.ogg',
"crate" = 'sound/vox_fem/crate.ogg',
+ "create" = 'sound/vox_fem/create.ogg',
"created" = 'sound/vox_fem/created.ogg',
+ "creating" = 'sound/vox_fem/creating.ogg',
"creature" = 'sound/vox_fem/creature.ogg',
"crew" = 'sound/vox_fem/crew.ogg',
"critical" = 'sound/vox_fem/critical.ogg',
@@ -251,9 +313,12 @@ GLOBAL_LIST_INIT(vox_sounds, list(
"deeoo" = 'sound/vox_fem/deeoo.ogg',
"defense" = 'sound/vox_fem/defense.ogg',
"degrees" = 'sound/vox_fem/degrees.ogg',
+ "delaminating" = 'sound/vox_fem/delaminating.ogg',
+ "delamination" = 'sound/vox_fem/delamination.ogg',
"delta" = 'sound/vox_fem/delta.ogg',
"demon" = 'sound/vox_fem/demon.ogg',
"denied" = 'sound/vox_fem/denied.ogg',
+ "deny" = 'sound/vox_fem/deny.ogg',
"departures" = 'sound/vox_fem/departures.ogg',
"deploy" = 'sound/vox_fem/deploy.ogg',
"deployed" = 'sound/vox_fem/deployed.ogg',
@@ -263,7 +328,9 @@ GLOBAL_LIST_INIT(vox_sounds, list(
"destroyed" = 'sound/vox_fem/destroyed.ogg',
"destruction" = 'sound/vox_fem/destruction.ogg',
"detain" = 'sound/vox_fem/detain.ogg',
+ "detect" = 'sound/vox_fem/detect.ogg',
"detected" = 'sound/vox_fem/detected.ogg',
+ "detecting" = 'sound/vox_fem/detecting.ogg',
"detective" = 'sound/vox_fem/detective.ogg',
"detonation" = 'sound/vox_fem/detonation.ogg',
"device" = 'sound/vox_fem/device.ogg',
@@ -271,8 +338,10 @@ GLOBAL_LIST_INIT(vox_sounds, list(
"did" = 'sound/vox_fem/did.ogg',
"die" = 'sound/vox_fem/die.ogg',
"died" = 'sound/vox_fem/died.ogg',
+ "different" = 'sound/vox_fem/different.ogg',
"dimensional" = 'sound/vox_fem/dimensional.ogg',
"dioxide" = 'sound/vox_fem/dioxide.ogg',
+ "direct" = 'sound/vox_fem/direct.ogg',
"director" = 'sound/vox_fem/director.ogg',
"dirt" = 'sound/vox_fem/dirt.ogg',
"disabled" = 'sound/vox_fem/disabled.ogg',
@@ -286,31 +355,47 @@ GLOBAL_LIST_INIT(vox_sounds, list(
"do" = 'sound/vox_fem/do.ogg',
"doctor" = 'sound/vox_fem/doctor.ogg',
"dog" = 'sound/vox_fem/dog.ogg',
+ "dont" = 'sound/vox_fem/dont.ogg',
"doomsday" = 'sound/vox_fem/doomsday.ogg',
"doop" = 'sound/vox_fem/doop.ogg',
"door" = 'sound/vox_fem/door.ogg',
"dormitory" = 'sound/vox_fem/dormitory.ogg',
"dot" = 'sound/vox_fem/dot.ogg',
+ "double" = 'sound/vox_fem/double.ogg',
"down" = 'sound/vox_fem/down.ogg',
+ "dress" = 'sound/vox_fem/dress.ogg',
+ "dressed" = 'sound/vox_fem/dressed.ogg',
+ "dressing" = 'sound/vox_fem/dressing.ogg',
"drone" = 'sound/vox_fem/drone.ogg',
"dual" = 'sound/vox_fem/dual.ogg',
"duct" = 'sound/vox_fem/duct.ogg',
"e" = 'sound/vox_fem/e.ogg',
+ "easily" = 'sound/vox_fem/easily.ogg',
"east" = 'sound/vox_fem/east.ogg',
+ "eat" = 'sound/vox_fem/eat.ogg',
+ "eaten" = 'sound/vox_fem/eaten.ogg',
"echo" = 'sound/vox_fem/echo.ogg',
"ed" = 'sound/vox_fem/ed.ogg',
+ "education" = 'sound/vox_fem/education.ogg',
"effect" = 'sound/vox_fem/effect.ogg',
+ "effects" = 'sound/vox_fem/effects.ogg',
"egress" = 'sound/vox_fem/egress.ogg',
"eight" = 'sound/vox_fem/eight.ogg',
"eighteen" = 'sound/vox_fem/eighteen.ogg',
"eighty" = 'sound/vox_fem/eighty.ogg',
"electric" = 'sound/vox_fem/electric.ogg',
+ "electrical" = 'sound/vox_fem/electrical.ogg',
"electromagnetic" = 'sound/vox_fem/electromagnetic.ogg',
"elevator" = 'sound/vox_fem/elevator.ogg',
"eleven" = 'sound/vox_fem/eleven.ogg',
"eliminate" = 'sound/vox_fem/eliminate.ogg',
"emergency" = 'sound/vox_fem/emergency.ogg',
+ "emitted" = 'sound/vox_fem/emitted.ogg',
+ "emitter" = 'sound/vox_fem/emitter.ogg',
+ "emitting" = 'sound/vox_fem/emitting.ogg',
"enabled" = 'sound/vox_fem/enabled.ogg',
+ "end" = 'sound/vox_fem/end.ogg',
+ "ends" = 'sound/vox_fem/ends.ogg',
"energy" = 'sound/vox_fem/energy.ogg',
"engage" = 'sound/vox_fem/engage.ogg',
"engaged" = 'sound/vox_fem/engaged.ogg',
@@ -318,6 +403,7 @@ GLOBAL_LIST_INIT(vox_sounds, list(
"engineer" = 'sound/vox_fem/engineer.ogg',
"engineering" = 'sound/vox_fem/engineering.ogg',
"enormous" = 'sound/vox_fem/enormous.ogg',
+ "enough" = 'sound/vox_fem/enough.ogg',
"enter" = 'sound/vox_fem/enter.ogg',
"entity" = 'sound/vox_fem/entity.ogg',
"entry" = 'sound/vox_fem/entry.ogg',
@@ -329,7 +415,11 @@ GLOBAL_LIST_INIT(vox_sounds, list(
"ethereal" = 'sound/vox_fem/ethereal.ogg',
"eva" = 'sound/vox_fem/eva.ogg',
"evacuate" = 'sound/vox_fem/evacuate.ogg',
+ "even" = 'sound/vox_fem/even.ogg',
"ever" = 'sound/vox_fem/ever.ogg',
+ "every" = 'sound/vox_fem/every.ogg',
+ "everybody" = 'sound/vox_fem/everybody.ogg',
+ "everyone" = 'sound/vox_fem/everyone.ogg',
"exchange" = 'sound/vox_fem/exchange.ogg',
"execute" = 'sound/vox_fem/execute.ogg',
"exit" = 'sound/vox_fem/exit.ogg',
@@ -337,12 +427,16 @@ GLOBAL_LIST_INIT(vox_sounds, list(
"experiment" = 'sound/vox_fem/experiment.ogg',
"experimental" = 'sound/vox_fem/experimental.ogg',
"explode" = 'sound/vox_fem/explode.ogg',
+ "exploded" = 'sound/vox_fem/exploded.ogg',
+ "exploding" = 'sound/vox_fem/exploding.ogg',
"explosion" = 'sound/vox_fem/explosion.ogg',
"explosive" = 'sound/vox_fem/explosive.ogg',
"exposure" = 'sound/vox_fem/exposure.ogg',
"exterminate" = 'sound/vox_fem/exterminate.ogg',
+ "external" = 'sound/vox_fem/external.ogg',
"extinguish" = 'sound/vox_fem/extinguish.ogg',
"extinguisher" = 'sound/vox_fem/extinguisher.ogg',
+ "extra" = 'sound/vox_fem/extra.ogg',
"extreme" = 'sound/vox_fem/extreme.ogg',
"f" = 'sound/vox_fem/f.ogg',
"facility" = 'sound/vox_fem/facility.ogg',
@@ -354,12 +448,19 @@ GLOBAL_LIST_INIT(vox_sounds, list(
"farthest" = 'sound/vox_fem/farthest.ogg',
"fast" = 'sound/vox_fem/fast.ogg',
"fauna" = 'sound/vox_fem/fauna.ogg',
+ "feature" = 'sound/vox_fem/feature.ogg',
+ "featured" = 'sound/vox_fem/featured.ogg',
+ "features" = 'sound/vox_fem/features.ogg',
+ "featuring" = 'sound/vox_fem/featuring.ogg',
"feet" = 'sound/vox_fem/feet.ogg',
"felinid" = 'sound/vox_fem/felinid.ogg',
+ "few" = 'sound/vox_fem/few.ogg',
"field" = 'sound/vox_fem/field.ogg',
"fifteen" = 'sound/vox_fem/fifteen.ogg',
"fifth" = 'sound/vox_fem/fifth.ogg',
"fifty" = 'sound/vox_fem/fifty.ogg',
+ "filter" = 'sound/vox_fem/filter.ogg',
+ "filters" = 'sound/vox_fem/filters.ogg',
"final" = 'sound/vox_fem/final.ogg',
"fine" = 'sound/vox_fem/fine.ogg',
"fire" = 'sound/vox_fem/fire.ogg',
@@ -370,6 +471,7 @@ GLOBAL_LIST_INIT(vox_sounds, list(
"floor" = 'sound/vox_fem/floor.ogg',
"flyman" = 'sound/vox_fem/flyman.ogg',
"fool" = 'sound/vox_fem/fool.ogg',
+ "foolish" = 'sound/vox_fem/foolish.ogg',
"for" = 'sound/vox_fem/for.ogg',
"forbidden" = 'sound/vox_fem/forbidden.ogg',
"force" = 'sound/vox_fem/force.ogg',
@@ -389,6 +491,7 @@ GLOBAL_LIST_INIT(vox_sounds, list(
"freeze" = 'sound/vox_fem/freeze.ogg',
"freezer" = 'sound/vox_fem/freezer.ogg',
"freezing" = 'sound/vox_fem/freezing.ogg',
+ "freon" = 'sound/vox_fem/freon.ogg',
"from" = 'sound/vox_fem/from.ogg',
"front" = 'sound/vox_fem/front.ogg',
"froze" = 'sound/vox_fem/froze.ogg',
@@ -399,9 +502,19 @@ GLOBAL_LIST_INIT(vox_sounds, list(
"fuel" = 'sound/vox_fem/fuel.ogg',
"g" = 'sound/vox_fem/g.ogg',
"gas" = 'sound/vox_fem/gas.ogg',
+ "gases" = 'sound/vox_fem/gases.ogg',
+ "gave" = 'sound/vox_fem/gave.ogg',
+ "gear" = 'sound/vox_fem/gear.ogg',
+ "geared" = 'sound/vox_fem/geared.ogg',
+ "gearing" = 'sound/vox_fem/gearing.ogg',
+ "generate" = 'sound/vox_fem/generate.ogg',
+ "generated" = 'sound/vox_fem/generated.ogg',
+ "generating" = 'sound/vox_fem/generating.ogg',
"generator" = 'sound/vox_fem/generator.ogg',
"geneticist" = 'sound/vox_fem/geneticist.ogg',
"get" = 'sound/vox_fem/get.ogg',
+ "give" = 'sound/vox_fem/give.ogg',
+ "given" = 'sound/vox_fem/given.ogg',
"glory" = 'sound/vox_fem/glory.ogg',
"go" = 'sound/vox_fem/go.ogg',
"god" = 'sound/vox_fem/god.ogg',
@@ -428,17 +541,29 @@ GLOBAL_LIST_INIT(vox_sounds, list(
"had" = 'sound/vox_fem/had.ogg',
"hall" = 'sound/vox_fem/hall.ogg',
"hallway" = 'sound/vox_fem/hallway.ogg',
+ "halon" = 'sound/vox_fem/halon.ogg',
"handling" = 'sound/vox_fem/handling.ogg',
"hangar" = 'sound/vox_fem/hangar.ogg',
+ "hard" = 'sound/vox_fem/hard.ogg',
+ "hardly" = 'sound/vox_fem/hardly.ogg',
"harm" = 'sound/vox_fem/harm.ogg',
"harmful" = 'sound/vox_fem/harmful.ogg',
+ "harness" = 'sound/vox_fem/harness.ogg',
+ "harnessed" = 'sound/vox_fem/harnessed.ogg',
+ "harnessing" = 'sound/vox_fem/harnessing.ogg',
"has" = 'sound/vox_fem/has.ogg',
"have" = 'sound/vox_fem/have.ogg',
"hazard" = 'sound/vox_fem/hazard.ogg',
"he" = 'sound/vox_fem/he.ogg',
"head" = 'sound/vox_fem/head.ogg',
+ "heal" = 'sound/vox_fem/heal.ogg',
+ "healed" = 'sound/vox_fem/healed.ogg',
+ "healing" = 'sound/vox_fem/healing.ogg',
+ "healium" = 'sound/vox_fem/healium.ogg',
"health" = 'sound/vox_fem/health.ogg',
"heat" = 'sound/vox_fem/heat.ogg',
+ "heated" = 'sound/vox_fem/heated.ogg',
+ "heating" = 'sound/vox_fem/heating.ogg',
"helicopter" = 'sound/vox_fem/helicopter.ogg',
"helium" = 'sound/vox_fem/helium.ogg',
"hello" = 'sound/vox_fem/hello.ogg',
@@ -468,7 +593,9 @@ GLOBAL_LIST_INIT(vox_sounds, list(
"hunger" = 'sound/vox_fem/hunger.ogg',
"hurt" = 'sound/vox_fem/hurt.ogg',
"hydro" = 'sound/vox_fem/hydro.ogg',
+ "hydrogen" = 'sound/vox_fem/hydrogen.ogg',
"hydroponics" = 'sound/vox_fem/hydroponics.ogg',
+ "hyper-noblium" = 'sound/vox_fem/hyper-noblium.ogg',
"i" = 'sound/vox_fem/i.ogg',
"ian" = 'sound/vox_fem/ian.ogg',
"idiot" = 'sound/vox_fem/idiot.ogg',
@@ -482,26 +609,33 @@ GLOBAL_LIST_INIT(vox_sounds, list(
"in" = 'sound/vox_fem/in.ogg',
"inches" = 'sound/vox_fem/inches.ogg',
"india" = 'sound/vox_fem/india.ogg',
+ "inert" = 'sound/vox_fem/inert.ogg',
"ing" = 'sound/vox_fem/ing.ogg',
"inoperative" = 'sound/vox_fem/inoperative.ogg',
"inside" = 'sound/vox_fem/inside.ogg',
"inspection" = 'sound/vox_fem/inspection.ogg',
"inspector" = 'sound/vox_fem/inspector.ogg',
"interchange" = 'sound/vox_fem/interchange.ogg',
+ "internal" = 'sound/vox_fem/internal.ogg',
"internals" = 'sound/vox_fem/internals.ogg',
"intruder" = 'sound/vox_fem/intruder.ogg',
"invalid" = 'sound/vox_fem/invalid.ogg',
"invalidate" = 'sound/vox_fem/invalidate.ogg',
"invasion" = 'sound/vox_fem/invasion.ogg',
+ "irradiate" = 'sound/vox_fem/irradiate.ogg',
"is" = 'sound/vox_fem/is.ogg',
"it" = 'sound/vox_fem/it.ogg',
+ "its" = 'sound/vox_fem/its.ogg',
"j" = 'sound/vox_fem/j.ogg',
"janitor" = 'sound/vox_fem/janitor.ogg',
"jesus" = 'sound/vox_fem/jesus.ogg',
+ "job" = 'sound/vox_fem/job.ogg',
+ "jobs" = 'sound/vox_fem/jobs.ogg',
"johnson" = 'sound/vox_fem/johnson.ogg',
"jolly" = 'sound/vox_fem/jolly.ogg',
"juliet" = 'sound/vox_fem/juliet.ogg',
"k" = 'sound/vox_fem/k.ogg',
+ "kelvin" = 'sound/vox_fem/kelvin.ogg',
"key" = 'sound/vox_fem/key.ogg',
"kidnapped" = 'sound/vox_fem/kidnapped.ogg',
"kidnapping" = 'sound/vox_fem/kidnapping.ogg',
@@ -536,7 +670,10 @@ GLOBAL_LIST_INIT(vox_sounds, list(
"light" = 'sound/vox_fem/light.ogg',
"lightbulb" = 'sound/vox_fem/lightbulb.ogg',
"lima" = 'sound/vox_fem/lima.ogg',
+ "limit" = 'sound/vox_fem/limit.ogg',
+ "limited" = 'sound/vox_fem/limited.ogg',
"liquid" = 'sound/vox_fem/liquid.ogg',
+ "list" = 'sound/vox_fem/list.ogg',
"live" = 'sound/vox_fem/live.ogg',
"live2" = 'sound/vox_fem/live2.ogg',
"lizard" = 'sound/vox_fem/lizard.ogg',
@@ -559,12 +696,14 @@ GLOBAL_LIST_INIT(vox_sounds, list(
"lusty" = 'sound/vox_fem/lusty.ogg',
"m" = 'sound/vox_fem/m.ogg',
"machine" = 'sound/vox_fem/machine.ogg',
+ "made" = 'sound/vox_fem/made.ogg',
"magic" = 'sound/vox_fem/magic.ogg',
"magnetic" = 'sound/vox_fem/magnetic.ogg',
"main" = 'sound/vox_fem/main.ogg',
"maintainer" = 'sound/vox_fem/maintainer.ogg',
"maintenance" = 'sound/vox_fem/maintenance.ogg',
"major" = 'sound/vox_fem/major.ogg',
+ "making" = 'sound/vox_fem/making.ogg',
"malfunction" = 'sound/vox_fem/malfunction.ogg',
"man" = 'sound/vox_fem/man.ogg',
"many" = 'sound/vox_fem/many.ogg',
@@ -573,15 +712,21 @@ GLOBAL_LIST_INIT(vox_sounds, list(
"maximum" = 'sound/vox_fem/maximum.ogg',
"may" = 'sound/vox_fem/may.ogg',
"me" = 'sound/vox_fem/me.ogg',
+ "mean" = 'sound/vox_fem/mean.ogg',
+ "means" = 'sound/vox_fem/means.ogg',
"meat" = 'sound/vox_fem/meat.ogg',
"medbay" = 'sound/vox_fem/medbay.ogg',
"medical" = 'sound/vox_fem/medical.ogg',
+ "medium" = 'sound/vox_fem/medium.ogg',
"megafauna" = 'sound/vox_fem/megafauna.ogg',
"men" = 'sound/vox_fem/men.ogg',
"mercy" = 'sound/vox_fem/mercy.ogg',
"mesa" = 'sound/vox_fem/mesa.ogg',
+ "meson" = 'sound/vox_fem/meson.ogg',
"message" = 'sound/vox_fem/message.ogg',
"meter" = 'sound/vox_fem/meter.ogg',
+ "method" = 'sound/vox_fem/method.ogg',
+ "miasma" = 'sound/vox_fem/miasma.ogg',
"micro" = 'sound/vox_fem/micro.ogg',
"middle" = 'sound/vox_fem/middle.ogg',
"mike" = 'sound/vox_fem/mike.ogg',
@@ -594,17 +739,22 @@ GLOBAL_LIST_INIT(vox_sounds, list(
"miner" = 'sound/vox_fem/miner.ogg',
"minimum" = 'sound/vox_fem/minimum.ogg',
"minor" = 'sound/vox_fem/minor.ogg',
+ "minute" = 'sound/vox_fem/minute.ogg',
"minutes" = 'sound/vox_fem/minutes.ogg',
"mister" = 'sound/vox_fem/mister.ogg',
+ "mixture" = 'sound/vox_fem/mixture.ogg',
"mode" = 'sound/vox_fem/mode.ogg',
"modification" = 'sound/vox_fem/modification.ogg',
"money" = 'sound/vox_fem/money.ogg',
"monkey" = 'sound/vox_fem/monkey.ogg',
+ "most" = 'sound/vox_fem/most.ogg',
"moth" = 'sound/vox_fem/moth.ogg',
"mothperson" = 'sound/vox_fem/mothperson.ogg',
"motor" = 'sound/vox_fem/motor.ogg',
"motorpool" = 'sound/vox_fem/motorpool.ogg',
"move" = 'sound/vox_fem/move.ogg',
+ "moved" = 'sound/vox_fem/moved.ogg',
+ "moving" = 'sound/vox_fem/moving.ogg',
"multitude" = 'sound/vox_fem/multitude.ogg',
"murder" = 'sound/vox_fem/murder.ogg',
"murderer" = 'sound/vox_fem/murderer.ogg',
@@ -613,7 +763,9 @@ GLOBAL_LIST_INIT(vox_sounds, list(
"mythic" = 'sound/vox_fem/mythic.ogg',
"n" = 'sound/vox_fem/n.ogg',
"nanotrasen" = 'sound/vox_fem/nanotrasen.ogg',
+ "near" = 'sound/vox_fem/near.ogg',
"nearest" = 'sound/vox_fem/nearest.ogg',
+ "nearly" = 'sound/vox_fem/nearly.ogg',
"need" = 'sound/vox_fem/need.ogg',
"never" = 'sound/vox_fem/never.ogg',
"nice" = 'sound/vox_fem/nice.ogg',
@@ -624,16 +776,21 @@ GLOBAL_LIST_INIT(vox_sounds, list(
"nitrogen" = 'sound/vox_fem/nitrogen.ogg',
"no" = 'sound/vox_fem/no.ogg',
"nominal" = 'sound/vox_fem/nominal.ogg',
+ "none" = 'sound/vox_fem/none.ogg',
+ "normal" = 'sound/vox_fem/normal.ogg',
+ "normally" = 'sound/vox_fem/normally.ogg',
"north" = 'sound/vox_fem/north.ogg',
"northeast" = 'sound/vox_fem/northeast.ogg',
"northwest" = 'sound/vox_fem/northwest.ogg',
"not" = 'sound/vox_fem/not.ogg',
+ "notably" = 'sound/vox_fem/notably.ogg',
"november" = 'sound/vox_fem/november.ogg',
"now" = 'sound/vox_fem/now.ogg',
"nuclear" = 'sound/vox_fem/nuclear.ogg',
"nuke" = 'sound/vox_fem/nuke.ogg',
"number" = 'sound/vox_fem/number.ogg',
"o" = 'sound/vox_fem/o.ogg',
+ "object" = 'sound/vox_fem/object.ogg',
"objective" = 'sound/vox_fem/objective.ogg',
"obliterate" = 'sound/vox_fem/obliterate.ogg',
"obliterated" = 'sound/vox_fem/obliterated.ogg',
@@ -660,13 +817,17 @@ GLOBAL_LIST_INIT(vox_sounds, list(
"option" = 'sound/vox_fem/option.ogg',
"or" = 'sound/vox_fem/or.ogg',
"order" = 'sound/vox_fem/order.ogg',
+ "ordered" = 'sound/vox_fem/ordered.ogg',
+ "ordering" = 'sound/vox_fem/ordering.ogg',
"organic" = 'sound/vox_fem/organic.ogg',
"oscar" = 'sound/vox_fem/oscar.ogg',
"out" = 'sound/vox_fem/out.ogg',
+ "output" = 'sound/vox_fem/output.ogg',
"outside" = 'sound/vox_fem/outside.ogg',
"over" = 'sound/vox_fem/over.ogg',
"overload" = 'sound/vox_fem/overload.ogg',
"override" = 'sound/vox_fem/override.ogg',
+ "own" = 'sound/vox_fem/own.ogg',
"oxygen" = 'sound/vox_fem/oxygen.ogg',
"p" = 'sound/vox_fem/p.ogg',
"pacification" = 'sound/vox_fem/pacification.ogg',
@@ -676,6 +837,7 @@ GLOBAL_LIST_INIT(vox_sounds, list(
"panel" = 'sound/vox_fem/panel.ogg',
"panting" = 'sound/vox_fem/panting.ogg',
"pathetic" = 'sound/vox_fem/pathetic.ogg',
+ "pda" = 'sound/vox_fem/pda.ogg',
"percent" = 'sound/vox_fem/percent.ogg',
"perfect" = 'sound/vox_fem/perfect.ogg',
"perhaps" = 'sound/vox_fem/perhaps.ogg',
@@ -690,28 +852,46 @@ GLOBAL_LIST_INIT(vox_sounds, list(
"plasma" = 'sound/vox_fem/plasma.ogg',
"plasmaman" = 'sound/vox_fem/plasmaman.ogg',
"platform" = 'sound/vox_fem/platform.ogg',
+ "plating" = 'sound/vox_fem/plating.ogg',
"plausible" = 'sound/vox_fem/plausible.ogg',
"please" = 'sound/vox_fem/please.ogg',
+ "pluoxium" = 'sound/vox_fem/pluoxium.ogg',
"point" = 'sound/vox_fem/point.ogg',
"port" = 'sound/vox_fem/port.ogg',
"portal" = 'sound/vox_fem/portal.ogg',
+ "portion" = 'sound/vox_fem/portion.ogg',
"possible" = 'sound/vox_fem/possible.ogg',
"power" = 'sound/vox_fem/power.ogg',
+ "powered" = 'sound/vox_fem/powered.ogg',
+ "powering" = 'sound/vox_fem/powering.ogg',
+ "premature" = 'sound/vox_fem/premature.ogg',
+ "prematurely" = 'sound/vox_fem/prematurely.ogg',
"presence" = 'sound/vox_fem/presence.ogg',
"present" = 'sound/vox_fem/present.ogg',
"presents" = 'sound/vox_fem/presents.ogg',
"press" = 'sound/vox_fem/press.ogg',
"pressure" = 'sound/vox_fem/pressure.ogg',
"primary" = 'sound/vox_fem/primary.ogg',
+ "priority" = 'sound/vox_fem/priority.ogg',
"prison" = 'sound/vox_fem/prison.ogg',
"prisoner" = 'sound/vox_fem/prisoner.ogg',
"proceed" = 'sound/vox_fem/proceed.ogg',
"processing" = 'sound/vox_fem/processing.ogg',
"progress" = 'sound/vox_fem/progress.ogg',
+ "projectile" = 'sound/vox_fem/projectile.ogg',
"proper" = 'sound/vox_fem/proper.ogg',
"propulsion" = 'sound/vox_fem/propulsion.ogg',
"prosecute" = 'sound/vox_fem/prosecute.ogg',
+ "protect" = 'sound/vox_fem/protect.ogg',
+ "protected" = 'sound/vox_fem/protected.ogg',
+ "protection" = 'sound/vox_fem/protection.ogg',
"protective" = 'sound/vox_fem/protective.ogg',
+ "proto-nitrate" = 'sound/vox_fem/proto-nitrate.ogg',
+ "pull" = 'sound/vox_fem/pull.ogg',
+ "pulled" = 'sound/vox_fem/pulled.ogg',
+ "pulling" = 'sound/vox_fem/pulling.ogg',
+ "pump" = 'sound/vox_fem/pump.ogg',
+ "pumps" = 'sound/vox_fem/pumps.ogg',
"push" = 'sound/vox_fem/push.ogg',
"put" = 'sound/vox_fem/put.ogg',
"q" = 'sound/vox_fem/q.ogg',
@@ -737,9 +917,14 @@ GLOBAL_LIST_INIT(vox_sounds, list(
"reactor" = 'sound/vox_fem/reactor.ogg',
"red" = 'sound/vox_fem/red.ogg',
"relay" = 'sound/vox_fem/relay.ogg',
+ "release" = 'sound/vox_fem/release.ogg',
"released" = 'sound/vox_fem/released.ogg',
+ "releasing" = 'sound/vox_fem/releasing.ogg',
"remaining" = 'sound/vox_fem/remaining.ogg',
"removal" = 'sound/vox_fem/removal.ogg',
+ "remove" = 'sound/vox_fem/remove.ogg',
+ "removed" = 'sound/vox_fem/removed.ogg',
+ "removing" = 'sound/vox_fem/removing.ogg',
"renegade" = 'sound/vox_fem/renegade.ogg',
"repair" = 'sound/vox_fem/repair.ogg',
"report" = 'sound/vox_fem/report.ogg',
@@ -752,6 +937,9 @@ GLOBAL_LIST_INIT(vox_sounds, list(
"research" = 'sound/vox_fem/research.ogg',
"resevoir" = 'sound/vox_fem/resevoir.ogg',
"resistance" = 'sound/vox_fem/resistance.ogg',
+ "resistant" = 'sound/vox_fem/resistant.ogg',
+ "resisting" = 'sound/vox_fem/resisting.ogg',
+ "resonance" = 'sound/vox_fem/resonance.ogg',
"rest" = 'sound/vox_fem/rest.ogg',
"restoration" = 'sound/vox_fem/restoration.ogg',
"revolution" = 'sound/vox_fem/revolution.ogg',
@@ -770,18 +958,28 @@ GLOBAL_LIST_INIT(vox_sounds, list(
"runtime" = 'sound/vox_fem/runtime.ogg',
"s" = 'sound/vox_fem/s.ogg',
"sabotage" = 'sound/vox_fem/sabotage.ogg',
+ "sabotaged" = 'sound/vox_fem/sabotaged.ogg',
+ "sabotaging" = 'sound/vox_fem/sabotaging.ogg',
"safe" = 'sound/vox_fem/safe.ogg',
"safety" = 'sound/vox_fem/safety.ogg',
"sairhorn" = 'sound/vox_fem/sairhorn.ogg',
+ "same" = 'sound/vox_fem/same.ogg',
"sarah" = 'sound/vox_fem/sarah.ogg',
"sargeant" = 'sound/vox_fem/sargeant.ogg',
"satellite" = 'sound/vox_fem/satellite.ogg',
"save" = 'sound/vox_fem/save.ogg',
+ "saw" = 'sound/vox_fem/saw.ogg',
+ "scan" = 'sound/vox_fem/scan.ogg',
+ "scanned" = 'sound/vox_fem/scanned.ogg',
+ "scanner" = 'sound/vox_fem/scanner.ogg',
+ "scanners" = 'sound/vox_fem/scanners.ogg',
+ "scanning" = 'sound/vox_fem/scanning.ogg',
"scensor" = 'sound/vox_fem/scensor.ogg',
"science" = 'sound/vox_fem/science.ogg',
"scientist" = 'sound/vox_fem/scientist.ogg',
"scream" = 'sound/vox_fem/scream.ogg',
"screen" = 'sound/vox_fem/screen.ogg',
+ "screw" = 'sound/vox_fem/screw.ogg',
"search" = 'sound/vox_fem/search.ogg',
"second" = 'sound/vox_fem/second.ogg',
"secondary" = 'sound/vox_fem/secondary.ogg',
@@ -791,21 +989,32 @@ GLOBAL_LIST_INIT(vox_sounds, list(
"secure" = 'sound/vox_fem/secure.ogg',
"secured" = 'sound/vox_fem/secured.ogg',
"security" = 'sound/vox_fem/security.ogg',
+ "seen" = 'sound/vox_fem/seen.ogg',
"select" = 'sound/vox_fem/select.ogg',
"selected" = 'sound/vox_fem/selected.ogg',
"self" = 'sound/vox_fem/self.ogg',
"sensors" = 'sound/vox_fem/sensors.ogg',
"server" = 'sound/vox_fem/server.ogg',
"service" = 'sound/vox_fem/service.ogg',
+ "set" = 'sound/vox_fem/set.ogg',
"seven" = 'sound/vox_fem/seven.ogg',
"seventeen" = 'sound/vox_fem/seventeen.ogg',
"seventy" = 'sound/vox_fem/seventy.ogg',
+ "sever" = 'sound/vox_fem/sever.ogg',
"severe" = 'sound/vox_fem/severe.ogg',
+ "severed" = 'sound/vox_fem/severed.ogg',
+ "severing" = 'sound/vox_fem/severing.ogg',
"sewage" = 'sound/vox_fem/sewage.ogg',
"sewer" = 'sound/vox_fem/sewer.ogg',
"shaft" = 'sound/vox_fem/shaft.ogg',
+ "shame" = 'sound/vox_fem/shame.ogg',
+ "shameful" = 'sound/vox_fem/shameful.ogg',
+ "shameless" = 'sound/vox_fem/shameless.ogg',
+ "shard" = 'sound/vox_fem/shard.ogg',
"she" = 'sound/vox_fem/she.ogg',
"shield" = 'sound/vox_fem/shield.ogg',
+ "shift" = 'sound/vox_fem/shift.ogg',
+ "shifts" = 'sound/vox_fem/shifts.ogg',
"shipment" = 'sound/vox_fem/shipment.ogg',
"shirt" = 'sound/vox_fem/shirt.ogg',
"shit" = 'sound/vox_fem/shit.ogg',
@@ -820,11 +1029,15 @@ GLOBAL_LIST_INIT(vox_sounds, list(
"shuttle" = 'sound/vox_fem/shuttle.ogg',
"sick" = 'sound/vox_fem/sick.ogg',
"side" = 'sound/vox_fem/side.ogg',
+ "sides" = 'sound/vox_fem/sides.ogg',
"sierra" = 'sound/vox_fem/sierra.ogg',
"sight" = 'sound/vox_fem/sight.ogg',
"silicon" = 'sound/vox_fem/silicon.ogg',
"silo" = 'sound/vox_fem/silo.ogg',
+ "single" = 'sound/vox_fem/single.ogg',
"singularity" = 'sound/vox_fem/singularity.ogg',
+ "siphon" = 'sound/vox_fem/siphon.ogg',
+ "siphoning" = 'sound/vox_fem/siphoning.ogg',
"six" = 'sound/vox_fem/six.ogg',
"sixteen" = 'sound/vox_fem/sixteen.ogg',
"sixty" = 'sound/vox_fem/sixty.ogg',
@@ -837,6 +1050,7 @@ GLOBAL_LIST_INIT(vox_sounds, list(
"sm" = 'sound/vox_fem/sm.ogg',
"small" = 'sound/vox_fem/small.ogg',
"sockmuncher" = 'sound/vox_fem/sockmuncher.ogg',
+ "soft" = 'sound/vox_fem/soft.ogg',
"solar" = 'sound/vox_fem/solar.ogg',
"solars" = 'sound/vox_fem/solars.ogg',
"soldier" = 'sound/vox_fem/soldier.ogg',
@@ -845,16 +1059,23 @@ GLOBAL_LIST_INIT(vox_sounds, list(
"something" = 'sound/vox_fem/something.ogg',
"son" = 'sound/vox_fem/son.ogg',
"sorry" = 'sound/vox_fem/sorry.ogg',
+ "source" = 'sound/vox_fem/source.ogg',
"south" = 'sound/vox_fem/south.ogg',
"southeast" = 'sound/vox_fem/southeast.ogg',
"southwest" = 'sound/vox_fem/southwest.ogg',
"space" = 'sound/vox_fem/space.ogg',
+ "special" = 'sound/vox_fem/special.ogg',
+ "spew" = 'sound/vox_fem/spew.ogg',
"squad" = 'sound/vox_fem/squad.ogg',
"square" = 'sound/vox_fem/square.ogg',
"ss13" = 'sound/vox_fem/ss13.ogg',
"stairway" = 'sound/vox_fem/stairway.ogg',
"starboard" = 'sound/vox_fem/starboard.ogg',
+ "start" = 'sound/vox_fem/start.ogg',
+ "starts" = 'sound/vox_fem/starts.ogg',
"station" = 'sound/vox_fem/station.ogg',
+ "stations" = 'sound/vox_fem/stations.ogg',
+ "stationwide" = 'sound/vox_fem/stationwide.ogg',
"status" = 'sound/vox_fem/status.ogg',
"stay" = 'sound/vox_fem/stay.ogg',
"sterile" = 'sound/vox_fem/sterile.ogg',
@@ -865,9 +1086,12 @@ GLOBAL_LIST_INIT(vox_sounds, list(
"stuck" = 'sound/vox_fem/stuck.ogg',
"sub" = 'sound/vox_fem/sub.ogg',
"subsurface" = 'sound/vox_fem/subsurface.ogg',
+ "such" = 'sound/vox_fem/such.ogg',
"sudden" = 'sound/vox_fem/sudden.ogg',
"suffer" = 'sound/vox_fem/suffer.ogg',
"suit" = 'sound/vox_fem/suit.ogg',
+ "suited" = 'sound/vox_fem/suited.ogg',
+ "super" = 'sound/vox_fem/super.ogg',
"superconducting" = 'sound/vox_fem/superconducting.ogg',
"supercooled" = 'sound/vox_fem/supercooled.ogg',
"supermatter" = 'sound/vox_fem/supermatter.ogg',
@@ -904,6 +1128,7 @@ GLOBAL_LIST_INIT(vox_sounds, list(
"terminate" = 'sound/vox_fem/terminate.ogg',
"terminated" = 'sound/vox_fem/terminated.ogg',
"termination" = 'sound/vox_fem/termination.ogg',
+ "tesla" = 'sound/vox_fem/tesla.ogg',
"test" = 'sound/vox_fem/test.ogg',
"text" = 'sound/vox_fem/text.ogg',
"thank" = 'sound/vox_fem/thank.ogg',
@@ -924,23 +1149,33 @@ GLOBAL_LIST_INIT(vox_sounds, list(
"threat" = 'sound/vox_fem/threat.ogg',
"three" = 'sound/vox_fem/three.ogg',
"through" = 'sound/vox_fem/through.ogg',
+ "tick" = 'sound/vox_fem/tick.ogg',
"tide" = 'sound/vox_fem/tide.ogg',
+ "tile" = 'sound/vox_fem/tile.ogg',
"time" = 'sound/vox_fem/time.ogg',
"tiny" = 'sound/vox_fem/tiny.ogg',
"to" = 'sound/vox_fem/to.ogg',
"top" = 'sound/vox_fem/top.ogg',
"topside" = 'sound/vox_fem/topside.ogg',
"touch" = 'sound/vox_fem/touch.ogg',
+ "touched" = 'sound/vox_fem/touched.ogg',
+ "touching" = 'sound/vox_fem/touching.ogg',
"towards" = 'sound/vox_fem/towards.ogg',
"toxins" = 'sound/vox_fem/toxins.ogg',
"track" = 'sound/vox_fem/track.ogg',
"train" = 'sound/vox_fem/train.ogg',
"traitor" = 'sound/vox_fem/traitor.ogg',
"transportation" = 'sound/vox_fem/transportation.ogg',
+ "trigger" = 'sound/vox_fem/trigger.ogg',
+ "triggered" = 'sound/vox_fem/triggered.ogg',
+ "triggering" = 'sound/vox_fem/triggering.ogg',
+ "triple" = 'sound/vox_fem/triple.ogg',
+ "tritium" = 'sound/vox_fem/tritium.ogg',
"truck" = 'sound/vox_fem/truck.ogg',
"true" = 'sound/vox_fem/true.ogg',
"tunnel" = 'sound/vox_fem/tunnel.ogg',
"turn" = 'sound/vox_fem/turn.ogg',
+ "turned" = 'sound/vox_fem/turned.ogg',
"turret" = 'sound/vox_fem/turret.ogg',
"twelve" = 'sound/vox_fem/twelve.ogg',
"twenty" = 'sound/vox_fem/twenty.ogg',
@@ -952,10 +1187,13 @@ GLOBAL_LIST_INIT(vox_sounds, list(
"unauthorized" = 'sound/vox_fem/unauthorized.ogg',
"under" = 'sound/vox_fem/under.ogg',
"uniform" = 'sound/vox_fem/uniform.ogg',
+ "unique" = 'sound/vox_fem/unique.ogg',
"unknown" = 'sound/vox_fem/unknown.ogg',
"unlocked" = 'sound/vox_fem/unlocked.ogg',
"unsafe" = 'sound/vox_fem/unsafe.ogg',
"until" = 'sound/vox_fem/until.ogg',
+ "unwrench" = 'sound/vox_fem/unwrench.ogg',
+ "unwrenching" = 'sound/vox_fem/unwrenching.ogg',
"up" = 'sound/vox_fem/up.ogg',
"update" = 'sound/vox_fem/update.ogg',
"updated" = 'sound/vox_fem/updated.ogg',
@@ -967,6 +1205,8 @@ GLOBAL_LIST_INIT(vox_sounds, list(
"usa" = 'sound/vox_fem/usa.ogg',
"use" = 'sound/vox_fem/use.ogg',
"used" = 'sound/vox_fem/used.ogg',
+ "useful" = 'sound/vox_fem/useful.ogg',
+ "useless" = 'sound/vox_fem/useless.ogg',
"user" = 'sound/vox_fem/user.ogg',
"v" = 'sound/vox_fem/v.ogg',
"vacate" = 'sound/vox_fem/vacate.ogg',
@@ -987,8 +1227,8 @@ GLOBAL_LIST_INIT(vox_sounds, list(
"vitals" = 'sound/vox_fem/vitals.ogg',
"voltage" = 'sound/vox_fem/voltage.ogg',
"vox" = 'sound/vox_fem/vox.ogg',
- "vox_login" = 'sound/vox_fem/vox_login.ogg',
"voxtest" = 'sound/vox_fem/voxtest.ogg',
+ "vox_login" = 'sound/vox_fem/vox_login.ogg',
"w" = 'sound/vox_fem/w.ogg',
"walk" = 'sound/vox_fem/walk.ogg',
"wall" = 'sound/vox_fem/wall.ogg',
@@ -1002,15 +1242,19 @@ GLOBAL_LIST_INIT(vox_sounds, list(
"was" = 'sound/vox_fem/was.ogg',
"waste" = 'sound/vox_fem/waste.ogg',
"water" = 'sound/vox_fem/water.ogg',
+ "way" = 'sound/vox_fem/way.ogg',
+ "ways" = 'sound/vox_fem/ways.ogg',
"we" = 'sound/vox_fem/we.ogg',
"weak" = 'sound/vox_fem/weak.ogg',
"weapon" = 'sound/vox_fem/weapon.ogg',
"welcome" = 'sound/vox_fem/welcome.ogg',
+ "weld" = 'sound/vox_fem/weld.ogg',
"west" = 'sound/vox_fem/west.ogg',
"wew" = 'sound/vox_fem/wew.ogg',
"what" = 'sound/vox_fem/what.ogg',
"when" = 'sound/vox_fem/when.ogg',
"where" = 'sound/vox_fem/where.ogg',
+ "which" = 'sound/vox_fem/which.ogg',
"while" = 'sound/vox_fem/while.ogg',
"whiskey" = 'sound/vox_fem/whiskey.ogg',
"white" = 'sound/vox_fem/white.ogg',
@@ -1025,7 +1269,15 @@ GLOBAL_LIST_INIT(vox_sounds, list(
"wood" = 'sound/vox_fem/wood.ogg',
"woody" = 'sound/vox_fem/woody.ogg',
"woop" = 'sound/vox_fem/woop.ogg',
+ "work" = 'sound/vox_fem/work.ogg',
+ "worked" = 'sound/vox_fem/worked.ogg',
+ "working" = 'sound/vox_fem/working.ogg',
+ "works" = 'sound/vox_fem/works.ogg',
+ "would" = 'sound/vox_fem/would.ogg',
+ "wouldnt" = 'sound/vox_fem/wouldnt.ogg',
"wow" = 'sound/vox_fem/wow.ogg',
+ "wrench" = 'sound/vox_fem/wrench.ogg',
+ "wrenching" = 'sound/vox_fem/wrenching.ogg',
"x" = 'sound/vox_fem/x.ogg',
"xeno" = 'sound/vox_fem/xeno.ogg',
"xenobiology" = 'sound/vox_fem/xenobiology.ogg',
@@ -1041,6 +1293,8 @@ GLOBAL_LIST_INIT(vox_sounds, list(
"your" = 'sound/vox_fem/your.ogg',
"yourself" = 'sound/vox_fem/yourself.ogg',
"z" = 'sound/vox_fem/z.ogg',
+ "zap" = 'sound/vox_fem/zap.ogg',
+ "zauker" = 'sound/vox_fem/zauker.ogg',
"zero" = 'sound/vox_fem/zero.ogg',
"zombie" = 'sound/vox_fem/zombie.ogg',
"zone" = 'sound/vox_fem/zone.ogg',
diff --git a/code/modules/mob/living/silicon/robot/robot_model.dm b/code/modules/mob/living/silicon/robot/robot_model.dm
index 4fc4bd5d8c15d..1721d6ec2c102 100644
--- a/code/modules/mob/living/silicon/robot/robot_model.dm
+++ b/code/modules/mob/living/silicon/robot/robot_model.dm
@@ -53,17 +53,9 @@
var/list/ride_offset_y = list("north" = 4, "south" = 4, "east" = 3, "west" = 3)
///List of skins the borg can be reskinned to, optional
var/list/borg_skins
- ///Omnitoolbox, holder of certain borg tools. Not all models have one
- var/obj/item/cyborg_omnitoolbox/toolbox
- ///Path to toolbox, if a model gets one
- var/toolbox_path
/obj/item/robot_model/Initialize(mapload)
. = ..()
-
- if(toolbox_path)
- toolbox = new toolbox_path(src)
-
for(var/path in basic_modules)
var/obj/item/new_module = new path(src)
basic_modules += new_module
@@ -406,7 +398,6 @@
model_select_icon = "engineer"
model_traits = list(TRAIT_NEGATES_GRAVITY)
hat_offset = -4
- toolbox_path = /obj/item/cyborg_omnitoolbox/engineering
/obj/item/robot_model/janitor
name = "Janitor"
@@ -687,7 +678,6 @@
/obj/item/reagent_containers/syringe,
/obj/item/borg/cyborg_omnitool/medical,
/obj/item/borg/cyborg_omnitool/medical,
- /obj/item/surgical_drapes/cyborg,
/obj/item/blood_filter,
/obj/item/extinguisher/mini,
/obj/item/emergency_bed/silicon,
@@ -705,7 +695,6 @@
model_select_icon = "medical"
model_traits = list(TRAIT_PUSHIMMUNE)
hat_offset = 3
- toolbox_path = /obj/item/cyborg_omnitoolbox/medical
borg_skins = list(
"Machinified Doctor" = list(SKIN_ICON_STATE = "medical"),
"Qualified Doctor" = list(SKIN_ICON_STATE = "qualified_doctor"),
@@ -888,7 +877,6 @@
/obj/item/healthanalyzer,
/obj/item/borg/cyborg_omnitool/medical,
/obj/item/borg/cyborg_omnitool/medical,
- /obj/item/surgical_drapes/cyborg,
/obj/item/blood_filter,
/obj/item/melee/energy/sword/cyborg/saw,
/obj/item/emergency_bed/silicon,
@@ -904,7 +892,6 @@
model_select_icon = "malf"
model_traits = list(TRAIT_PUSHIMMUNE)
hat_offset = 3
- toolbox_path = /obj/item/cyborg_omnitoolbox/medical
/obj/item/robot_model/saboteur
name = "Syndicate Saboteur"
@@ -934,7 +921,6 @@
model_select_icon = "malf"
model_traits = list(TRAIT_PUSHIMMUNE, TRAIT_NEGATES_GRAVITY)
hat_offset = -4
- toolbox_path = /obj/item/cyborg_omnitoolbox/engineering
canDispose = TRUE
/obj/item/robot_model/syndicate/kiltborg
diff --git a/code/modules/mob/living/silicon/silicon.dm b/code/modules/mob/living/silicon/silicon.dm
index 6d94c2c5be978..9ca655740cd08 100644
--- a/code/modules/mob/living/silicon/silicon.dm
+++ b/code/modules/mob/living/silicon/silicon.dm
@@ -76,6 +76,7 @@
TRAIT_MARTIAL_ARTS_IMMUNE,
TRAIT_NOFIRE_SPREAD,
TRAIT_BRAWLING_KNOCKDOWN_BLOCKED,
+ TRAIT_FENCE_CLIMBER,
)
add_traits(traits_to_apply, ROUNDSTART_TRAIT)
diff --git a/code/modules/mob/living/silicon/silicon_say.dm b/code/modules/mob/living/silicon/silicon_say.dm
index 1468b9c736591..9310211aa0e6d 100644
--- a/code/modules/mob/living/silicon/silicon_say.dm
+++ b/code/modules/mob/living/silicon/silicon_say.dm
@@ -70,6 +70,9 @@
)
/mob/living/silicon/binarycheck()
+ var/area/our_area = get_area(src)
+ if(our_area.area_flags & BINARY_JAMMING)
+ return FALSE
return TRUE
/mob/living/silicon/radio(message, list/message_mods = list(), list/spans, language)
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 b4cafdb6d538d..4156d9678bca0 100644
--- a/code/modules/mob/living/simple_animal/hostile/megafauna/hierophant.dm
+++ b/code/modules/mob/living/simple_animal/hostile/megafauna/hierophant.dm
@@ -538,12 +538,12 @@ Difficulty: Hard
/obj/effect/temp_visual/hierophant/wall/Initialize(mapload, new_caster)
. = ..()
- if(smoothing_flags & (SMOOTH_CORNERS|SMOOTH_BITMASK))
+ if(smoothing_flags & USES_SMOOTHING)
QUEUE_SMOOTH_NEIGHBORS(src)
QUEUE_SMOOTH(src)
/obj/effect/temp_visual/hierophant/wall/Destroy()
- if(smoothing_flags & (SMOOTH_CORNERS|SMOOTH_BITMASK))
+ if(smoothing_flags & USES_SMOOTHING)
QUEUE_SMOOTH_NEIGHBORS(src)
return ..()
diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/elite.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/elite.dm
index 8558601f12467..ae0011f998a68 100644
--- a/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/elite.dm
+++ b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/elite.dm
@@ -272,8 +272,8 @@ While using this makes the system rely on OnFire, it still gives options for tim
var/obj/effect/temp_visual/heal/H = new /obj/effect/temp_visual/heal(get_turf(mychild))
H.color = COLOR_RED
-/obj/structure/elite_tumor/attackby(obj/item/attacking_item, mob/user, params)
- . = ..()
+/obj/structure/elite_tumor/item_interaction(mob/living/user, obj/item/attacking_item, list/modifiers)
+ . = NONE
if(istype(attacking_item, /obj/item/organ/internal/monster_core/regenerative_core) && activity == TUMOR_INACTIVE && !boosted)
var/obj/item/organ/internal/monster_core/regenerative_core/core = attacking_item
visible_message(span_boldwarning("As [user] drops the core into [src], [src] appears to swell."))
@@ -282,7 +282,7 @@ While using this makes the system rely on OnFire, it still gives options for tim
set_light_range(6)
desc = "[desc] This one seems to glow with a strong intensity."
qdel(core)
- return TRUE
+ return ITEM_INTERACT_SUCCESS
/obj/structure/elite_tumor/proc/arena_checks()
if(activity != TUMOR_ACTIVE || QDELETED(src))
@@ -408,12 +408,12 @@ While using this makes the system rely on OnFire, it still gives options for tim
/obj/effect/temp_visual/elite_tumor_wall/Initialize(mapload, new_caster)
. = ..()
- if(smoothing_flags & (SMOOTH_CORNERS|SMOOTH_BITMASK))
+ if(smoothing_flags & USES_SMOOTHING)
QUEUE_SMOOTH_NEIGHBORS(src)
QUEUE_SMOOTH(src)
/obj/effect/temp_visual/elite_tumor_wall/Destroy()
- if(smoothing_flags & (SMOOTH_CORNERS|SMOOTH_BITMASK))
+ if(smoothing_flags & USES_SMOOTHING)
QUEUE_SMOOTH_NEIGHBORS(src)
return ..()
diff --git a/code/modules/mob/living/simple_animal/hostile/ooze.dm b/code/modules/mob/living/simple_animal/hostile/ooze.dm
index e20ecc2337a4d..f69c010ac6bfb 100644
--- a/code/modules/mob/living/simple_animal/hostile/ooze.dm
+++ b/code/modules/mob/living/simple_animal/hostile/ooze.dm
@@ -375,7 +375,7 @@
name = "mending globule"
icon_state = "glob_projectile"
shrapnel_type = /obj/item/mending_globule
- embedding = list("embed_chance" = 100, ignore_throwspeed_threshold = TRUE, "pain_mult" = 0, "jostle_pain_mult" = 0, "fall_chance" = 0.5)
+ embed_type = /datum/embed_data/mending_globule
damage = 0
///This item is what is embedded into the mob, and actually handles healing of mending globules
@@ -384,10 +384,17 @@
desc = "It somehow heals those who touch it."
icon = 'icons/obj/science/vatgrowing.dmi'
icon_state = "globule"
- embedding = list("embed_chance" = 100, ignore_throwspeed_threshold = TRUE, "pain_mult" = 0, "jostle_pain_mult" = 0, "fall_chance" = 0.5)
+ embed_type = /datum/embed_data/mending_globule
var/obj/item/bodypart/bodypart
var/heals_left = 35
+/datum/embed_data/mending_globule
+ embed_chance = 100
+ ignore_throwspeed_threshold = TRUE
+ pain_mult = 0
+ jostle_pain_mult = 0
+ fall_chance = 0.5
+
/obj/item/mending_globule/Destroy()
. = ..()
bodypart = null
diff --git a/code/modules/mob/login.dm b/code/modules/mob/login.dm
index 81a9fa7c62447..a4964add6c865 100644
--- a/code/modules/mob/login.dm
+++ b/code/modules/mob/login.dm
@@ -99,7 +99,8 @@
update_client_colour()
update_mouse_pointer()
- refresh_looping_ambience()
+ update_ambience_area(get_area(src))
+
if(!can_hear())
stop_sound_channel(CHANNEL_AMBIENCE)
diff --git a/code/modules/mob/logout.dm b/code/modules/mob/logout.dm
index cf937e42bb74f..09d50c8436c71 100644
--- a/code/modules/mob/logout.dm
+++ b/code/modules/mob/logout.dm
@@ -3,6 +3,7 @@
log_message("[key_name(src)] is no longer owning mob [src]([src.type])", LOG_OWNERSHIP)
SStgui.on_logout(src)
remove_from_player_list()
+ update_ambience_area(null) // Unset ambience vars so it plays again on login
..()
if(loc)
diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm
index f24f5a58c0d8d..6ec820bc469c9 100644
--- a/code/modules/mob/mob.dm
+++ b/code/modules/mob/mob.dm
@@ -6,7 +6,6 @@
* * GLOB.dead_mob_list
* * GLOB.alive_mob_list
* * GLOB.all_clockwork_mobs
- * * GLOB.mob_directory
*
* Unsets the focus var
*
@@ -67,7 +66,6 @@
*
* Adds to global lists
* * GLOB.mob_list
- * * GLOB.mob_directory (by tag)
* * GLOB.dead_mob_list - if mob is dead
* * GLOB.alive_mob_list - if the mob is alive
*
@@ -441,88 +439,6 @@
return FALSE
-/**
- * Try to equip an item to a slot on the mob
- *
- * This is a SAFE proc. Use this instead of equip_to_slot()!
- *
- * set qdel_on_fail to have it delete W if it fails to equip
- *
- * set disable_warning to disable the 'you are unable to equip that' warning.
- *
- * unset redraw_mob to prevent the mob icons from being redrawn at the end.
- *
- * Initial is used to indicate whether or not this is the initial equipment (job datums etc) or just a player doing it
- *
- * set indirect_action to allow insertions into "soft" locked objects, things that are easily opened by the owning mob
- */
-/mob/proc/equip_to_slot_if_possible(obj/item/W, slot, qdel_on_fail = FALSE, disable_warning = FALSE, redraw_mob = TRUE, bypass_equip_delay_self = FALSE, initial = FALSE, indirect_action = FALSE)
- if(!istype(W) || QDELETED(W)) //This qdeleted is to prevent stupid behavior with things that qdel during init, like say stacks
- return FALSE
- if(!W.mob_can_equip(src, slot, disable_warning, bypass_equip_delay_self, indirect_action = indirect_action))
- if(qdel_on_fail)
- qdel(W)
- else if(!disable_warning)
- to_chat(src, span_warning("You are unable to equip that!"))
- return FALSE
- equip_to_slot(W, slot, initial, redraw_mob, indirect_action = indirect_action) //This proc should not ever fail.
- return TRUE
-
-/**
- * Actually equips an item to a slot (UNSAFE)
- *
- * This is an UNSAFE proc. It merely handles the actual job of equipping. All the checks on
- * whether you can or can't equip need to be done before! Use mob_can_equip() for that task.
- *
- *In most cases you will want to use equip_to_slot_if_possible()
- */
-/mob/proc/equip_to_slot(obj/item/equipping, slot, initial = FALSE, redraw_mob = FALSE, indirect_action = FALSE)
- return
-
-/**
- * Equip an item to the slot or delete
- *
- * This is just a commonly used configuration for the equip_to_slot_if_possible() proc, used to
- * equip people when the round starts and when events happen and such.
- *
- * Also bypasses equip delay checks, since the mob isn't actually putting it on.
- * Initial is used to indicate whether or not this is the initial equipment (job datums etc) or just a player doing it
- * set indirect_action to allow insertions into "soft" locked objects, things that are easily opened by the owning mob
- */
-/mob/proc/equip_to_slot_or_del(obj/item/W, slot, initial = FALSE, indirect_action = FALSE)
- return equip_to_slot_if_possible(W, slot, TRUE, TRUE, FALSE, TRUE, initial, indirect_action)
-
-/**
- * Auto equip the passed in item the appropriate slot based on equipment priority
- *
- * puts the item "W" into an appropriate slot in a human's inventory
- *
- * returns 0 if it cannot, 1 if successful
- */
-/mob/proc/equip_to_appropriate_slot(obj/item/W, qdel_on_fail = FALSE, indirect_action = FALSE)
- if(!istype(W))
- return FALSE
- var/slot_priority = W.slot_equipment_priority
-
- if(!slot_priority)
- slot_priority = list( \
- ITEM_SLOT_BACK, ITEM_SLOT_ID,\
- ITEM_SLOT_ICLOTHING, ITEM_SLOT_OCLOTHING,\
- ITEM_SLOT_MASK, ITEM_SLOT_HEAD, ITEM_SLOT_NECK,\
- ITEM_SLOT_FEET, ITEM_SLOT_GLOVES,\
- ITEM_SLOT_EARS, ITEM_SLOT_EYES,\
- ITEM_SLOT_BELT, ITEM_SLOT_SUITSTORE,\
- ITEM_SLOT_LPOCKET, ITEM_SLOT_RPOCKET,\
- ITEM_SLOT_DEX_STORAGE\
- )
-
- for(var/slot in slot_priority)
- if(equip_to_slot_if_possible(W, slot, disable_warning = TRUE, redraw_mob = TRUE, indirect_action = indirect_action))
- return TRUE
-
- if(qdel_on_fail)
- qdel(W)
- return FALSE
/**
* Reset the attached clients perspective (viewpoint)
*
@@ -889,10 +805,6 @@
set category = null
return
-///Is the mob muzzled (default false)
-/mob/proc/is_muzzled()
- return FALSE
-
/// Adds this list to the output to the stat browser
/mob/proc/get_status_tab_items()
. = list("") //we want to offset unique stuff from standard stuff
diff --git a/code/modules/mob/mob_defines.dm b/code/modules/mob/mob_defines.dm
index a0658c3a02b38..2206efd0e13ce 100644
--- a/code/modules/mob/mob_defines.dm
+++ b/code/modules/mob/mob_defines.dm
@@ -191,3 +191,6 @@
var/active_typing_indicator
///the icon currently used for the thinking indicator's bubble
var/active_thinking_indicator
+
+ /// A ref of the area we're taking our ambient loop from.
+ var/area/ambience_tracked_area
diff --git a/code/modules/mob/mob_lists.dm b/code/modules/mob/mob_lists.dm
index 9fd097a1fd6a4..f43f82ac24609 100644
--- a/code/modules/mob/mob_lists.dm
+++ b/code/modules/mob/mob_lists.dm
@@ -1,12 +1,10 @@
///Adds the mob reference to the list and directory of all mobs. Called on Initialize().
/mob/proc/add_to_mob_list()
GLOB.mob_list |= src
- GLOB.mob_directory[tag] = src
///Removes the mob reference from the list and directory of all mobs. Called on Destroy().
/mob/proc/remove_from_mob_list()
GLOB.mob_list -= src
- GLOB.mob_directory -= tag
///Adds the mob reference to the list of all mobs alive. If mob is cliented, it adds it to the list of all living player-mobs.
/mob/proc/add_to_alive_mob_list()
diff --git a/code/modules/mob/mob_say.dm b/code/modules/mob/mob_say.dm
index 568aee6690292..6b8c8f9aa6b2a 100644
--- a/code/modules/mob/mob_say.dm
+++ b/code/modules/mob/mob_say.dm
@@ -106,9 +106,6 @@
if(!allow_mimes && HAS_MIND_TRAIT(src, TRAIT_MIMING))
return FALSE
- if(is_muzzled())
- return FALSE
-
return ..()
///Speak as a dead person (ghost etc)
diff --git a/code/modules/mob_spawn/ghost_roles/fugitive_hunter_roles.dm b/code/modules/mob_spawn/ghost_roles/fugitive_hunter_roles.dm
index 5b9e043349ea0..0165b27f21a1a 100644
--- a/code/modules/mob_spawn/ghost_roles/fugitive_hunter_roles.dm
+++ b/code/modules/mob_spawn/ghost_roles/fugitive_hunter_roles.dm
@@ -96,3 +96,18 @@
if I assisted them with my 'flesh-gaze'. They're a bunch of freaks, but at least they leave me be after I'm done helping them..."
back_story = HUNTER_PACK_PSYKER
outfit = /datum/outfit/psyker_seer
+
+/obj/effect/mob_spawn/ghost_role/human/fugitive/mi13
+ name = "top-secret pod"
+ desc = "You don't have the classification to know what this pod contains or what its purpose is."
+ icon = 'icons/obj/machines/sleeper.dmi'
+ icon_state = "sleeper_s"
+ prompt_name = "a MI13 agent"
+ you_are_text = "I am an agent sent by MI13."
+ flavour_text = "Your mission is to infiltrate the space around SS13 and capture the fugitives on board, dead or alive. Your shuttle has been disguised as an ordinary food truck to help you remain undetected. \
+ This is a stealth mission in enemy territory. Reinforcements will not be sent to save you. Microbombs have been implanted in case of capture. Do not disappoint."
+ back_story = HUNTER_PACK_MI13
+ outfit = /datum/outfit/mi13_hunter
+
+/obj/effect/mob_spawn/ghost_role/human/fugitive/mi13/chef
+ outfit = /datum/outfit/mi13_hunter/chef
diff --git a/code/modules/mob_spawn/ghost_roles/unused_roles.dm b/code/modules/mob_spawn/ghost_roles/unused_roles.dm
index d9cdd699ade70..80f584d52c2b6 100644
--- a/code/modules/mob_spawn/ghost_roles/unused_roles.dm
+++ b/code/modules/mob_spawn/ghost_roles/unused_roles.dm
@@ -308,7 +308,7 @@
/datum/outfit/syndicatespace/syndicrew
name = "Syndicate Ship Crew Member"
- glasses = /obj/item/clothing/glasses/night
+ glasses = /obj/item/clothing/glasses/night/colorless
mask = /obj/item/clothing/mask/gas/syndicate
l_pocket = /obj/item/gun/ballistic/automatic/pistol
r_pocket = /obj/item/knife/combat/survival
diff --git a/code/modules/mob_spawn/mob_spawn.dm b/code/modules/mob_spawn/mob_spawn.dm
index c9130fb706e1b..3337a15b441c5 100644
--- a/code/modules/mob_spawn/mob_spawn.dm
+++ b/code/modules/mob_spawn/mob_spawn.dm
@@ -59,26 +59,15 @@
spawned_human.underwear = "Nude"
spawned_human.undershirt = "Nude"
spawned_human.socks = "Nude"
+ randomize_human_normie(spawned_human)
if(hairstyle)
- spawned_human.hairstyle = hairstyle
- else
- spawned_human.hairstyle = random_hairstyle(spawned_human.gender)
+ spawned_human.set_hairstyle(hairstyle, update = FALSE)
if(facial_hairstyle)
- spawned_human.facial_hairstyle = facial_hairstyle
- else
- spawned_human.facial_hairstyle = random_facial_hairstyle(spawned_human.gender)
+ spawned_human.set_facial_hairstyle(facial_hairstyle, update = FALSE)
if(haircolor)
- spawned_human.hair_color = haircolor
- else
- spawned_human.hair_color = "#[random_color()]"
+ spawned_human.set_haircolor(haircolor, update = FALSE)
if(facial_haircolor)
- spawned_human.facial_hair_color = facial_haircolor
- else
- spawned_human.facial_hair_color = "#[random_color()]"
- if(skin_tone)
- spawned_human.skin_tone = skin_tone
- else
- spawned_human.skin_tone = pick(GLOB.skin_tones)
+ spawned_human.set_facial_haircolor(facial_haircolor, update = FALSE)
spawned_human.update_body(is_creating = TRUE)
/obj/effect/mob_spawn/proc/name_mob(mob/living/spawned_mob, forced_name)
diff --git a/code/modules/mod/mod_link.dm b/code/modules/mod/mod_link.dm
index 5733d48f45f6f..aa5307957f981 100644
--- a/code/modules/mod/mod_link.dm
+++ b/code/modules/mod/mod_link.dm
@@ -16,7 +16,7 @@
/proc/get_link_visual_generic(datum/mod_link/mod_link, atom/movable/visuals, proc_path)
var/mob/living/user = mod_link.get_user_callback.Invoke()
playsound(mod_link.holder, 'sound/machines/terminal_processing.ogg', 50, vary = TRUE)
- visuals.add_overlay(mutable_appearance('icons/effects/effects.dmi', "static_base", TURF_LAYER))
+ visuals.add_overlay(mutable_appearance('icons/effects/effects.dmi', "static_base", ABOVE_NORMAL_TURF_LAYER))
visuals.add_overlay(mutable_appearance('icons/effects/effects.dmi', "modlink", ABOVE_ALL_MOB_LAYER))
visuals.add_filter("crop_square", 1, alpha_mask_filter(icon = icon('icons/effects/effects.dmi', "modlink_filter")))
visuals.maptext_height = 6
@@ -77,29 +77,34 @@
)
/obj/item/mod/control/multitool_act_secondary(mob/living/user, obj/item/multitool/tool)
- if(!multitool_check_buffer(user, tool))
- return
+ . = NONE
+
var/tool_frequency = null
if(istype(tool.buffer, /datum/mod_link))
var/datum/mod_link/buffer_link = tool.buffer
tool_frequency = buffer_link.frequency
balloon_alert(user, "frequency set")
+ . = ITEM_INTERACT_SUCCESS
if(!tool_frequency && mod_link.frequency)
tool.set_buffer(mod_link)
balloon_alert(user, "frequency copied")
+ . = ITEM_INTERACT_SUCCESS
else if(tool_frequency && !mod_link.frequency)
mod_link.frequency = tool_frequency
+ . = ITEM_INTERACT_SUCCESS
else if(tool_frequency && mod_link.frequency)
var/response = tgui_alert(user, "Would you like to copy or imprint the frequency?", "MODlink Frequency", list("Copy", "Imprint"))
if(!user.is_holding(tool))
- return
+ return ITEM_INTERACT_BLOCKING
switch(response)
if("Copy")
tool.set_buffer(mod_link)
balloon_alert(user, "frequency copied")
+ . = ITEM_INTERACT_SUCCESS
if("Imprint")
mod_link.frequency = tool_frequency
balloon_alert(user, "frequency set")
+ . = ITEM_INTERACT_SUCCESS
/obj/item/mod/control/proc/can_call()
return get_charge() && wearer && wearer.stat < DEAD
@@ -227,29 +232,34 @@
return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
/obj/item/clothing/neck/link_scryer/multitool_act_secondary(mob/living/user, obj/item/multitool/tool)
- if(!multitool_check_buffer(user, tool))
- return
+ . = NONE
+
var/tool_frequency = null
if(istype(tool.buffer, /datum/mod_link))
var/datum/mod_link/buffer_link = tool.buffer
tool_frequency = buffer_link.frequency
balloon_alert(user, "frequency set")
+ . = ITEM_INTERACT_SUCCESS
if(!tool_frequency && mod_link.frequency)
tool.set_buffer(mod_link)
balloon_alert(user, "frequency copied")
+ . = ITEM_INTERACT_SUCCESS
else if(tool_frequency && !mod_link.frequency)
mod_link.frequency = tool_frequency
+ . = ITEM_INTERACT_SUCCESS
else if(tool_frequency && mod_link.frequency)
var/response = tgui_alert(user, "Would you like to copy or imprint the frequency?", "MODlink Frequency", list("Copy", "Imprint"))
if(!user.is_holding(tool))
- return
+ return ITEM_INTERACT_BLOCKING
switch(response)
if("Copy")
tool.set_buffer(mod_link)
balloon_alert(user, "frequency copied")
+ . = ITEM_INTERACT_SUCCESS
if("Imprint")
mod_link.frequency = tool_frequency
balloon_alert(user, "frequency set")
+ . = ITEM_INTERACT_SUCCESS
/obj/item/clothing/neck/link_scryer/worn_overlays(mutable_appearance/standing, isinhands)
. = ..()
diff --git a/code/modules/mod/mod_types.dm b/code/modules/mod/mod_types.dm
index d6465960beb28..c6d0869c6e987 100644
--- a/code/modules/mod/mod_types.dm
+++ b/code/modules/mod/mod_types.dm
@@ -5,7 +5,7 @@
/// The MOD core we apply to the suit.
var/applied_core = /obj/item/mod/core/standard
/// The cell we apply to the core. Only applies to standard core suits.
- var/applied_cell = /obj/item/stock_parts/power_store/cell/high
+ var/applied_cell = /obj/item/stock_parts/power_store/cell/super
/// List of modules we spawn with.
var/list/applied_modules = list()
/// Modules that we pin when the suit is installed for the first time, for convenience, can be applied or theme inbuilt modules.
@@ -165,9 +165,9 @@
/obj/item/mod/module/storage,
/obj/item/mod/module/magnetic_harness,
/obj/item/mod/module/flashlight,
+ /obj/item/mod/module/jetpack,
/obj/item/mod/module/pepper_shoulders,
/obj/item/mod/module/criminalcapture,
- /obj/item/mod/module/dispenser/mirage,
/obj/item/mod/module/quick_cuff,
/obj/item/mod/module/headprotector,
)
@@ -197,13 +197,13 @@
/obj/item/mod/module/storage/large_capacity,
/obj/item/mod/module/hat_stabilizer,
/obj/item/mod/module/magnetic_harness,
- /obj/item/mod/module/jetpack/advanced,
+ /obj/item/mod/module/jetpack,
/obj/item/mod/module/pathfinder,
/obj/item/mod/module/quick_cuff,
/obj/item/mod/module/headprotector,
)
default_pins = list(
- /obj/item/mod/module/jetpack/advanced,
+ /obj/item/mod/module/jetpack,
)
/obj/item/mod/control/pre_equipped/cosmohonk
@@ -245,7 +245,7 @@
/obj/item/mod/module/shock_absorber,
/obj/item/mod/module/emp_shield,
/obj/item/mod/module/magnetic_harness,
- /obj/item/mod/module/jetpack/advanced,
+ /obj/item/mod/module/jetpack,
/obj/item/mod/module/jump_jet,
/obj/item/mod/module/flashlight,
/obj/item/mod/module/dna_lock,
@@ -254,7 +254,7 @@
)
default_pins = list(
/obj/item/mod/module/armor_booster,
- /obj/item/mod/module/jetpack/advanced,
+ /obj/item/mod/module/jetpack,
/obj/item/mod/module/jump_jet,
)
@@ -268,7 +268,7 @@
/obj/item/mod/module/shock_absorber,
/obj/item/mod/module/emp_shield,
/obj/item/mod/module/magnetic_harness,
- /obj/item/mod/module/jetpack/advanced,
+ /obj/item/mod/module/jetpack,
/obj/item/mod/module/jump_jet,
/obj/item/mod/module/flashlight,
/obj/item/mod/module/hat_stabilizer/syndicate,
@@ -276,14 +276,14 @@
)
default_pins = list(
/obj/item/mod/module/armor_booster,
- /obj/item/mod/module/jetpack/advanced,
+ /obj/item/mod/module/jetpack,
/obj/item/mod/module/jump_jet,
)
/obj/item/mod/control/pre_equipped/nuclear/no_jetpack
/obj/item/mod/control/pre_equipped/nuclear/no_jetpack/Initialize(mapload, new_theme, new_skin, new_core)
- applied_modules -= list(/obj/item/mod/module/jetpack/advanced, /obj/item/mod/module/jump_jet)
+ applied_modules -= list(/obj/item/mod/module/jetpack, /obj/item/mod/module/jump_jet)
return ..()
/obj/item/mod/control/pre_equipped/nuclear/plasmaman
@@ -305,7 +305,7 @@
/obj/item/mod/module/shock_absorber,
/obj/item/mod/module/emp_shield,
/obj/item/mod/module/magnetic_harness,
- /obj/item/mod/module/jetpack/advanced,
+ /obj/item/mod/module/jetpack,
/obj/item/mod/module/jump_jet,
/obj/item/mod/module/flashlight,
/obj/item/mod/module/hat_stabilizer/syndicate,
@@ -313,7 +313,7 @@
)
default_pins = list(
/obj/item/mod/module/armor_booster,
- /obj/item/mod/module/jetpack/advanced,
+ /obj/item/mod/module/jetpack,
/obj/item/mod/module/jump_jet,
)
@@ -324,7 +324,7 @@
/obj/item/mod/module/emp_shield,
/obj/item/mod/module/magnetic_harness,
/obj/item/mod/module/thermal_regulator,
- /obj/item/mod/module/jetpack/advanced,
+ /obj/item/mod/module/jetpack,
/obj/item/mod/module/jump_jet,
/obj/item/mod/module/flashlight,
/obj/item/mod/module/hat_stabilizer/syndicate,
@@ -333,7 +333,7 @@
)
default_pins = list(
/obj/item/mod/module/armor_booster,
- /obj/item/mod/module/jetpack/advanced,
+ /obj/item/mod/module/jetpack,
/obj/item/mod/module/jump_jet,
/obj/item/mod/module/flamethrower,
)
@@ -357,7 +357,7 @@
starting_frequency = MODLINK_FREQ_SYNDICATE
applied_cell = /obj/item/stock_parts/power_store/cell/super
applied_modules = list(
- /obj/item/mod/module/organ_thrower,
+ /obj/item/mod/module/organizer,
/obj/item/mod/module/defibrillator/combat,
/obj/item/mod/module/flashlight,
/obj/item/mod/module/health_analyzer,
@@ -426,13 +426,13 @@
applied_modules = list(
/obj/item/mod/module/storage,
/obj/item/mod/module/magnetic_harness,
- /obj/item/mod/module/jetpack/advanced,
+ /obj/item/mod/module/jetpack,
/obj/item/mod/module/jump_jet,
/obj/item/mod/module/flashlight,
)
default_pins = list(
/obj/item/mod/module/armor_booster,
- /obj/item/mod/module/jetpack/advanced,
+ /obj/item/mod/module/jetpack,
/obj/item/mod/module/jump_jet,
)
@@ -623,7 +623,7 @@
/obj/item/mod/module/stealth/ninja,
/obj/item/mod/module/quick_carry/advanced,
/obj/item/mod/module/magboot/advanced,
- /obj/item/mod/module/jetpack/advanced,
+ /obj/item/mod/module/jetpack,
/obj/item/mod/module/anomaly_locked/kinesis/admin,
/obj/item/mod/module/shove_blocker,
/obj/item/mod/module/quick_cuff,
@@ -631,7 +631,7 @@
default_pins = list(
/obj/item/mod/module/stealth/ninja,
/obj/item/mod/module/magboot/advanced,
- /obj/item/mod/module/jetpack/advanced,
+ /obj/item/mod/module/jetpack,
/obj/item/mod/module/anomaly_locked/kinesis/admin,
)
diff --git a/code/modules/mod/modules/modules_engineering.dm b/code/modules/mod/modules/modules_engineering.dm
index fc21937eef049..40e1889efd968 100644
--- a/code/modules/mod/modules/modules_engineering.dm
+++ b/code/modules/mod/modules/modules_engineering.dm
@@ -120,7 +120,7 @@
hitsound = 'sound/weapons/batonextend.ogg'
hitsound_wall = 'sound/weapons/batonextend.ogg'
suppressed = SUPPRESSED_VERY
- hit_threshhold = LATTICE_LAYER
+ hit_threshhold = ABOVE_NORMAL_TURF_LAYER
/// Reference to the beam following the projectile.
var/line
diff --git a/code/modules/mod/modules/modules_general.dm b/code/modules/mod/modules/modules_general.dm
index 3ef6b9558712a..815cfb0a144bc 100644
--- a/code/modules/mod/modules/modules_general.dm
+++ b/code/modules/mod/modules/modules_general.dm
@@ -108,8 +108,6 @@
overlay_state_inactive = "module_jetpack"
overlay_state_active = "module_jetpack_on"
required_slots = list(ITEM_SLOT_BACK)
- /// Do we give the wearer a speed buff.
- var/full_speed = FALSE
/// Do we have stabilizers? If yes the user won't move from inertia.
var/stabilize = TRUE
/// Callback to see if we can thrust the user.
@@ -143,14 +141,6 @@
/datum/effect_system/trail_follow/ion/grav_allowed \
)
-/obj/item/mod/module/jetpack/on_activation()
- if(full_speed)
- mod.wearer.add_movespeed_modifier(/datum/movespeed_modifier/jetpack/fullspeed)
-
-/obj/item/mod/module/jetpack/on_deactivation(display_message = TRUE, deleting = FALSE)
- if(full_speed)
- mod.wearer.remove_movespeed_modifier(/datum/movespeed_modifier/jetpack/fullspeed)
-
/obj/item/mod/module/jetpack/get_configuration()
. = ..()
.["stabilizers"] = add_ui_configuration("Stabilizers", "bool", stabilize)
@@ -167,15 +157,6 @@
return FALSE
return TRUE
-/obj/item/mod/module/jetpack/advanced
- name = "MOD advanced ion jetpack module"
- desc = "An improvement on the previous model of electric thrusters. This one achieves higher speeds through \
- mounting of more jets and application of red paint."
- icon_state = "jetpack_advanced"
- overlay_state_inactive = "module_jetpackadv"
- overlay_state_active = "module_jetpackadv_on"
- full_speed = TRUE
-
/// Cooldown to use if we didn't actually launch a jump jet
#define FAILED_ACTIVATION_COOLDOWN 3 SECONDS
diff --git a/code/modules/mod/modules/modules_medical.dm b/code/modules/mod/modules/modules_medical.dm
index 3cf1d34a63a83..a0d58d721f3d4 100644
--- a/code/modules/mod/modules/modules_medical.dm
+++ b/code/modules/mod/modules/modules_medical.dm
@@ -119,19 +119,19 @@
volume = 30
inject_flags = INJECT_CHECK_PENETRATE_THICK
-///Organ Thrower - Lets you shoot organs, immediately replacing them if the target has the organ manipulation surgery.
-/obj/item/mod/module/organ_thrower
- name = "MOD organ thrower module"
+///Organizer - Lets you shoot organs, immediately replacing them if the target has the organ manipulation surgery.
+/obj/item/mod/module/organizer
+ name = "MOD organizer module"
desc = "A device recovered from a crashed Interdyne Pharmaceuticals vessel, \
this module has been unearthed for better or for worse. \
- It's an arm-mounted device utilizing technology similar to modern-day part replacers, \
- capable of storing and inserting organs into open patients. \
+ It's an arm-mounted device utilizing technology similar to modern rapid part exchange devices, \
+ capable of instantly replacing up to 5 organs at once in surgery without the need to remove them first, even from range. \
It's recommended by the DeForest Medical Corporation to not inform patients it has been used."
- icon_state = "organ_thrower"
+ icon_state = "organizer"
module_type = MODULE_ACTIVE
complexity = 2
use_energy_cost = DEFAULT_CHARGE_DRAIN
- incompatible_modules = list(/obj/item/mod/module/organ_thrower, /obj/item/mod/module/microwave_beam)
+ incompatible_modules = list(/obj/item/mod/module/organizer, /obj/item/mod/module/microwave_beam)
cooldown_time = 0.5 SECONDS
required_slots = list(ITEM_SLOT_GLOVES)
/// How many organs the module can hold.
@@ -139,7 +139,7 @@
/// A list of all our organs.
var/organ_list = list()
-/obj/item/mod/module/organ_thrower/on_select_use(atom/target)
+/obj/item/mod/module/organizer/on_select_use(atom/target)
. = ..()
if(!.)
return
diff --git a/code/modules/mod/modules/modules_ninja.dm b/code/modules/mod/modules/modules_ninja.dm
index a868eb6205659..0e0de691e030b 100644
--- a/code/modules/mod/modules/modules_ninja.dm
+++ b/code/modules/mod/modules/modules_ninja.dm
@@ -418,11 +418,7 @@
if(IS_SPACE_NINJA(mod.wearer))
mod.wearer.say(pick_list_replacements(NINJA_FILE, "lines"), forced = type)
to_chat(mod.wearer, span_notice("You have used the adrenaline boost."))
- mod.wearer.SetUnconscious(0)
- mod.wearer.SetStun(0)
- mod.wearer.SetKnockdown(0)
- mod.wearer.SetImmobilized(0)
- mod.wearer.SetParalyzed(0)
+ mod.wearer.SetAllImmobility(0)
mod.wearer.adjustStaminaLoss(-200)
mod.wearer.remove_status_effect(/datum/status_effect/speech/stutter)
mod.wearer.reagents.add_reagent(/datum/reagent/medicine/stimulants, 5)
@@ -430,24 +426,18 @@
addtimer(CALLBACK(src, PROC_REF(boost_aftereffects), mod.wearer), 7 SECONDS)
/obj/item/mod/module/adrenaline_boost/on_install()
- RegisterSignal(mod, COMSIG_ATOM_ATTACKBY, PROC_REF(on_attackby))
+ RegisterSignal(mod, COMSIG_ATOM_ITEM_INTERACTION, PROC_REF(try_boost))
/obj/item/mod/module/adrenaline_boost/on_uninstall(deleting = FALSE)
- UnregisterSignal(mod, COMSIG_ATOM_ATTACKBY)
+ UnregisterSignal(mod, COMSIG_ATOM_ITEM_INTERACTION)
-/obj/item/mod/module/adrenaline_boost/attackby(obj/item/attacking_item, mob/user, params)
- if(charge_boost(attacking_item, user))
- return TRUE
- return ..()
-
-/obj/item/mod/module/adrenaline_boost/proc/on_attackby(datum/source, obj/item/attacking_item, mob/user)
+/obj/item/mod/module/adrenaline_boost/proc/try_boost(source, mob/user, obj/item/attacking_item)
SIGNAL_HANDLER
-
- if(charge_boost(attacking_item, user))
+ if(charge_boost(attacking_item))
return COMPONENT_NO_AFTERATTACK
return NONE
-/obj/item/mod/module/adrenaline_boost/proc/charge_boost(obj/item/attacking_item, mob/user)
+/obj/item/mod/module/adrenaline_boost/proc/charge_boost(obj/item/attacking_item)
if(!attacking_item.is_open_container())
return FALSE
if(reagents.has_reagent(reagent_required, reagent_required_amount))
diff --git a/code/modules/mod/modules/modules_security.dm b/code/modules/mod/modules/modules_security.dm
index 2a317becf18e6..19150b8a4cd67 100644
--- a/code/modules/mod/modules/modules_security.dm
+++ b/code/modules/mod/modules/modules_security.dm
@@ -308,6 +308,8 @@
var/field_radius = 2
/// Damage multiplier on projectiles.
var/damage_multiplier = 0.75
+ /// Debuff multiplier on projectiles.
+ var/debuff_multiplier = 0.66
/// Speed multiplier on projectiles, higher means slower.
var/speed_multiplier = 2.5
/// List of all tracked projectiles.
@@ -338,6 +340,9 @@
SIGNAL_HANDLER
projectile.damage *= damage_multiplier
+ projectile.stamina *= damage_multiplier
+ projectile.stun *= debuff_multiplier
+ projectile.knockdown *= debuff_multiplier
projectile.speed *= speed_multiplier
projectile.add_overlay(projectile_effect)
@@ -346,6 +351,9 @@
projectile.damage /= damage_multiplier
projectile.speed /= speed_multiplier
+ projectile.stamina /= damage_multiplier
+ projectile.stun /= debuff_multiplier
+ projectile.knockdown /= debuff_multiplier
projectile.cut_overlay(projectile_effect)
///Active Sonar - Displays a hud circle on the turf of any living creatures in the given radius
diff --git a/code/modules/mod/modules/modules_service.dm b/code/modules/mod/modules/modules_service.dm
index 044137f0f2d07..4d2650585b1c0 100644
--- a/code/modules/mod/modules/modules_service.dm
+++ b/code/modules/mod/modules/modules_service.dm
@@ -35,7 +35,7 @@
module_type = MODULE_ACTIVE
complexity = 1
use_energy_cost = DEFAULT_CHARGE_DRAIN * 5
- incompatible_modules = list(/obj/item/mod/module/microwave_beam, /obj/item/mod/module/organ_thrower)
+ incompatible_modules = list(/obj/item/mod/module/microwave_beam, /obj/item/mod/module/organizer)
cooldown_time = 10 SECONDS
required_slots = list(ITEM_SLOT_GLOVES)
diff --git a/code/modules/mod/modules/modules_supply.dm b/code/modules/mod/modules/modules_supply.dm
index c86cb182c6b84..0e2bffd0aa6a7 100644
--- a/code/modules/mod/modules/modules_supply.dm
+++ b/code/modules/mod/modules/modules_supply.dm
@@ -559,7 +559,7 @@
light_range = 1
light_power = 1
light_color = COLOR_LIGHT_ORANGE
- embedding = null
+ embed_type = null
/obj/projectile/bullet/mining_bomb/Initialize(mapload)
. = ..()
diff --git a/code/modules/modular_computers/computers/item/pda.dm b/code/modules/modular_computers/computers/item/pda.dm
index 2fb7863445de2..f1fced454e4d3 100644
--- a/code/modules/modular_computers/computers/item/pda.dm
+++ b/code/modules/modular_computers/computers/item/pda.dm
@@ -149,14 +149,14 @@
if(tool.w_class >= WEIGHT_CLASS_SMALL) // Anything equal to or larger than small won't work
user.balloon_alert(user, "too big!")
return ITEM_INTERACT_BLOCKING
- if(inserted_item)
- balloon_alert(user, "no room!")
- return ITEM_INTERACT_BLOCKING
if(!user.transferItemToLoc(tool, src))
return ITEM_INTERACT_BLOCKING
- balloon_alert(user, "inserted [tool]")
- inserted_item = tool
- playsound(src, 'sound/machines/pda_button1.ogg', 50, TRUE)
+ if(inserted_item)
+ swap_pen(user, tool)
+ else
+ balloon_alert(user, "inserted [tool]")
+ inserted_item = tool
+ playsound(src, 'sound/machines/pda_button1.ogg', 50, TRUE)
return ITEM_INTERACT_SUCCESS
@@ -185,6 +185,14 @@
update_appearance()
playsound(src, 'sound/machines/pda_button2.ogg', 50, TRUE)
+/obj/item/modular_computer/pda/proc/swap_pen(mob/user, obj/item/tool)
+ if(inserted_item)
+ balloon_alert(user, "swapped pens")
+ user.put_in_hands(inserted_item)
+ inserted_item = tool
+ update_appearance()
+ playsound(src, 'sound/machines/pda_button1.ogg', 50, TRUE)
+
/obj/item/modular_computer/pda/proc/explode(mob/target, mob/bomber, from_message_menu = FALSE)
var/turf/current_turf = get_turf(src)
diff --git a/code/modules/modular_computers/computers/machinery/console_presets.dm b/code/modules/modular_computers/computers/machinery/console_presets.dm
index ad87960603c35..94ff9439e3b11 100644
--- a/code/modules/modular_computers/computers/machinery/console_presets.dm
+++ b/code/modules/modular_computers/computers/machinery/console_presets.dm
@@ -33,6 +33,17 @@
/datum/computer_file/program/scipaper_program,
)
+/obj/machinery/modular_computer/preset/research/away
+ name = "old research console"
+ desc = "An old computer used for writing research papers."
+ starting_programs = list(
+ /datum/computer_file/program/scipaper_program,
+ )
+
+/obj/machinery/modular_computer/preset/research/away/Initialize(mapload)
+ . = ..()
+ cpu.device_theme = PDA_THEME_RETRO
+
// ===== COMMAND CONSOLE =====
/obj/machinery/modular_computer/preset/command
name = "command console"
diff --git a/code/modules/movespeed/modifiers/items.dm b/code/modules/movespeed/modifiers/items.dm
index 6bdf2f31760d5..601ecc2289261 100644
--- a/code/modules/movespeed/modifiers/items.dm
+++ b/code/modules/movespeed/modifiers/items.dm
@@ -5,9 +5,6 @@
/datum/movespeed_modifier/jetpack/cybernetic
multiplicative_slowdown = -0.5
-/datum/movespeed_modifier/jetpack/fullspeed
- multiplicative_slowdown = -0.5
-
/datum/movespeed_modifier/die_of_fate
multiplicative_slowdown = 1
diff --git a/code/modules/pai/pai.dm b/code/modules/pai/pai.dm
index acde3c51a5c50..4268c040e2bcd 100644
--- a/code/modules/pai/pai.dm
+++ b/code/modules/pai/pai.dm
@@ -164,7 +164,6 @@
QDEL_NULL(signaler)
QDEL_NULL(leash)
card = null
- GLOB.pai_list.Remove(src)
return ..()
// Need to override parent here because the message we dispatch is turf-based, not based on the location of the object because that could be fuckin anywhere
@@ -221,7 +220,6 @@
if(istype(loc, /obj/item/modular_computer))
give_messenger_ability()
START_PROCESSING(SSfastprocess, src)
- GLOB.pai_list += src
make_laws()
for(var/law in laws.inherent)
lawcheck += law
diff --git a/code/modules/paperwork/pen.dm b/code/modules/paperwork/pen.dm
index 36949b65c7cdd..d37d97c167792 100644
--- a/code/modules/paperwork/pen.dm
+++ b/code/modules/paperwork/pen.dm
@@ -29,7 +29,7 @@
var/degrees = 0
var/font = PEN_FONT
var/requires_gravity = TRUE // can you use this to write in zero-g
- embedding = list(embed_chance = 50)
+ embed_type = /datum/embed_data/pen
sharpness = SHARP_POINTY
var/dart_insert_icon = 'icons/obj/weapons/guns/toy.dmi'
var/dart_insert_casing_icon_state = "overlay_pen"
@@ -37,6 +37,9 @@
/// If this pen can be clicked in order to retract it
var/can_click = TRUE
+/datum/embed_data/pen
+ embed_chance = 50
+
/obj/item/pen/Initialize(mapload)
. = ..()
AddComponent(/datum/component/dart_insert, \
@@ -73,7 +76,7 @@
if(user)
balloon_alert(user, "clicked")
- playsound(src, 'sound/machines/click.ogg', 30, TRUE, -3)
+ playsound(src, 'sound/items/pen_click.ogg', 30, TRUE, -3)
icon_state = initial(icon_state) + (active ? "_retracted" : "")
update_appearance(UPDATE_ICON)
@@ -86,7 +89,7 @@
return list(
"damage" = max(5, throwforce),
"speed" = max(0, throw_speed - 3),
- "embedding" = embedding,
+ "embedding" = get_embed(),
"armour_penetration" = armour_penetration,
"wound_bonus" = wound_bonus,
"bare_wound_bonus" = bare_wound_bonus,
@@ -191,7 +194,7 @@
"Black and Silver" = "pen-fountain-b",
"Command Blue" = "pen-fountain-cb"
)
- embedding = list("embed_chance" = 75)
+ embed_type = /datum/embed_data/pen/captain
dart_insert_casing_icon_state = "overlay_fountainpen_gold"
dart_insert_projectile_icon_state = "overlay_fountainpen_gold_proj"
var/list/overlay_reskin = list(
@@ -202,6 +205,9 @@
"Command Blue" = "overlay_fountainpen_gold"
)
+/datum/embed_data/pen/captain
+ embed_chance = 50
+
/obj/item/pen/fountain/captain/Initialize(mapload)
. = ..()
AddComponent(/datum/component/butchering, \
@@ -414,7 +420,7 @@
inhand_icon_state = hidden_icon
lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi'
righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi'
- embedding = list(embed_chance = 100) // Rule of cool
+ set_embed(/datum/embed_data/edagger_active)
else
name = initial(name)
desc = initial(desc)
@@ -422,15 +428,17 @@
inhand_icon_state = initial(inhand_icon_state)
lefthand_file = initial(lefthand_file)
righthand_file = initial(righthand_file)
- embedding = list(embed_chance = EMBED_CHANCE)
+ set_embed(embed_type)
- updateEmbedding()
if(user)
balloon_alert(user, "[hidden_name] [active ? "active" : "concealed"]")
playsound(src, active ? 'sound/weapons/saberon.ogg' : 'sound/weapons/saberoff.ogg', 5, TRUE)
set_light_on(active)
return COMPONENT_NO_DEFAULT_MESSAGE
+/datum/embed_data/edagger_active
+ embed_chance = 100
+
/obj/item/pen/edagger/proc/on_scan(datum/source, mob/user, list/extra_data)
SIGNAL_HANDLER
LAZYADD(extra_data[DETSCAN_CATEGORY_ILLEGAL], "Hard-light generator detected.")
diff --git a/code/modules/paperwork/ticketmachine.dm b/code/modules/paperwork/ticketmachine.dm
index 5c849f4a530cd..b4e97615a923a 100644
--- a/code/modules/paperwork/ticketmachine.dm
+++ b/code/modules/paperwork/ticketmachine.dm
@@ -49,13 +49,10 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/ticket_machine, 32)
. += span_notice("The ticket machine shows that ticket #[current_number] is currently being served.")
. += span_notice("You can take a ticket out with Left-Click to be number [ticket_number + 1] in queue.")
-/obj/machinery/ticket_machine/multitool_act(mob/living/user, obj/item/I)
- if(!multitool_check_buffer(user, I)) //make sure it has a data buffer
- return
- var/obj/item/multitool/M = I
+/obj/machinery/ticket_machine/multitool_act(mob/living/user, obj/item/multitool/M)
M.set_buffer(src)
balloon_alert(user, "saved to multitool buffer")
- return TRUE
+ return ITEM_INTERACT_SUCCESS
/obj/machinery/ticket_machine/emag_act(mob/user, obj/item/card/emag/emag_card) //Emag the ticket machine to dispense burning tickets, as well as randomize its number to destroy the HoP's mind.
if(obj_flags & EMAGGED)
diff --git a/code/modules/photography/camera/camera_image_capturing.dm b/code/modules/photography/camera/camera_image_capturing.dm
index d928164ff014d..64eeb192a2286 100644
--- a/code/modules/photography/camera/camera_image_capturing.dm
+++ b/code/modules/photography/camera/camera_image_capturing.dm
@@ -10,11 +10,18 @@
step_y = AM.step_y
. = ..()
+#define PHYSICAL_POSITION(atom) ((atom.y * world.icon_size) + (atom.pixel_y))
+
/obj/item/camera/proc/camera_get_icon(list/turfs, turf/center, psize_x = 96, psize_y = 96, datum/turf_reservation/clone_area, size_x, size_y, total_x, total_y)
var/list/atoms = list()
+ var/list/lighting = list()
var/skip_normal = FALSE
var/wipe_atoms = FALSE
+ var/mutable_appearance/backdrop = mutable_appearance('icons/hud/screen_gen.dmi', "flash")
+ backdrop.blend_mode = BLEND_OVERLAY
+ backdrop.color = "#292319"
+
if(istype(clone_area) && total_x == clone_area.width && total_y == clone_area.height && size_x >= 0 && size_y > 0)
var/turf/bottom_left = clone_area.bottom_left_turfs[1]
var/cloned_center_x = round(bottom_left.x + ((total_x - 1) / 2))
@@ -29,6 +36,12 @@
atoms += new /obj/effect/appearance_clone(newT, T)
if(T.loc.icon_state)
atoms += new /obj/effect/appearance_clone(newT, T.loc)
+ if(T.lighting_object)
+ var/obj/effect/appearance_clone/lighting_overlay = new(newT)
+ lighting_overlay.appearance = T.lighting_object.current_underlay
+ lighting_overlay.underlays += backdrop
+ lighting_overlay.blend_mode = BLEND_MULTIPLY
+ lighting += lighting_overlay
for(var/i in T.contents)
var/atom/A = i
if(!A.invisibility || (see_ghosts && isobserver(A)))
@@ -41,6 +54,12 @@
for(var/i in turfs)
var/turf/T = i
atoms += T
+ if(T.lighting_object)
+ var/obj/effect/appearance_clone/lighting_overlay = new(T)
+ lighting_overlay.appearance = T.lighting_object.current_underlay
+ lighting_overlay.underlays += backdrop
+ lighting_overlay.blend_mode = BLEND_MULTIPLY
+ lighting += lighting_overlay
for(var/atom/movable/A in T)
if(A.invisibility)
if(!(see_ghosts && isobserver(A)))
@@ -50,6 +69,7 @@
var/icon/res = icon('icons/blanks/96x96.dmi', "nothing")
res.Scale(psize_x, psize_y)
+ atoms += lighting
var/list/sorted = list()
var/j
@@ -57,7 +77,19 @@
var/atom/c = atoms[i]
for(j = sorted.len, j > 0, --j)
var/atom/c2 = sorted[j]
- if((c2.plane <= c.plane) && (c2.layer <= c.layer))
+ if(c2.plane > c.plane)
+ continue
+ if(c2.plane < c.plane)
+ break
+ var/c_position = PHYSICAL_POSITION(c)
+ var/c2_position = PHYSICAL_POSITION(c2)
+ // If you are above me, I layer above you
+ if(c2_position - 32 >= c_position)
+ break
+ // If I am above you you will always layer above me
+ if(c2_position <= c_position - 32)
+ continue
+ if(c2.layer < c.layer)
break
sorted.Insert(j+1, c)
CHECK_TICK
@@ -80,32 +112,34 @@
for(var/X in sorted) //these are clones
var/obj/effect/appearance_clone/clone = X
var/icon/img = getFlatIcon(clone, no_anim = TRUE)
- if(img)
- // Center of the image in X
- var/xo = (clone.x - center.x) * world.icon_size + clone.pixel_x + xcomp + clone.step_x
- // Center of the image in Y
- var/yo = (clone.y - center.y) * world.icon_size + clone.pixel_y + ycomp + clone.step_y
-
- if(clone.transform) // getFlatIcon doesn't give a snot about transforms.
- var/datum/decompose_matrix/decompose = clone.transform.decompose()
- // Scale in X, Y
- if(decompose.scale_x != 1 || decompose.scale_y != 1)
- var/base_w = img.Width()
- var/base_h = img.Height()
- // scale_x can be negative
- img.Scale(base_w * abs(decompose.scale_x), base_h * decompose.scale_y)
- if(decompose.scale_x < 0)
- img.Flip(EAST)
- xo -= base_w * (decompose.scale_x - SIGN(decompose.scale_x)) / 2 * SIGN(decompose.scale_x)
- yo -= base_h * (decompose.scale_y - 1) / 2
- // Rotation
- if(decompose.rotation != 0)
- img.Turn(decompose.rotation)
- // Shift
- xo += decompose.shift_x
- yo += decompose.shift_y
-
- res.Blend(img, blendMode2iconMode(clone.blend_mode), xo, yo)
+ if(!img)
+ CHECK_TICK
+ continue
+ // Center of the image in X
+ var/xo = (clone.x - center.x) * world.icon_size + clone.pixel_x + xcomp + clone.step_x
+ // Center of the image in Y
+ var/yo = (clone.y - center.y) * world.icon_size + clone.pixel_y + ycomp + clone.step_y
+
+ if(clone.transform) // getFlatIcon doesn't give a snot about transforms.
+ var/datum/decompose_matrix/decompose = clone.transform.decompose()
+ // Scale in X, Y
+ if(decompose.scale_x != 1 || decompose.scale_y != 1)
+ var/base_w = img.Width()
+ var/base_h = img.Height()
+ // scale_x can be negative
+ img.Scale(base_w * abs(decompose.scale_x), base_h * decompose.scale_y)
+ if(decompose.scale_x < 0)
+ img.Flip(EAST)
+ xo -= base_w * (decompose.scale_x - SIGN(decompose.scale_x)) / 2 * SIGN(decompose.scale_x)
+ yo -= base_h * (decompose.scale_y - 1) / 2
+ // Rotation
+ if(decompose.rotation != 0)
+ img.Turn(decompose.rotation)
+ // Shift
+ xo += decompose.shift_x
+ yo += decompose.shift_y
+
+ res.Blend(img, blendMode2iconMode(clone.blend_mode), xo, yo)
CHECK_TICK
if(!silent)
@@ -116,5 +150,9 @@
if(wipe_atoms)
QDEL_LIST(atoms)
+ else
+ QDEL_LIST(lighting)
return res
+
+#undef PHYSICAL_POSITION
diff --git a/code/modules/plumbing/plumbers/_plumb_machinery.dm b/code/modules/plumbing/plumbers/_plumb_machinery.dm
index 2e8eaee9d6dec..c8564b15f1215 100644
--- a/code/modules/plumbing/plumbers/_plumb_machinery.dm
+++ b/code/modules/plumbing/plumbers/_plumb_machinery.dm
@@ -12,6 +12,7 @@
active_power_usage = BASE_MACHINE_ACTIVE_CONSUMPTION * 2.75
resistance_flags = FIRE_PROOF | UNACIDABLE | ACID_PROOF
interaction_flags_machine = parent_type::interaction_flags_machine | INTERACT_MACHINE_OFFLINE
+ reagents = /datum/reagents/plumbing
///Plumbing machinery is always gonna need reagents, so we might aswell put it here
var/buffer = 50
@@ -25,6 +26,12 @@
AddComponent(/datum/component/simple_rotation)
register_context()
+/obj/machinery/plumbing/create_reagents(max_vol, flags)
+ if(!ispath(reagents))
+ qdel(reagents)
+ reagents = new reagents(max_vol, flags)
+ reagents.my_atom = src
+
/obj/machinery/plumbing/add_context(atom/source, list/context, obj/item/held_item, mob/user)
. = NONE
if(isnull(held_item))
@@ -96,58 +103,114 @@
reagents.expose(get_turf(src), TOUCH) //splash on the floor
reagents.clear_reagents()
-///We can empty beakers in here and everything
-/obj/machinery/plumbing/input
- name = "input gate"
- desc = "Can be manually filled with reagents from containers."
- icon_state = "pipe_input"
- pass_flags_self = PASSMACHINE | LETPASSTHROW // Small
- reagent_flags = TRANSPARENT | REFILLABLE
-
-
-/obj/machinery/plumbing/input/Initialize(mapload, bolt, layer)
- . = ..()
- AddComponent(/datum/component/plumbing/simple_supply, bolt, layer)
-
-///We can fill beakers in here and everything. we dont inheret from input because it has nothing that we need
-/obj/machinery/plumbing/output
- name = "output gate"
- desc = "A manual output for plumbing systems, for taking reagents directly into containers."
- icon_state = "pipe_output"
- pass_flags_self = PASSMACHINE | LETPASSTHROW // Small
- reagent_flags = TRANSPARENT | DRAINABLE
-
-/obj/machinery/plumbing/output/Initialize(mapload, bolt, layer)
- . = ..()
- AddComponent(/datum/component/plumbing/simple_demand, bolt, layer)
-
-/obj/machinery/plumbing/output/tap
- name = "drinking tap"
- desc = "A manual output for plumbing systems, for taking drinks directly into glasses."
- icon_state = "tap_output"
-
-/obj/machinery/plumbing/tank
- name = "chemical tank"
- desc = "A massive chemical holding tank."
- icon_state = "tank"
- buffer = 400
-
-/obj/machinery/plumbing/tank/Initialize(mapload, bolt, layer)
- . = ..()
- AddComponent(/datum/component/plumbing/tank, bolt, layer)
-
-///Layer manifold machine that connects a bunch of layers
-/obj/machinery/plumbing/layer_manifold
- name = "layer manifold"
- desc = "A plumbing manifold for layers."
- icon_state = "manifold"
- density = FALSE
-
-/obj/machinery/plumbing/layer_manifold/Initialize(mapload, bolt, layer)
- . = ..()
-
- AddComponent(/datum/component/plumbing/manifold, bolt, FIRST_DUCT_LAYER)
- AddComponent(/datum/component/plumbing/manifold, bolt, SECOND_DUCT_LAYER)
- AddComponent(/datum/component/plumbing/manifold, bolt, THIRD_DUCT_LAYER)
- AddComponent(/datum/component/plumbing/manifold, bolt, FOURTH_DUCT_LAYER)
- AddComponent(/datum/component/plumbing/manifold, bolt, FIFTH_DUCT_LAYER)
+/**
+ * Specialized reagent container for plumbing. Uses the round robin approach of transferring reagents
+ * so transfer 5 from 15 water, 15 sugar and 15 plasma becomes 10, 15, 15 instead of 13.3333, 13.3333 13.3333. Good if you hate floating point errors
+ */
+/datum/reagents/plumbing
+
+/**
+ * Same as the parent trans_to except only a few arguments have impact here & the rest of the arguments are discarded.
+ * Arguments
+ *
+ * * atom/target - the target we are transfering to
+ * * amount - amount to transfer
+ * * datum/reagent/target_id - the reagent id we want to transfer. if null everything gets transfered
+ * * methods - this is key for deciding between round-robin or proportional transfer. It does not mean the same as the
+ * parent proc. LINEAR for round robin(in this technique reagents are missing/lost/not preserved when there isn't enough space to hold them)
+ * NONE means everything is transfered regardless of how much space is available in the receiver in proportions
+ */
+/datum/reagents/plumbing/trans_to(
+ atom/target,
+ amount = 1,
+ multiplier = 1, //unused for plumbing
+ datum/reagent/target_id,
+ preserve_data = TRUE, //unused for plumbing
+ no_react = FALSE, //unused for plumbing we always want reactions
+ mob/transferred_by, //unused for plumbing logging is not important inside plumbing machines
+ remove_blacklisted = FALSE, //unused for plumbing, we don't care what reagents are inside us
+ methods = LINEAR, //default round robin technique for transferring reagents
+ show_message = TRUE, //unused for plumbing, used for logging only
+ ignore_stomach = FALSE //unused for plumbing, reagents flow only between machines & is not injected to mobs at any point in time
+)
+ if(QDELETED(target) || !total_volume)
+ return FALSE
+
+ if(!IS_FINITE(amount))
+ stack_trace("non finite amount passed to trans_to [amount] amount of reagents")
+ return FALSE
+
+ if(!isnull(target_id) && !ispath(target_id))
+ stack_trace("invalid target reagent id [target_id] passed to trans_to")
+ return FALSE
+
+ var/datum/reagents/target_holder
+ if(istype(target, /datum/reagents))
+ target_holder = target
+ else
+ target_holder = target.reagents
+
+ // Prevents small amount problems, as well as zero and below zero amounts.
+ amount = round(min(amount, total_volume, target_holder.maximum_volume - target_holder.total_volume), CHEMICAL_QUANTISATION_LEVEL)
+ if(amount <= 0)
+ return FALSE
+
+ //Set up new reagents to inherit the old ongoing reactions
+ transfer_reactions(target_holder)
+
+ var/list/cached_reagents = reagent_list
+ var/list/reagents_to_remove = list()
+ var/transfer_amount
+ var/transfered_amount
+ var/total_transfered_amount = 0
+
+ var/round_robin = methods & LINEAR
+ var/part
+ var/to_transfer
+ if(round_robin)
+ to_transfer = amount
+ else
+ part = amount / total_volume
+
+ //first add reagents to target
+ for(var/datum/reagent/reagent as anything in cached_reagents)
+ if(round_robin && !to_transfer)
+ break
+
+ if(!isnull(target_id))
+ if(reagent.type == target_id)
+ force_stop_reagent_reacting(reagent)
+ transfer_amount = min(amount, reagent.volume)
+ else
+ continue
+ else
+ if(round_robin)
+ transfer_amount = min(to_transfer, reagent.volume)
+ else
+ transfer_amount = reagent.volume * part
+
+ if(reagent.intercept_reagents_transfer(target_holder, amount))
+ continue
+
+ transfered_amount = target_holder.add_reagent(reagent.type, transfer_amount, copy_data(reagent), chem_temp, reagent.purity, reagent.ph, no_react = TRUE, ignore_splitting = reagent.chemical_flags & REAGENT_DONOTSPLIT) //we only handle reaction after every reagent has been transferred.
+ if(!transfered_amount)
+ continue
+ reagents_to_remove += list(list("R" = reagent, "T" = transfer_amount))
+ total_transfered_amount += transfered_amount
+ if(round_robin)
+ to_transfer -= transfered_amount
+
+ if(!isnull(target_id))
+ break
+
+ //remove chemicals that were added above
+ for(var/list/data as anything in reagents_to_remove)
+ var/datum/reagent/reagent = data["R"]
+ transfer_amount = data["T"]
+ remove_reagent(reagent.type, transfer_amount)
+
+ //handle reactions
+ target_holder.handle_reactions()
+ src.handle_reactions()
+
+ return round(total_transfered_amount, CHEMICAL_VOLUME_ROUNDING)
diff --git a/code/modules/plumbing/plumbers/bottler.dm b/code/modules/plumbing/plumbers/bottler.dm
index 5f63a3070bd2a..b3421e9ffc362 100644
--- a/code/modules/plumbing/plumbers/bottler.dm
+++ b/code/modules/plumbing/plumbers/bottler.dm
@@ -2,6 +2,7 @@
name = "chemical bottler"
desc = "Puts reagents into containers, like bottles and beakers in the tile facing the green light spot, they will exit on the red light spot if successfully filled."
icon_state = "bottler"
+ reagents = /datum/reagents
layer = ABOVE_ALL_MOB_LAYER
plane = ABOVE_GAME_PLANE
reagent_flags = TRANSPARENT | DRAINABLE
diff --git a/code/modules/plumbing/plumbers/iv_drip.dm b/code/modules/plumbing/plumbers/iv_drip.dm
index 6e2585553849c..45c2ebca27acb 100644
--- a/code/modules/plumbing/plumbers/iv_drip.dm
+++ b/code/modules/plumbing/plumbers/iv_drip.dm
@@ -10,7 +10,7 @@
/obj/machinery/iv_drip/plumbing/Initialize(mapload, bolt, layer)
. = ..()
- AddComponent(/datum/component/plumbing/iv_drip, bolt, layer)
+ AddComponent(/datum/component/plumbing/simple_demand, bolt, layer)
AddComponent(/datum/component/simple_rotation)
/obj/machinery/iv_drip/plumbing/add_context(atom/source, list/context, obj/item/held_item, mob/living/user)
diff --git a/code/modules/plumbing/plumbers/simple_machines.dm b/code/modules/plumbing/plumbers/simple_machines.dm
new file mode 100644
index 0000000000000..c2ef308297324
--- /dev/null
+++ b/code/modules/plumbing/plumbers/simple_machines.dm
@@ -0,0 +1,57 @@
+///We can empty beakers in here and everything
+/obj/machinery/plumbing/input
+ name = "input gate"
+ desc = "Can be manually filled with reagents from containers."
+ icon_state = "pipe_input"
+ pass_flags_self = PASSMACHINE | LETPASSTHROW // Small
+ reagent_flags = TRANSPARENT | REFILLABLE
+
+/obj/machinery/plumbing/input/Initialize(mapload, bolt, layer)
+ . = ..()
+ AddComponent(/datum/component/plumbing/simple_supply, bolt, layer)
+
+///We can fill beakers in here and everything. we dont inheret from input because it has nothing that we need
+/obj/machinery/plumbing/output
+ name = "output gate"
+ desc = "A manual output for plumbing systems, for taking reagents directly into containers."
+ icon_state = "pipe_output"
+ pass_flags_self = PASSMACHINE | LETPASSTHROW // Small
+ reagent_flags = TRANSPARENT | DRAINABLE
+ reagents = /datum/reagents
+
+/obj/machinery/plumbing/output/Initialize(mapload, bolt, layer)
+ . = ..()
+ AddComponent(/datum/component/plumbing/simple_demand, bolt, layer)
+
+///For pouring reagents from ducts directly into cups
+/obj/machinery/plumbing/output/tap
+ name = "drinking tap"
+ desc = "A manual output for plumbing systems, for taking drinks directly into glasses."
+ icon_state = "tap_output"
+
+///For storing large volume of reagents
+/obj/machinery/plumbing/tank
+ name = "chemical tank"
+ desc = "A massive chemical holding tank."
+ icon_state = "tank"
+ buffer = 400
+
+/obj/machinery/plumbing/tank/Initialize(mapload, bolt, layer)
+ . = ..()
+ AddComponent(/datum/component/plumbing/tank, bolt, layer)
+
+///Layer manifold machine that connects a bunch of layers
+/obj/machinery/plumbing/layer_manifold
+ name = "layer manifold"
+ desc = "A plumbing manifold for layers."
+ icon_state = "manifold"
+ density = FALSE
+
+/obj/machinery/plumbing/layer_manifold/Initialize(mapload, bolt, layer)
+ . = ..()
+
+ AddComponent(/datum/component/plumbing/manifold, bolt, FIRST_DUCT_LAYER)
+ AddComponent(/datum/component/plumbing/manifold, bolt, SECOND_DUCT_LAYER)
+ AddComponent(/datum/component/plumbing/manifold, bolt, THIRD_DUCT_LAYER)
+ AddComponent(/datum/component/plumbing/manifold, bolt, FOURTH_DUCT_LAYER)
+ AddComponent(/datum/component/plumbing/manifold, bolt, FIFTH_DUCT_LAYER)
diff --git a/code/modules/plumbing/plumbers/teleporter.dm b/code/modules/plumbing/plumbers/teleporter.dm
index df79220d15a8c..46c46d594f6f6 100644
--- a/code/modules/plumbing/plumbers/teleporter.dm
+++ b/code/modules/plumbing/plumbers/teleporter.dm
@@ -12,15 +12,10 @@
. = ..()
AddComponent(/datum/component/plumbing/simple_demand, bolt, layer)
-/obj/machinery/plumbing/sender/multitool_act(mob/living/user, obj/item/I)
- if(!multitool_check_buffer(user, I))
- return
-
- var/obj/item/multitool/M = I
-
+/obj/machinery/plumbing/sender/multitool_act(mob/living/user, obj/item/multitool/M)
if(!istype(M.buffer, /obj/machinery/plumbing/receiver))
to_chat(user, span_warning("Invalid buffer."))
- return
+ return ITEM_INTERACT_BLOCKING
if(target)
lose_teleport_target()
@@ -28,7 +23,7 @@
set_teleport_target(M.buffer)
to_chat(user, span_green("You succesfully link [src] to the [M.buffer]."))
- return TRUE
+ return ITEM_INTERACT_SUCCESS
///Lose our previous target and make our previous target lose us. Seperate proc because I feel like I'll need this again
/obj/machinery/plumbing/sender/proc/lose_teleport_target()
@@ -67,14 +62,10 @@
. = ..()
AddComponent(/datum/component/plumbing/simple_supply, bolt)
-/obj/machinery/plumbing/receiver/multitool_act(mob/living/user, obj/item/I)
- if(!multitool_check_buffer(user, I))
- return
-
- var/obj/item/multitool/M = I
+/obj/machinery/plumbing/receiver/multitool_act(mob/living/user, obj/item/multitool/M)
M.set_buffer(src)
balloon_alert(user, "saved to multitool buffer")
- return TRUE
+ return ITEM_INTERACT_SUCCESS
/obj/machinery/plumbing/receiver/process(seconds_per_tick)
if(!is_operational || panel_open)
diff --git a/code/modules/power/lighting/light.dm b/code/modules/power/lighting/light.dm
index 3781944ad0099..013140e399099 100644
--- a/code/modules/power/lighting/light.dm
+++ b/code/modules/power/lighting/light.dm
@@ -722,7 +722,7 @@
icon_state = "floor"
brightness = 4
light_angle = 360
- layer = LOW_OBJ_LAYER
+ layer = ABOVE_OPEN_TURF_LAYER
plane = FLOOR_PLANE
light_type = /obj/item/light/bulb
fitting = "bulb"
@@ -739,4 +739,6 @@
/obj/machinery/light/floor/transport
name = "transport light"
break_if_moved = FALSE
+ // has to render above tram things (trams are stupid)
layer = BELOW_OPEN_DOOR_LAYER
+ plane = GAME_PLANE
diff --git a/code/modules/power/pipecleaners.dm b/code/modules/power/pipecleaners.dm
index 7f1ef8fc2e3a8..4514c89b862e2 100644
--- a/code/modules/power/pipecleaners.dm
+++ b/code/modules/power/pipecleaners.dm
@@ -29,6 +29,7 @@ By design, d1 is the smallest direction and d2 is the highest
icon = 'icons/obj/pipes_n_cables/pipe_cleaner.dmi'
icon_state = "0-1"
layer = WIRE_LAYER //Above hidden pipes, GAS_PIPE_HIDDEN_LAYER
+ plane = FLOOR_PLANE
anchored = TRUE
obj_flags = CAN_BE_HIT
color = CABLE_HEX_COLOR_RED
diff --git a/code/modules/power/singularity/field_generator.dm b/code/modules/power/singularity/field_generator.dm
index b46b29538c608..da3a4e12c5662 100644
--- a/code/modules/power/singularity/field_generator.dm
+++ b/code/modules/power/singularity/field_generator.dm
@@ -51,6 +51,8 @@ no power level overlay is currently in the overlays list.
var/list/obj/machinery/field/generator/connected_gens = list()
///Check for asynk cleanups for this and the connected gens
var/clean_up = FALSE
+ /// we warm up and cool down instantly
+ var/instantenous = FALSE
/datum/armor/field_generator
melee = 25
@@ -207,8 +209,11 @@ no power level overlay is currently in the overlays list.
can_atmos_pass = ATMOS_PASS_YES
air_update_turf(TRUE, FALSE)
INVOKE_ASYNC(src, PROC_REF(cleanup))
- addtimer(CALLBACK(src, PROC_REF(cool_down)), 5 SECONDS)
RemoveElement(/datum/element/give_turf_traits, string_list(list(TRAIT_CONTAINMENT_FIELD)))
+ if(instantenous)
+ warming_up = 0
+ return
+ addtimer(CALLBACK(src, PROC_REF(cool_down)), 5 SECONDS)
/obj/machinery/field/generator/proc/cool_down()
if(active || warming_up <= 0)
@@ -219,9 +224,14 @@ no power level overlay is currently in the overlays list.
addtimer(CALLBACK(src, PROC_REF(cool_down)), 5 SECONDS)
/obj/machinery/field/generator/proc/turn_on()
+ AddElement(/datum/element/give_turf_traits, string_list(list(TRAIT_CONTAINMENT_FIELD)))
+ if(instantenous)
+ active = FG_ONLINE
+ warming_up = 3
+ start_fields()
+ return
active = FG_CHARGING
addtimer(CALLBACK(src, PROC_REF(warm_up)), 5 SECONDS)
- AddElement(/datum/element/give_turf_traits, string_list(list(TRAIT_CONTAINMENT_FIELD)))
/obj/machinery/field/generator/proc/warm_up()
if(!active)
@@ -420,9 +430,20 @@ no power level overlay is currently in the overlays list.
state = FG_WELDED
/obj/machinery/field/generator/starts_on/Initialize(mapload)
+ . = ..()
+ return INITIALIZE_HINT_LATELOAD
+
+/obj/machinery/field/generator/starts_on/post_machine_initialize()
. = ..()
turn_on()
+/obj/machinery/field/generator/starts_on/magic
+ power_level = 6 //forces the highest level overlay
+ instantenous = TRUE
+
+/obj/machinery/field/generator/starts_on/magic/process()
+ return PROCESS_KILL // this is the only place calc_power is called, and doing it here avoids one unnecessary proc call
+
#undef FG_UNSECURED
#undef FG_SECURED
#undef FG_WELDED
diff --git a/code/modules/power/supermatter/supermatter_gas.dm b/code/modules/power/supermatter/supermatter_gas.dm
index 35b9db0731f92..fe0ed388148b5 100644
--- a/code/modules/power/supermatter/supermatter_gas.dm
+++ b/code/modules/power/supermatter/supermatter_gas.dm
@@ -216,7 +216,7 @@ GLOBAL_LIST_INIT(sm_gas_behavior, init_sm_gas())
desc = "Will generate electrical zaps."
/datum/sm_gas/zauker/extra_effects(obj/machinery/power/supermatter_crystal/sm)
- if(!prob(sm.gas_percentage[/datum/gas/zauker]))
+ if(!prob(sm.gas_percentage[/datum/gas/zauker] * 100))
return
playsound(sm.loc, 'sound/weapons/emitter2.ogg', 100, TRUE, extrarange = 10)
sm.supermatter_zap(
diff --git a/code/modules/projectiles/ammunition/ballistic/rifle.dm b/code/modules/projectiles/ammunition/ballistic/rifle.dm
index 3a7c3fcb59d36..4c5c24a3eec89 100644
--- a/code/modules/projectiles/ammunition/ballistic/rifle.dm
+++ b/code/modules/projectiles/ammunition/ballistic/rifle.dm
@@ -20,6 +20,10 @@
/obj/item/ammo_casing/strilka310/enchanted
projectile_type = /obj/projectile/bullet/strilka310/enchanted
+/obj/item/ammo_casing/strilka310/phasic
+ name = ".310 Strilka phasic bullet casing"
+ desc = "A phasic .310 Strika bullet casing. "
+ projectile_type = /obj/projectile/bullet/strilka310/phasic
// .223 (M-90gl Carbine)
/obj/item/ammo_casing/a223
diff --git a/code/modules/projectiles/ammunition/ballistic/shotgun.dm b/code/modules/projectiles/ammunition/ballistic/shotgun.dm
index b545500420bc1..aeb3b34ed4a64 100644
--- a/code/modules/projectiles/ammunition/ballistic/shotgun.dm
+++ b/code/modules/projectiles/ammunition/ballistic/shotgun.dm
@@ -140,8 +140,8 @@
var/static/list/slapcraft_recipe_list = list(/datum/crafting_recipe/meteorslug, /datum/crafting_recipe/pulseslug, /datum/crafting_recipe/dragonsbreath, /datum/crafting_recipe/ionslug)
- AddComponent(
- /datum/component/slapcrafting,\
+ AddElement(
+ /datum/element/slapcrafting,\
slapcraft_recipes = slapcraft_recipe_list,\
)
diff --git a/code/modules/projectiles/ammunition/energy/special.dm b/code/modules/projectiles/ammunition/energy/special.dm
index f9d5ca5d61250..c42bcdc746e45 100644
--- a/code/modules/projectiles/ammunition/energy/special.dm
+++ b/code/modules/projectiles/ammunition/energy/special.dm
@@ -85,3 +85,10 @@
harmful = FALSE
e_cost = LASER_SHOTS(2, STANDARD_CELL_CHARGE * 0.5)
fire_sound = 'sound/weapons/gun/general/heavy_shot_suppressed.ogg' // fwip fwip fwip fwip
+
+// Used by /obj/item/gun/energy/photon
+/obj/item/ammo_casing/energy/photon
+ fire_sound = 'sound/weapons/lasercannonfire.ogg'
+ e_cost = LASER_SHOTS(4, STANDARD_CELL_CHARGE)
+ select_name = "flare"
+ projectile_type = /obj/projectile/energy/photon
diff --git a/code/modules/projectiles/boxes_magazines/ammo_boxes.dm b/code/modules/projectiles/boxes_magazines/ammo_boxes.dm
index 430ef11b7dd5d..bbd89389eb809 100644
--- a/code/modules/projectiles/boxes_magazines/ammo_boxes.dm
+++ b/code/modules/projectiles/boxes_magazines/ammo_boxes.dm
@@ -130,6 +130,12 @@
name = "stripper clip (.310 Surplus)"
ammo_type = /obj/item/ammo_casing/strilka310/surplus
+/obj/item/ammo_box/strilka310/phasic
+ name = "stripper clip (.310 Phasic)"
+ desc = "A stripper clip filled with phasic bullets, hastily developed after an incident where a misfire resulted in the destruction of Atrakor Silverscale's priceless Vigoxian Fabergé egg. \
+ These fancy bullets pass right though valuables until they end up in a far less expensive human skull."
+ ammo_type = /obj/item/ammo_casing/strilka310/phasic
+
/obj/item/ammo_box/n762
name = "ammo box (7.62x38mmR)"
icon_state = "10mmbox"
diff --git a/code/modules/projectiles/boxes_magazines/internal/rifle.dm b/code/modules/projectiles/boxes_magazines/internal/rifle.dm
index 8c6abaa0e7798..863f29508dac0 100644
--- a/code/modules/projectiles/boxes_magazines/internal/rifle.dm
+++ b/code/modules/projectiles/boxes_magazines/internal/rifle.dm
@@ -9,6 +9,9 @@
/obj/item/ammo_box/magazine/internal/boltaction/surplus
ammo_type = /obj/item/ammo_casing/strilka310/surplus
+/obj/item/ammo_box/magazine/internal/boltaction/phasic
+ ammo_type = /obj/item/ammo_casing/strilka310/phasic
+
/obj/item/ammo_box/magazine/internal/boltaction/pipegun
name = "pipegun internal magazine"
caliber = CALIBER_JUNK
diff --git a/code/modules/projectiles/gun.dm b/code/modules/projectiles/gun.dm
index 198eed66e3c92..f1df323609dd9 100644
--- a/code/modules/projectiles/gun.dm
+++ b/code/modules/projectiles/gun.dm
@@ -64,11 +64,6 @@
/// True if a gun dosen't need a pin, mostly used for abstract guns like tentacles and meathooks
var/pinless = FALSE
- var/can_bayonet = FALSE //if a bayonet can be added or removed if it already has one.
- var/obj/item/knife/bayonet
- var/knife_x_offset = 0
- var/knife_y_offset = 0
-
var/ammo_x_offset = 0 //used for positioning ammo count overlay on sprite
var/ammo_y_offset = 0
@@ -83,12 +78,11 @@
pin = new pin(src)
add_seclight_point()
+ add_bayonet_point()
/obj/item/gun/Destroy()
if(isobj(pin)) //Can still be the initial path, then we skip
QDEL_NULL(pin)
- if(bayonet)
- QDEL_NULL(bayonet)
if(chambered) //Not all guns are chambered (EMP'ed energy guns etc)
QDEL_NULL(chambered)
if(isatom(suppressed)) //SUPPRESSED IS USED AS BOTH A TRUE/FALSE AND AS A REF, WHAT THE FUCKKKKKKKKKKKKKKKKK
@@ -111,6 +105,10 @@
/obj/item/gun/proc/add_seclight_point()
return
+/// Similarly to add_seclight_point(), handles [the bayonet attachment component][/datum/component/bayonet_attachable]
+/obj/item/gun/proc/add_bayonet_point()
+ return
+
/obj/item/gun/Exited(atom/movable/gone, direction)
. = ..()
if(gone == pin)
@@ -120,10 +118,6 @@
update_appearance()
if(gone == suppressed)
clear_suppressor()
- if(gone == bayonet)
- bayonet = null
- if(!QDELING(src))
- update_appearance()
///Clears var and updates icon. In the case of ballistic weapons, also updates the gun's weight.
/obj/item/gun/proc/clear_suppressor()
@@ -144,13 +138,6 @@
else
. += "It doesn't have a firing pin installed, and won't fire."
- if(bayonet)
- . += "It has \a [bayonet] [can_bayonet ? "" : "permanently "]affixed to it."
- if(can_bayonet) //if it has a bayonet and this is false, the bayonet is permanent.
- . += span_info("[bayonet] looks like it can be unscrewed from [src].")
- if(can_bayonet)
- . += "It has a bayonet lug on it."
-
//called after the gun has successfully fired its chambered ammo.
/obj/item/gun/proc/process_chamber(empty_chamber = TRUE, from_firing = TRUE, chamber_next_round = TRUE)
handle_chamber(empty_chamber, from_firing, chamber_next_round)
@@ -248,31 +235,6 @@
return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
-/obj/item/gun/pre_attack(atom/A, mob/living/user, params)
- . = ..()
- if(.)
- return .
- if(isnull(bayonet) || !user.combat_mode)
- return .
- return bayonet.melee_attack_chain(user, A, params)
-
-/obj/item/gun/item_interaction(mob/living/user, obj/item/tool, list/modifiers)
- if(user.combat_mode)
- return NONE
-
- if(istype(tool, /obj/item/knife))
- var/obj/item/knife/new_stabber = tool
- if(!can_bayonet || !new_stabber.bayonet || !isnull(bayonet)) //ensure the gun has an attachment point available, and that the knife is compatible with it.
- return ITEM_INTERACT_BLOCKING
- if(!user.transferItemToLoc(new_stabber, src))
- return ITEM_INTERACT_BLOCKING
- to_chat(user, span_notice("You attach [new_stabber] to [src]'s bayonet lug."))
- bayonet = new_stabber
- update_appearance()
- return ITEM_INTERACT_SUCCESS
-
- return NONE
-
/obj/item/gun/interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers)
if(user.combat_mode && isliving(interacting_with))
return ITEM_INTERACT_SKIP_TO_ATTACK // Gun bash / bayonet attack
@@ -508,17 +470,7 @@
return
if(!user.can_perform_action(src, FORBID_TELEKINESIS_REACH))
return
-
- if(bayonet && can_bayonet) //if it has a bayonet, and the bayonet can be removed
- I.play_tool_sound(src)
- to_chat(user, span_notice("You unfix [bayonet] from [src]."))
- bayonet.forceMove(drop_location())
-
- if(Adjacent(user) && !issilicon(user))
- user.put_in_hands(bayonet)
- return ITEM_INTERACT_SUCCESS
-
- else if(pin?.pin_removable && user.is_holding(src))
+ if(pin?.pin_removable && user.is_holding(src))
user.visible_message(span_warning("[user] attempts to remove [pin] from [src] with [I]."),
span_notice("You attempt to remove [pin] from [src]. (It will take [DisplayTimeText(FIRING_PIN_REMOVAL_DELAY)].)"), null, 3)
if(I.use_tool(src, user, FIRING_PIN_REMOVAL_DELAY, volume = 50))
@@ -563,19 +515,6 @@
QDEL_NULL(pin)
return TRUE
-/obj/item/gun/update_overlays()
- . = ..()
- if(bayonet)
- var/mutable_appearance/knife_overlay
- var/state = "bayonet" //Generic state.
- if(bayonet.icon_state in icon_states('icons/obj/weapons/guns/bayonets.dmi')) //Snowflake state?
- state = bayonet.icon_state
- var/icon/bayonet_icons = 'icons/obj/weapons/guns/bayonets.dmi'
- knife_overlay = mutable_appearance(bayonet_icons, state)
- knife_overlay.pixel_x = knife_x_offset
- knife_overlay.pixel_y = knife_y_offset
- . += knife_overlay
-
/obj/item/gun/animate_atom_living(mob/living/owner)
new /mob/living/simple_animal/hostile/mimic/copy/ranged(drop_location(), src, owner)
diff --git a/code/modules/projectiles/guns/ballistic.dm b/code/modules/projectiles/guns/ballistic.dm
index 19c67f9154190..adfd2fbf4fd54 100644
--- a/code/modules/projectiles/guns/ballistic.dm
+++ b/code/modules/projectiles/guns/ballistic.dm
@@ -638,8 +638,7 @@ GLOBAL_LIST_INIT(gun_saw_types, typecacheof(list(
if(sawn_off)
balloon_alert(user, "it's already shortened!")
return
- if(bayonet)
- balloon_alert(user, "[bayonet.name] must be removed!")
+ if (SEND_SIGNAL(src, COMSIG_GUN_BEING_SAWNOFF, user) & COMPONENT_CANCEL_SAWING_OFF)
return
user.changeNext_move(CLICK_CD_MELEE)
user.visible_message(span_notice("[user] begins to shorten [src]."), span_notice("You begin to shorten [src]..."))
@@ -649,27 +648,30 @@ GLOBAL_LIST_INIT(gun_saw_types, typecacheof(list(
user.visible_message(span_danger("[src] goes off!"), span_danger("[src] goes off in your face!"))
return
- if(do_after(user, 3 SECONDS, target = src))
- if(sawn_off)
- return
- user.visible_message(span_notice("[user] shortens [src]!"), span_notice("You shorten [src]."))
- sawn_off = TRUE
- if(handle_modifications)
- name = "sawn-off [src.name]"
- desc = sawn_desc
- update_weight_class(WEIGHT_CLASS_NORMAL)
- //The file might not have a "gun" icon, let's prepare for this
- lefthand_file = 'icons/mob/inhands/weapons/guns_lefthand.dmi'
- righthand_file = 'icons/mob/inhands/weapons/guns_righthand.dmi'
- inhand_x_dimension = 32
- inhand_y_dimension = 32
- inhand_icon_state = "gun"
- worn_icon_state = "gun"
- slot_flags &= ~ITEM_SLOT_BACK //you can't sling it on your back
- slot_flags |= ITEM_SLOT_BELT //but you can wear it on your belt (poorly concealed under a trenchcoat, ideally)
- recoil = SAWN_OFF_RECOIL
- update_appearance()
+ if(!do_after(user, 3 SECONDS, target = src))
+ return
+ if(sawn_off)
+ return
+ user.visible_message(span_notice("[user] shortens [src]!"), span_notice("You shorten [src]."))
+ sawn_off = TRUE
+ SEND_SIGNAL(src, COMSIG_GUN_SAWN_OFF)
+ if(!handle_modifications)
return TRUE
+ name = "sawn-off [src.name]"
+ desc = sawn_desc
+ update_weight_class(WEIGHT_CLASS_NORMAL)
+ //The file might not have a "gun" icon, let's prepare for this
+ lefthand_file = 'icons/mob/inhands/weapons/guns_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/weapons/guns_righthand.dmi'
+ inhand_x_dimension = 32
+ inhand_y_dimension = 32
+ inhand_icon_state = "gun"
+ worn_icon_state = "gun"
+ slot_flags &= ~ITEM_SLOT_BACK //you can't sling it on your back
+ slot_flags |= ITEM_SLOT_BELT //but you can wear it on your belt (poorly concealed under a trenchcoat, ideally)
+ recoil = SAWN_OFF_RECOIL
+ update_appearance()
+ return TRUE
/obj/item/gun/ballistic/proc/guncleaning(mob/user, obj/item/A)
if(misfire_probability == initial(misfire_probability))
diff --git a/code/modules/projectiles/guns/ballistic/automatic.dm b/code/modules/projectiles/guns/ballistic/automatic.dm
index 30ba65dc94a86..b86e2a9938995 100644
--- a/code/modules/projectiles/guns/ballistic/automatic.dm
+++ b/code/modules/projectiles/guns/ballistic/automatic.dm
@@ -41,13 +41,13 @@
fire_delay = 2
burst_size = 3
pin = /obj/item/firing_pin/implant/pindicate
- can_bayonet = TRUE
- knife_x_offset = 26
- knife_y_offset = 12
mag_display = TRUE
mag_display_ammo = TRUE
empty_indicator = TRUE
+/obj/item/gun/ballistic/automatic/c20r/add_bayonet_point()
+ AddComponent(/datum/component/bayonet_attachable, offset_x = 26, offset_y = 12)
+
/obj/item/gun/ballistic/automatic/c20r/update_overlays()
. = ..()
if(!chambered && empty_indicator) //this is duplicated due to a layering issue with the select fire icon.
@@ -75,9 +75,6 @@
can_suppress = FALSE
burst_size = 1
actions_types = list()
- can_bayonet = TRUE
- knife_x_offset = 25
- knife_y_offset = 12
mag_display = TRUE
mag_display_ammo = TRUE
empty_indicator = TRUE
@@ -86,6 +83,9 @@
. = ..()
AddComponent(/datum/component/automatic_fire, 0.3 SECONDS)
+/obj/item/gun/ballistic/automatic/wt550/add_bayonet_point()
+ AddComponent(/datum/component/bayonet_attachable, offset_x = 25, offset_y = 12)
+
/obj/item/gun/ballistic/automatic/plastikov
name = "\improper PP-95 SMG"
desc = "An ancient 9mm submachine gun pattern updated and simplified to lower costs, though perhaps simplified too much."
diff --git a/code/modules/projectiles/guns/ballistic/bows/_bow.dm b/code/modules/projectiles/guns/ballistic/bows/_bow.dm
index 15c89ddb8553d..c0ce0b1ef2c9d 100644
--- a/code/modules/projectiles/guns/ballistic/bows/_bow.dm
+++ b/code/modules/projectiles/guns/ballistic/bows/_bow.dm
@@ -27,7 +27,12 @@
/obj/item/gun/ballistic/bow/update_icon_state()
. = ..()
- icon_state = chambered ? "[base_icon_state]_[drawn ? "drawn" : "nocked"]" : "[base_icon_state]"
+ icon_state = "[base_icon_state][drawn ? "_drawn" : ""]"
+
+/obj/item/gun/ballistic/bow/update_overlays()
+ . = ..()
+ if(chambered)
+ . += "[chambered.base_icon_state][drawn ? "_drawn" : ""]"
/obj/item/gun/ballistic/bow/click_alt(mob/user)
if(isnull(chambered))
@@ -72,6 +77,11 @@
return FALSE
return ..() //fires, removing the arrow
+/obj/item/gun/ballistic/bow/postfire_empty_checks(last_shot_succeeded)
+ if(!chambered && !get_ammo())
+ drawn = FALSE
+ update_appearance()
+
/obj/item/gun/ballistic/bow/equipped(mob/user, slot, initial)
. = ..()
if(slot != ITEM_SLOT_HANDS && chambered)
diff --git a/code/modules/projectiles/guns/ballistic/bows/bow_arrows.dm b/code/modules/projectiles/guns/ballistic/bows/bow_arrows.dm
index 22e441cd56b17..92c4f19e9b333 100644
--- a/code/modules/projectiles/guns/ballistic/bows/bow_arrows.dm
+++ b/code/modules/projectiles/guns/ballistic/bows/bow_arrows.dm
@@ -33,16 +33,17 @@
speed = 1
range = 25
shrapnel_type = null
- embedding = list(
- embed_chance = 90,
- fall_chance = 2,
- jostle_chance = 2,
- ignore_throwspeed_threshold = TRUE,
- pain_stam_pct = 0.5,
- pain_mult = 3,
- jostle_pain_mult = 3,
- rip_time = 1 SECONDS
- )
+ embed_type = /datum/embed_data/arrow
+
+/datum/embed_data/arrow
+ embed_chance = 90
+ fall_chance = 2
+ jostle_chance = 2
+ ignore_throwspeed_threshold = TRUE
+ pain_stam_pct = 0.5
+ pain_mult = 3
+ jostle_pain_mult = 3
+ rip_time = 1 SECONDS
/// holy arrows
/obj/item/ammo_casing/arrow/holy
@@ -58,22 +59,34 @@
name = "holy arrow"
desc = "Here it comes, cultist scum!"
icon_state = "holy_arrow_projectile"
- damage = 20 //still a lot but this is roundstart gear so far less
- embedding = list(
- embed_chance = 50,
- fall_chance = 2,
- jostle_chance = 0,
- ignore_throwspeed_threshold = TRUE,
- pain_stam_pct = 0.5,
- pain_mult = 3,
- rip_time = 1 SECONDS
- )
/obj/projectile/bullet/arrow/holy/Initialize(mapload)
. = ..()
//50 damage to revenants
AddElement(/datum/element/bane, target_type = /mob/living/basic/revenant, damage_multiplier = 0, added_damage = 30)
+/// plastic arrows
+// completely dogshit quality and they break when they hit something.
+/obj/item/ammo_casing/arrow/plastic
+ name = "plastic arrow"
+ desc = "The earliest mining teams within the Spinward Sector were the somewhat stout ancestors of the modern settlers. These teams \
+ found themselves often unable to access the quality materials they were digging up for equipment maintenance, all being sent off-site. \
+ Left with few options, and in need of a way to protect themselves in the hostile work enviroments of the Spinward, they turned \
+ to the one material they had in abundance."
+ icon_state = "plastic_arrow"
+ base_icon_state = "plastic_arrow"
+ projectile_type = /obj/projectile/bullet/arrow/plastic
+ reusable = FALSE //cheap shit
+
+/// plastic arrow projectile
+/obj/projectile/bullet/arrow/plastic
+ name = "plastic arrow"
+ desc = "If this is about to kill you, you should feel genuine shame."
+ damage = 5
+ stamina = 50
+ weak_against_armour = TRUE
+ icon_state = "plastic_arrow_projectile"
+
/// special pyre sect arrow
/// in the future, this needs a special sprite, but bows don't support non-hardcoded arrow sprites
/obj/item/ammo_casing/arrow/holy/blazing
@@ -87,7 +100,7 @@
desc = "THE UNMATCHED POWER OF THE SUN"
icon_state = "holy_arrow_projectile"
damage = 20
- embedding = null
+ embed_type = null
/obj/projectile/bullet/arrow/blazing/on_hit(atom/target, blocked, pierce_hit)
. = ..()
@@ -99,5 +112,5 @@
human_target.adjust_fire_stacks(2)
human_target.ignite_mob()
return
- to_chat(human_target, span_danger("[src] reacts with the flames on y-"))
+ to_chat(human_target, span_danger("[src] reacts with the flames enveloping you! Oh shit!"))
explosion(src, light_impact_range = 1, flame_range = 2) //ow
diff --git a/code/modules/projectiles/guns/ballistic/bows/bow_quivers.dm b/code/modules/projectiles/guns/ballistic/bows/bow_quivers.dm
index 07d7cc93ce16e..2492f0d4276f5 100644
--- a/code/modules/projectiles/guns/ballistic/bows/bow_quivers.dm
+++ b/code/modules/projectiles/guns/ballistic/bows/bow_quivers.dm
@@ -8,6 +8,7 @@
worn_icon_state = "harpoon_quiver"
/// type of arrow the quivel should hold
var/arrow_path = /obj/item/ammo_casing/arrow
+ var/max_slots = 40
/obj/item/storage/bag/quiver/Initialize(mapload)
. = ..()
@@ -17,7 +18,10 @@
atom_storage.max_total_storage = 100
atom_storage.set_holdable(/obj/item/ammo_casing/arrow)
-/obj/item/storage/bag/quiver/PopulateContents()
+/obj/item/storage/bag/quiver/lesser
+ max_slots = 10
+
+/obj/item/storage/bag/quiver/full/PopulateContents()
. = ..()
for(var/i in 1 to 10)
new arrow_path(src)
@@ -29,3 +33,8 @@
inhand_icon_state = "holyquiver"
worn_icon_state = "holyquiver"
arrow_path = /obj/item/ammo_casing/arrow/holy
+
+/obj/item/storage/bag/quiver/holy/PopulateContents()
+ . = ..()
+ for(var/i in 1 to 10)
+ new arrow_path(src)
diff --git a/code/modules/projectiles/guns/ballistic/bows/bow_types.dm b/code/modules/projectiles/guns/ballistic/bows/bow_types.dm
index b9ac1af0cca12..83b3bc43dd174 100644
--- a/code/modules/projectiles/guns/ballistic/bows/bow_types.dm
+++ b/code/modules/projectiles/guns/ballistic/bows/bow_types.dm
@@ -4,6 +4,12 @@
name = "longbow"
desc = "While pretty finely crafted, surely you can find something better to use in the current year."
+/// Shortbow, made via the crafting recipe
+/obj/item/gun/ballistic/bow/shortbow
+ name = "shortbow"
+ desc = "A simple homemade shortbow. Great for LARPing. Or poking out someones eye."
+ projectile_damage_multiplier = 0.36
+
///chaplain's divine archer bow
/obj/item/gun/ballistic/bow/divine
name = "divine bow"
@@ -15,6 +21,7 @@
slot_flags = ITEM_SLOT_BACK
obj_flags = UNIQUE_RENAME
accepted_magazine_type = /obj/item/ammo_box/magazine/internal/bow/holy
+ projectile_damage_multiplier = 0.4
/obj/item/ammo_box/magazine/internal/bow/holy
name = "divine bowstring"
diff --git a/code/modules/projectiles/guns/ballistic/rifle.dm b/code/modules/projectiles/guns/ballistic/rifle.dm
index a8161ddb39612..8604e21b32503 100644
--- a/code/modules/projectiles/guns/ballistic/rifle.dm
+++ b/code/modules/projectiles/guns/ballistic/rifle.dm
@@ -54,9 +54,6 @@
slot_flags = ITEM_SLOT_BACK
accepted_magazine_type = /obj/item/ammo_box/magazine/internal/boltaction
- can_bayonet = TRUE
- knife_x_offset = 42
- knife_y_offset = 12
can_be_sawn_off = TRUE
weapon_weight = WEAPON_HEAVY
var/jamming_chance = 20
@@ -67,11 +64,13 @@
SET_BASE_PIXEL(-8, 0)
+/obj/item/gun/ballistic/rifle/boltaction/add_bayonet_point()
+ AddComponent(/datum/component/bayonet_attachable, offset_x = 32, offset_y = 12)
+
/obj/item/gun/ballistic/rifle/boltaction/sawoff(mob/user)
. = ..()
if(.)
spread = 36
- can_bayonet = FALSE
SET_BASE_PIXEL(0, 0)
update_appearance()
@@ -160,6 +159,7 @@
You are now probably one of the few people in the universe to ever hold an \"Obrez Moderna\". \
All you had to do was take an allen wrench to the stock to take it off. But no, you just had to \
go for the saw."
+ accepted_magazine_type = /obj/item/ammo_box/magazine/internal/boltaction/phasic
/obj/item/gun/ballistic/rifle/boltaction/prime/Initialize(mapload)
. = ..()
@@ -271,14 +271,14 @@
projectile_damage_multiplier = 1.35
obj_flags = UNIQUE_RENAME
- can_bayonet = TRUE
- knife_x_offset = 35
- knife_y_offset = 10
can_be_sawn_off = FALSE
trigger_guard = TRIGGER_GUARD_ALLOW_ALL
SET_BASE_PIXEL(-8, 0)
+/obj/item/gun/ballistic/rifle/boltaction/pipegun/add_bayonet_point()
+ AddComponent(/datum/component/bayonet_attachable, offset_x = 35, offset_y = 10)
+
/obj/item/gun/ballistic/rifle/boltaction/pipegun/handle_chamber()
. = ..()
do_sparks(1, TRUE, src)
@@ -305,11 +305,13 @@
spread = 15 //kinda inaccurate
slot_flags = ITEM_SLOT_BELT
w_class = WEIGHT_CLASS_NORMAL
- can_bayonet = FALSE
weapon_weight = WEAPON_MEDIUM
SET_BASE_PIXEL(0, 0)
+/obj/item/gun/ballistic/rifle/boltaction/pipegun/pipepistol/add_bayonet_point()
+ return
+
/obj/item/gun/ballistic/rifle/boltaction/pipegun/prime
name = "regal pipegun"
desc = "To call this 'regal' is a cruel irony. For the only noteworthy quality of nobility is in how it is wielded to kill. \
diff --git a/code/modules/projectiles/guns/energy/crank_guns.dm b/code/modules/projectiles/guns/energy/crank_guns.dm
index 64ffa86f36015..fa56075990ec5 100644
--- a/code/modules/projectiles/guns/energy/crank_guns.dm
+++ b/code/modules/projectiles/guns/energy/crank_guns.dm
@@ -7,9 +7,9 @@
ammo_type = list(/obj/item/ammo_casing/energy/laser/musket)
slot_flags = ITEM_SLOT_BACK
obj_flags = UNIQUE_RENAME
- can_bayonet = TRUE
- knife_x_offset = 22
- knife_y_offset = 11
+
+/obj/item/gun/energy/laser/musket/add_bayonet_point()
+ AddComponent(/datum/component/bayonet_attachable, offset_x = 22, offset_y = 11)
/obj/item/gun/energy/laser/musket/Initialize(mapload)
. = ..()
@@ -84,12 +84,12 @@
shaded_charge = TRUE
ammo_x_offset = 1
obj_flags = UNIQUE_RENAME
- can_bayonet = TRUE
- knife_x_offset = 19
- knife_y_offset = 13
w_class = WEIGHT_CLASS_NORMAL
dual_wield_spread = 5 //as intended by the coders
+/obj/item/gun/energy/laser/thermal/add_bayonet_point()
+ AddComponent(/datum/component/bayonet_attachable, offset_x = 19, offset_y = 13)
+
/obj/item/gun/energy/laser/thermal/Initialize(mapload)
. = ..()
AddElement(/datum/element/empprotection, EMP_PROTECT_SELF|EMP_PROTECT_CONTENTS)
diff --git a/code/modules/projectiles/guns/energy/energy_gun.dm b/code/modules/projectiles/guns/energy/energy_gun.dm
index 3ce3338bf8749..e826e1392bf28 100644
--- a/code/modules/projectiles/guns/energy/energy_gun.dm
+++ b/code/modules/projectiles/guns/energy/energy_gun.dm
@@ -16,8 +16,8 @@
return
var/static/list/slapcraft_recipe_list = list(/datum/crafting_recipe/advancedegun, /datum/crafting_recipe/tempgun, /datum/crafting_recipe/beam_rifle)
- AddComponent(
- /datum/component/slapcrafting,\
+ AddElement(
+ /datum/element/slapcrafting,\
slapcraft_recipes = slapcraft_recipe_list,\
)
diff --git a/code/modules/projectiles/guns/energy/kinetic_accelerator.dm b/code/modules/projectiles/guns/energy/kinetic_accelerator.dm
index 7237ee0e32747..ab023d38b55ef 100644
--- a/code/modules/projectiles/guns/energy/kinetic_accelerator.dm
+++ b/code/modules/projectiles/guns/energy/kinetic_accelerator.dm
@@ -9,9 +9,6 @@
obj_flags = UNIQUE_RENAME
resistance_flags = FIRE_PROOF
weapon_weight = WEAPON_LIGHT
- can_bayonet = TRUE
- knife_x_offset = 20
- knife_y_offset = 12
gun_flags = NOT_A_REAL_GUN
///List of all mobs that projectiles fired from this gun will ignore.
var/list/ignored_mob_types
@@ -20,6 +17,9 @@
///The max capacity of modkits the PKA can have installed at once.
var/max_mod_capacity = 100
+/obj/item/gun/energy/recharge/kinetic_accelerator/add_bayonet_point()
+ AddComponent(/datum/component/bayonet_attachable, offset_x = 20, offset_y = 12)
+
/obj/item/gun/energy/recharge/kinetic_accelerator/Initialize(mapload)
. = ..()
// Only actual KAs can be converted
@@ -27,8 +27,8 @@
return
var/static/list/slapcraft_recipe_list = list(/datum/crafting_recipe/ebow)
- AddComponent(
- /datum/component/slapcrafting,\
+ AddElement(
+ /datum/element/slapcrafting,\
slapcraft_recipes = slapcraft_recipe_list,\
)
diff --git a/code/modules/projectiles/guns/energy/laser.dm b/code/modules/projectiles/guns/energy/laser.dm
index 95baca98db80f..90dc0c5717275 100644
--- a/code/modules/projectiles/guns/energy/laser.dm
+++ b/code/modules/projectiles/guns/energy/laser.dm
@@ -16,8 +16,8 @@
return
var/static/list/slapcraft_recipe_list = list(/datum/crafting_recipe/xraylaser, /datum/crafting_recipe/hellgun, /datum/crafting_recipe/ioncarbine)
- AddComponent(
- /datum/component/slapcrafting,\
+ AddElement(
+ /datum/element/slapcrafting,\
slapcraft_recipes = slapcraft_recipe_list,\
)
diff --git a/code/modules/projectiles/guns/energy/pulse.dm b/code/modules/projectiles/guns/energy/pulse.dm
index 0635de8037adf..839689144c2f1 100644
--- a/code/modules/projectiles/guns/energy/pulse.dm
+++ b/code/modules/projectiles/guns/energy/pulse.dm
@@ -59,6 +59,9 @@
/obj/item/gun/energy/pulse/carbine/loyalpin
pin = /obj/item/firing_pin/implant/mindshield
+/obj/item/gun/energy/pulse/carbine/taserless
+ ammo_type = list(/obj/item/ammo_casing/energy/laser/pulse, /obj/item/ammo_casing/energy/laser)
+
/obj/item/gun/energy/pulse/destroyer
name = "pulse destroyer"
desc = "A heavy-duty energy rifle built for pure destruction."
@@ -79,6 +82,9 @@
inhand_icon_state = "gun"
cell_type = /obj/item/stock_parts/power_store/cell/pulse/pistol
+/obj/item/gun/energy/pulse/pistol/taserless
+ ammo_type = list(/obj/item/ammo_casing/energy/laser/pulse, /obj/item/ammo_casing/energy/laser)
+
/obj/item/gun/energy/pulse/pistol/loyalpin
pin = /obj/item/firing_pin/implant/mindshield
diff --git a/code/modules/projectiles/guns/energy/recharge.dm b/code/modules/projectiles/guns/energy/recharge.dm
index 0aa14b48c32f6..504dc5c9e0f4a 100644
--- a/code/modules/projectiles/guns/energy/recharge.dm
+++ b/code/modules/projectiles/guns/energy/recharge.dm
@@ -111,9 +111,9 @@
recharge_time = 2 SECONDS
holds_charge = TRUE
unique_frequency = TRUE
- can_bayonet = TRUE
- knife_x_offset = 20
- knife_y_offset = 12
+
+/obj/item/gun/energy/recharge/ebow/add_bayonet_point()
+ AddComponent(/datum/component/bayonet_attachable, offset_x = 20, offset_y = 12)
/obj/item/gun/energy/recharge/ebow/halloween
name = "candy corn crossbow"
diff --git a/code/modules/projectiles/guns/energy/special.dm b/code/modules/projectiles/guns/energy/special.dm
index 653cffcbeec7d..961d32c96ee8b 100644
--- a/code/modules/projectiles/guns/energy/special.dm
+++ b/code/modules/projectiles/guns/energy/special.dm
@@ -428,3 +428,21 @@
new_coin.preparePixelProjectile(target_turf, user)
new_coin.fire()
return ITEM_INTERACT_SUCCESS
+
+/obj/item/gun/energy/photon
+ name = "photon cannon"
+ desc = "A competitive design to the tesla cannon, that instead of charging latent electrons, releases energy into photons. Eye protection is recommended."
+ icon_state = "photon"
+ inhand_icon_state = "tesla"
+ fire_sound = 'sound/weapons/lasercannonfire.ogg'
+ ammo_type = list(/obj/item/ammo_casing/energy/photon)
+ shaded_charge = TRUE
+ weapon_weight = WEAPON_HEAVY
+ light_color = LIGHT_COLOR_DEFAULT
+ light_system = OVERLAY_LIGHT
+ light_power = 2
+ light_range = 1
+
+/obj/item/gun/energy/photon/Initialize(mapload)
+ . = ..()
+ set_light_on(TRUE) // The gun quite literally shoots mini-suns.
diff --git a/code/modules/projectiles/guns/special/hand_of_midas.dm b/code/modules/projectiles/guns/special/hand_of_midas.dm
index 68b8158c32d9f..e92ffe8d0bf22 100644
--- a/code/modules/projectiles/guns/special/hand_of_midas.dm
+++ b/code/modules/projectiles/guns/special/hand_of_midas.dm
@@ -37,20 +37,26 @@
balloon_alert(user, "not enough gold")
// Siphon gold from a victim, recharging our gun & removing their Midas Blight debuff in the process.
+/obj/item/gun/magic/midas_hand/interact_with_atom_secondary(atom/interacting_with, mob/living/user, list/modifiers)
+ if(!isliving(interacting_with))
+ return ITEM_INTERACT_BLOCKING
+ return suck_gold(interacting_with, user)
+
/obj/item/gun/magic/midas_hand/ranged_interact_with_atom_secondary(atom/interacting_with, mob/living/user, list/modifiers)
if(!isliving(interacting_with) || !IN_GIVEN_RANGE(user, interacting_with, gold_suck_range))
return ITEM_INTERACT_BLOCKING
- if(interacting_with == user)
+ return suck_gold(interacting_with, user)
+
+/obj/item/gun/magic/midas_hand/proc/suck_gold(mob/living/victim, mob/living/user)
+ if(victim == user)
balloon_alert(user, "can't siphon from self!")
return ITEM_INTERACT_BLOCKING
- if(!interacting_with.reagents)
+ if(!victim.reagents)
return ITEM_INTERACT_BLOCKING
-
- var/gold_amount = interacting_with.reagents.get_reagent_amount(/datum/reagent/gold, type_check = REAGENT_SUB_TYPE)
+ var/gold_amount = victim.reagents.get_reagent_amount(/datum/reagent/gold, type_check = REAGENT_SUB_TYPE)
if(!gold_amount)
balloon_alert(user, "no gold in bloodstream!")
return ITEM_INTERACT_BLOCKING
- var/mob/living/victim = interacting_with
var/gold_beam = user.Beam(victim, icon_state = "drain_gold")
if(!do_after(
user = user,
diff --git a/code/modules/projectiles/projectile.dm b/code/modules/projectiles/projectile.dm
index 013cc6e4b294c..7870c10e9ef4a 100644
--- a/code/modules/projectiles/projectile.dm
+++ b/code/modules/projectiles/projectile.dm
@@ -191,7 +191,9 @@
///If defined, on hit we create an item of this type then call hitby() on the hit target with this, mainly used for embedding items (bullets) in targets
var/shrapnel_type
///If we have a shrapnel_type defined, these embedding stats will be passed to the spawned shrapnel type, which will roll for embedding on the target
- var/list/embedding
+ var/embed_type
+ ///Saves embedding data
+ var/datum/embed_data/embed_data
///If TRUE, hit mobs, even if they are lying on the floor and are not our target within MAX_RANGE_HIT_PRONE_TARGETS tiles
var/hit_prone_targets = FALSE
///if TRUE, ignores the range of MAX_RANGE_HIT_PRONE_TARGETS tiles of hit_prone_targets
@@ -218,8 +220,8 @@
/obj/projectile/Initialize(mapload)
. = ..()
decayedRange = range
- if(embedding)
- updateEmbedding()
+ if(get_embed())
+ AddElement(/datum/element/embed)
AddElement(/datum/element/connect_loc, projectile_connections)
/obj/projectile/proc/Range()
@@ -227,8 +229,8 @@
if(wound_bonus != CANT_WOUND)
wound_bonus += wound_falloff_tile
bare_wound_bonus = max(0, bare_wound_bonus + wound_falloff_tile)
- if(embedding)
- embedding["embed_chance"] += embed_falloff_tile
+ if(get_embed())
+ set_embed(embed_data.generate_with_values(embed_data.embed_chance + embed_falloff_tile)) // Should be rewritten in projecitle refactor
if(damage_falloff_tile && damage >= 0)
damage += damage_falloff_tile
if(stamina_falloff_tile && stamina >= 0)
@@ -1130,26 +1132,6 @@
/obj/projectile/experience_pressure_difference()
return
-///Like [/obj/item/proc/updateEmbedding] but for projectiles instead, call this when you want to add embedding or update the stats on the embedding element
-/obj/projectile/proc/updateEmbedding()
- if(!shrapnel_type || !LAZYLEN(embedding))
- return
-
- AddElement(/datum/element/embed,\
- embed_chance = (!isnull(embedding["embed_chance"]) ? embedding["embed_chance"] : EMBED_CHANCE),\
- fall_chance = (!isnull(embedding["fall_chance"]) ? embedding["fall_chance"] : EMBEDDED_ITEM_FALLOUT),\
- pain_chance = (!isnull(embedding["pain_chance"]) ? embedding["pain_chance"] : EMBEDDED_PAIN_CHANCE),\
- pain_mult = (!isnull(embedding["pain_mult"]) ? embedding["pain_mult"] : EMBEDDED_PAIN_MULTIPLIER),\
- remove_pain_mult = (!isnull(embedding["remove_pain_mult"]) ? embedding["remove_pain_mult"] : EMBEDDED_UNSAFE_REMOVAL_PAIN_MULTIPLIER),\
- rip_time = (!isnull(embedding["rip_time"]) ? embedding["rip_time"] : EMBEDDED_UNSAFE_REMOVAL_TIME),\
- ignore_throwspeed_threshold = (!isnull(embedding["ignore_throwspeed_threshold"]) ? embedding["ignore_throwspeed_threshold"] : FALSE),\
- impact_pain_mult = (!isnull(embedding["impact_pain_mult"]) ? embedding["impact_pain_mult"] : EMBEDDED_IMPACT_PAIN_MULTIPLIER),\
- jostle_chance = (!isnull(embedding["jostle_chance"]) ? embedding["jostle_chance"] : EMBEDDED_JOSTLE_CHANCE),\
- jostle_pain_mult = (!isnull(embedding["jostle_pain_mult"]) ? embedding["jostle_pain_mult"] : EMBEDDED_JOSTLE_PAIN_MULTIPLIER),\
- pain_stam_pct = (!isnull(embedding["pain_stam_pct"]) ? embedding["pain_stam_pct"] : EMBEDDED_PAIN_STAM_PCT),\
- projectile_payload = shrapnel_type)
- return TRUE
-
/**
* Is this projectile considered "hostile"?
*
@@ -1169,7 +1151,7 @@
///Checks if the projectile can embed into someone
/obj/projectile/proc/can_embed_into(atom/hit)
- return embedding && shrapnel_type && iscarbon(hit) && !HAS_TRAIT(hit, TRAIT_PIERCEIMMUNE)
+ return get_embed() && shrapnel_type && iscarbon(hit) && !HAS_TRAIT(hit, TRAIT_PIERCEIMMUNE)
/// Reflects the projectile off of something
/obj/projectile/proc/reflect(atom/hit_atom)
@@ -1212,3 +1194,17 @@
bullet.preparePixelProjectile(target, src)
bullet.fire()
return bullet
+
+/// Fetches embedding data
+/obj/projectile/proc/get_embed()
+ RETURN_TYPE(/datum/embed_data)
+ return embed_type ? (embed_data ||= get_embed_by_type(embed_type)) : embed_data
+
+/obj/projectile/proc/set_embed(datum/embed_data/embed)
+ if(embed_data == embed)
+ return
+ // GLOB.embed_by_type stores shared "default" embedding values of datums
+ // Dynamically generated embeds use the base class and thus are not present in there, and should be qdeleted upon being discarded
+ if(!isnull(embed_data) && !GLOB.embed_by_type[embed_data.type])
+ qdel(embed_data)
+ embed_data = ispath(embed) ? get_embed_by_type(armor) : embed
diff --git a/code/modules/projectiles/projectile/bullets.dm b/code/modules/projectiles/projectile/bullets.dm
index 5e938c4995390..1d1313d9e5551 100644
--- a/code/modules/projectiles/projectile/bullets.dm
+++ b/code/modules/projectiles/projectile/bullets.dm
@@ -8,7 +8,7 @@
sharpness = SHARP_POINTY
impact_effect_type = /obj/effect/temp_visual/impact_effect
shrapnel_type = /obj/item/shrapnel/bullet
- embedding = list(embed_chance=20, fall_chance=2, jostle_chance=0, ignore_throwspeed_threshold=TRUE, pain_stam_pct=0.5, pain_mult=3, rip_time=10)
+ embed_type = /datum/embed_data/bullet
wound_bonus = 0
wound_falloff_tile = -5
embed_falloff_tile = -3
@@ -16,3 +16,12 @@
/obj/projectile/bullet/smite
name = "divine retribution"
damage = 10
+
+/datum/embed_data/bullet
+ embed_chance=20
+ fall_chance=2
+ jostle_chance=0
+ ignore_throwspeed_threshold=TRUE
+ pain_stam_pct=0.5
+ pain_mult=3
+ rip_time=10
diff --git a/code/modules/projectiles/projectile/bullets/_incendiary.dm b/code/modules/projectiles/projectile/bullets/_incendiary.dm
index 85c2dce80c51a..e151afa294d50 100644
--- a/code/modules/projectiles/projectile/bullets/_incendiary.dm
+++ b/code/modules/projectiles/projectile/bullets/_incendiary.dm
@@ -30,7 +30,7 @@
pass_flags = PASSTABLE | PASSMOB
sharpness = NONE
shrapnel_type = null
- embedding = null
+ embed_type = null
impact_effect_type = null
suppressed = SUPPRESSED_VERY
damage_type = BURN
diff --git a/code/modules/projectiles/projectile/bullets/cannonball.dm b/code/modules/projectiles/projectile/bullets/cannonball.dm
index 1d6108ed33fd2..358a23eb63c90 100644
--- a/code/modules/projectiles/projectile/bullets/cannonball.dm
+++ b/code/modules/projectiles/projectile/bullets/cannonball.dm
@@ -8,7 +8,7 @@
dismemberment = 0
paralyze = 5 SECONDS
stutter = 20 SECONDS
- embedding = null
+ embed_type = null
hitsound = 'sound/effects/meteorimpact.ogg'
hitsound_wall = 'sound/weapons/sonic_jackhammer.ogg'
/// If our cannonball hits something, it reduces the damage by this value.
diff --git a/code/modules/projectiles/projectile/bullets/dart_syringe.dm b/code/modules/projectiles/projectile/bullets/dart_syringe.dm
index 405552a8909c2..1bd88d75fdea0 100644
--- a/code/modules/projectiles/projectile/bullets/dart_syringe.dm
+++ b/code/modules/projectiles/projectile/bullets/dart_syringe.dm
@@ -2,7 +2,7 @@
name = "dart"
icon_state = "cbbolt"
damage = 6
- embedding = null
+ embed_type = null
shrapnel_type = null
var/inject_flags = null
diff --git a/code/modules/projectiles/projectile/bullets/dnainjector.dm b/code/modules/projectiles/projectile/bullets/dnainjector.dm
index fdb051e7f8006..b4029b07f2da6 100644
--- a/code/modules/projectiles/projectile/bullets/dnainjector.dm
+++ b/code/modules/projectiles/projectile/bullets/dnainjector.dm
@@ -4,7 +4,7 @@
var/obj/item/dnainjector/injector
damage = 5
hitsound_wall = SFX_SHATTER
- embedding = null
+ embed_type = null
shrapnel_type = null
/obj/projectile/bullet/dnainjector/on_hit(atom/target, blocked = 0, pierce_hit)
diff --git a/code/modules/projectiles/projectile/bullets/foam_dart.dm b/code/modules/projectiles/projectile/bullets/foam_dart.dm
index 3f086166e6a88..7eaa1ce6c8ee4 100644
--- a/code/modules/projectiles/projectile/bullets/foam_dart.dm
+++ b/code/modules/projectiles/projectile/bullets/foam_dart.dm
@@ -8,7 +8,7 @@
base_icon_state = "foamdart"
range = 10
shrapnel_type = null
- embedding = null
+ embed_type = null
var/modified = FALSE
var/obj/item/pen/pen = null
diff --git a/code/modules/projectiles/projectile/bullets/grenade.dm b/code/modules/projectiles/projectile/bullets/grenade.dm
index a99a7b57ff3ec..0814a844be4a7 100644
--- a/code/modules/projectiles/projectile/bullets/grenade.dm
+++ b/code/modules/projectiles/projectile/bullets/grenade.dm
@@ -5,7 +5,7 @@
desc = "USE A WEEL GUN"
icon_state= "bolter"
damage = 60
- embedding = null
+ embed_type = null
shrapnel_type = null
/obj/projectile/bullet/a40mm/on_hit(atom/target, blocked = 0, pierce_hit)
diff --git a/code/modules/projectiles/projectile/bullets/junk.dm b/code/modules/projectiles/projectile/bullets/junk.dm
index 344a732911347..285c3ea281509 100644
--- a/code/modules/projectiles/projectile/bullets/junk.dm
+++ b/code/modules/projectiles/projectile/bullets/junk.dm
@@ -4,11 +4,21 @@
name = "junk bullet"
icon_state = "trashball"
damage = 30
- embedding = list(embed_chance=15, fall_chance=3, jostle_chance=4, ignore_throwspeed_threshold=TRUE, pain_stam_pct=0.4, pain_mult=5, jostle_pain_mult=6, rip_time=10)
+ embed_type = /datum/embed_data/bullet_junk
var/bane_mob_biotypes = MOB_ROBOTIC
var/bane_multiplier = 1.5
var/bane_added_damage = 0
+/datum/embed_data/bullet_junk
+ embed_chance=15
+ fall_chance=3
+ jostle_chance=4
+ ignore_throwspeed_threshold=TRUE
+ pain_stam_pct=0.4
+ pain_mult=5
+ jostle_pain_mult=6
+ rip_time=10
+
/obj/projectile/bullet/junk/Initialize(mapload)
. = ..()
AddElement(/datum/element/bane, mob_biotypes = bane_mob_biotypes, target_type = /mob/living, damage_multiplier = bane_multiplier, added_damage = bane_added_damage, requires_combat_mode = FALSE)
@@ -28,7 +38,7 @@
name = "bundle of live electrical parts"
icon_state = "tesla_projectile"
damage = 15
- embedding = null
+ embed_type = null
shrapnel_type = null
bane_multiplier = 3
@@ -49,10 +59,20 @@
name = "junk ripper bullet"
icon_state = "redtrac"
damage = 10
- embedding = list(embed_chance=100, fall_chance=3, jostle_chance=4, ignore_throwspeed_threshold=TRUE, pain_stam_pct=0.4, pain_mult=5, jostle_pain_mult=6, rip_time=10)
+ embed_type = /datum/embed_data/bullet_junk_ripper
wound_bonus = 10
bare_wound_bonus = 30
+/datum/embed_data/bullet_junk_ripper
+ embed_chance=100
+ fall_chance=3
+ jostle_chance=4
+ ignore_throwspeed_threshold=TRUE
+ pain_stam_pct=0.4
+ pain_mult=5
+ jostle_pain_mult=6
+ rip_time=10
+
/obj/projectile/bullet/junk/reaper
name = "junk reaper bullet"
tracer_type = /obj/effect/projectile/tracer/sniper
diff --git a/code/modules/projectiles/projectile/bullets/pistol.dm b/code/modules/projectiles/projectile/bullets/pistol.dm
index 8fccc510ff8fd..6bd355219f950 100644
--- a/code/modules/projectiles/projectile/bullets/pistol.dm
+++ b/code/modules/projectiles/projectile/bullets/pistol.dm
@@ -3,13 +3,23 @@
/obj/projectile/bullet/c9mm
name = "9mm bullet"
damage = 30
- embedding = list(embed_chance=15, fall_chance=3, jostle_chance=4, ignore_throwspeed_threshold=TRUE, pain_stam_pct=0.4, pain_mult=5, jostle_pain_mult=6, rip_time=10)
+ embed_type = /datum/embed_data/bullet_c9mm
+
+/datum/embed_data/bullet_c9mm
+ embed_chance=15
+ fall_chance=3
+ jostle_chance=4
+ ignore_throwspeed_threshold=TRUE
+ pain_stam_pct=0.4
+ pain_mult=5
+ jostle_pain_mult=6
+ rip_time=10
/obj/projectile/bullet/c9mm/ap
name = "9mm armor-piercing bullet"
damage = 27
armour_penetration = 40
- embedding = null
+ embed_type = null
shrapnel_type = null
/obj/projectile/bullet/c9mm/hp
diff --git a/code/modules/projectiles/projectile/bullets/revolver.dm b/code/modules/projectiles/projectile/bullets/revolver.dm
index 417f61534bcb7..e9d6389c89d0d 100644
--- a/code/modules/projectiles/projectile/bullets/revolver.dm
+++ b/code/modules/projectiles/projectile/bullets/revolver.dm
@@ -21,9 +21,19 @@
ricochet_auto_aim_range = 3
wound_bonus = -20
bare_wound_bonus = 10
- embedding = list(embed_chance=25, fall_chance=2, jostle_chance=2, ignore_throwspeed_threshold=TRUE, pain_stam_pct=0.4, pain_mult=3, jostle_pain_mult=5, rip_time=1 SECONDS)
+ embed_type = /datum/embed_data/bullet_c38
embed_falloff_tile = -4
+/datum/embed_data/bullet_c38
+ embed_chance=25
+ fall_chance=2
+ jostle_chance=2
+ ignore_throwspeed_threshold=TRUE
+ pain_stam_pct=0.4
+ pain_mult=3
+ jostle_pain_mult=5
+ rip_time=1 SECONDS
+
/obj/projectile/bullet/c38/match
name = ".38 Match bullet"
ricochets_max = 4
@@ -45,7 +55,7 @@
ricochet_decay_damage = 0.8
shrapnel_type = null
sharpness = NONE
- embedding = null
+ embed_type = null
// premium .38 ammo from cargo, weak against armor, lower base damage, but excellent at embedding and causing slice wounds at close range
/obj/projectile/bullet/c38/dumdum
@@ -56,10 +66,20 @@
sharpness = SHARP_EDGED
wound_bonus = 20
bare_wound_bonus = 20
- embedding = list(embed_chance=75, fall_chance=3, jostle_chance=4, ignore_throwspeed_threshold=TRUE, pain_stam_pct=0.4, pain_mult=5, jostle_pain_mult=6, rip_time=1 SECONDS)
+ embed_type = /datum/embed_data/bullet_c38_dumdum
wound_falloff_tile = -5
embed_falloff_tile = -15
+/datum/embed_data/bullet_c38_dumdum
+ embed_chance=75
+ fall_chance=3
+ jostle_chance=4
+ ignore_throwspeed_threshold=TRUE
+ pain_stam_pct=0.4
+ pain_mult=5
+ jostle_pain_mult=6
+ rip_time=1 SECONDS
+
/obj/projectile/bullet/c38/trac
name = ".38 TRAC bullet"
damage = 10
diff --git a/code/modules/projectiles/projectile/bullets/rifle.dm b/code/modules/projectiles/projectile/bullets/rifle.dm
index 3cbb894b8fb9e..6f14df2f4c8bd 100644
--- a/code/modules/projectiles/projectile/bullets/rifle.dm
+++ b/code/modules/projectiles/projectile/bullets/rifle.dm
@@ -35,6 +35,10 @@
damage = 20
stamina = 80
+/obj/projectile/bullet/strilka310/phasic
+ name = "phasic .310 bullet"
+ projectile_phasing = PASSTABLE | PASSGLASS | PASSGRILLE | PASSCLOSEDTURF | PASSMACHINE | PASSSTRUCTURE | PASSDOORS
+
// Harpoons (Harpoon Gun)
/obj/projectile/bullet/harpoon
@@ -44,10 +48,20 @@
armour_penetration = 50
wound_bonus = -20
bare_wound_bonus = 80
- embedding = list(embed_chance=100, fall_chance=3, jostle_chance=4, ignore_throwspeed_threshold=TRUE, pain_stam_pct=0.4, pain_mult=5, jostle_pain_mult=6, rip_time=10)
+ embed_type = /datum/embed_data/harpoon
wound_falloff_tile = -5
shrapnel_type = null
+/datum/embed_data/harpoon
+ embed_chance=100
+ fall_chance=3
+ jostle_chance=4
+ ignore_throwspeed_threshold=TRUE
+ pain_stam_pct=0.4
+ pain_mult=5
+ jostle_pain_mult=6
+ rip_time=10
+
// Rebar (Rebar Crossbow)
/obj/projectile/bullet/rebar
name = "rebar"
@@ -58,11 +72,21 @@
armour_penetration = 10
wound_bonus = -20
bare_wound_bonus = 20
- embedding = list("embed_chance" = 60, "fall_chance" = 2, "jostle_chance" = 2, "ignore_throwspeed_threshold" = TRUE, "pain_stam_pct" = 0.4, "pain_mult" = 4, "jostle_pain_mult" = 2, "rip_time" = 10)
+ embed_type = /datum/embed_data/rebar
embed_falloff_tile = -5
wound_falloff_tile = -2
shrapnel_type = /obj/item/ammo_casing/rebar
+/datum/embed_data/rebar
+ embed_chance = 60
+ fall_chance = 2
+ jostle_chance = 2
+ ignore_throwspeed_threshold = TRUE
+ pain_stam_pct = 0.4
+ pain_mult = 4
+ jostle_pain_mult = 2
+ rip_time = 10
+
/obj/projectile/bullet/rebar/proc/handle_drop(datum/source, obj/item/ammo_casing/rebar/newcasing)
/obj/projectile/bullet/rebar/syndie
@@ -74,10 +98,20 @@
armour_penetration = 20 //A bit better versus armor. Gets past anti laser armor or a vest, but doesnt wound proc on sec armor.
wound_bonus = 10
bare_wound_bonus = 20
- embedding = list("embed_chance" = 80, "fall_chance" = 1, "jostle_chance" = 3, "ignore_throwspeed_threshold" = TRUE, "pain_stam_pct" = 0.4, "pain_mult" = 3, "jostle_pain_mult" = 2, "rip_time" = 14)
embed_falloff_tile = -3
+ embed_type = /datum/embed_data/rebar_syndie
shrapnel_type = /obj/item/ammo_casing/rebar/syndie
+/datum/embed_data/rebar_syndie
+ embed_chance = 80
+ fall_chance = 1
+ jostle_chance = 3
+ ignore_throwspeed_threshold
+ pain_stam_pct = 0.4
+ pain_mult = 3
+ jostle_pain_mult = 2
+ rip_time = 14
+
/obj/projectile/bullet/rebar/zaukerite
name = "zaukerite shard"
icon_state = "rebar_zaukerite"
@@ -89,10 +123,20 @@
armour_penetration = 20 // not nearly as good, as its not as sharp.
wound_bonus = 10
bare_wound_bonus = 40
- embedding = list("embed_chance" =100, "fall_chance" = 0, "jostle_chance" = 5, "ignore_throwspeed_threshold" = TRUE, "pain_stam_pct" = 0.8, "pain_mult" = 6, "jostle_pain_mult" = 2, "rip_time" = 30)
+ embed_type = /datum/embed_data/rebar_zaukerite
embed_falloff_tile = 0 // very spiky.
shrapnel_type = /obj/item/ammo_casing/rebar/zaukerite
+/datum/embed_data/rebar_zaukerite
+ embed_chance = 100
+ fall_chance = 0
+ jostle_chance = 5
+ ignore_throwspeed_threshold = TRUE
+ pain_stam_pct = 0.8
+ pain_mult = 6
+ jostle_pain_mult = 2
+ rip_time = 30
+
/obj/projectile/bullet/rebar/hydrogen
name = "metallic hydrogen bolt"
icon_state = "rebar_hydrogen"
@@ -104,10 +148,20 @@
projectile_piercing = PASSMOB //felt this might have been a nice compromise for the lower damage for the difficulty of getting it
wound_bonus = -15
bare_wound_bonus = 10
- embedding = list("embed_chance" = 50, "fall_chance" = 2, "jostle_chance" = 3, "ignore_throwspeed_threshold" = TRUE, "pain_stam_pct" = 0.6, "pain_mult" = 4, "jostle_pain_mult" = 2, "rip_time" =18)
+ embed_type = /datum/embed_data/rebar_hydrogen
embed_falloff_tile = -3
shrapnel_type = /obj/item/ammo_casing/rebar/hydrogen
+/datum/embed_data/rebar_hydrogen
+ embed_chance = 50
+ fall_chance = 2
+ jostle_chance = 3
+ ignore_throwspeed_threshold = TRUE
+ pain_stam_pct = 0.6
+ pain_mult = 4
+ jostle_pain_mult = 2
+ rip_time =18
+
/obj/projectile/bullet/rebar/healium
name = "healium bolt"
icon_state = "rebar_healium"
@@ -118,7 +172,7 @@
armour_penetration = 100
wound_bonus = -100
bare_wound_bonus = -100
- embedding = list(embed_chance = 0)
+ embed_type = null
embed_falloff_tile = -3
shrapnel_type = /obj/item/ammo_casing/rebar/healium
@@ -135,7 +189,6 @@
return BULLET_ACT_HIT
-
/obj/projectile/bullet/rebar/supermatter
name = "supermatter bolt"
icon_state = "rebar_supermatter"
@@ -143,6 +196,7 @@
speed = 0.4
dismemberment = 0
damage_type = TOX
+ embed_type = null
armour_penetration = 100
shrapnel_type = /obj/item/ammo_casing/rebar/supermatter
@@ -160,7 +214,6 @@
return BULLET_ACT_HIT
-
/obj/projectile/bullet/rebar/supermatter/proc/dust_feedback(atom/target)
playsound(get_turf(src), 'sound/effects/supermatter.ogg', 10, TRUE)
visible_message(span_danger("[target] is hit by [src], turning [target.p_them()] to dust in a brilliant flash of light!"))
@@ -170,7 +223,7 @@
damage = 1 // It's a damn toy.
range = 10
shrapnel_type = null
- embedding = null
+ embed_type = null
name = "paper ball"
desc = "doink!"
damage_type = BRUTE
diff --git a/code/modules/projectiles/projectile/bullets/shotgun.dm b/code/modules/projectiles/projectile/bullets/shotgun.dm
index b3ddaf9bc9013..a013aaba691d7 100644
--- a/code/modules/projectiles/projectile/bullets/shotgun.dm
+++ b/code/modules/projectiles/projectile/bullets/shotgun.dm
@@ -22,7 +22,7 @@
stamina = 55
wound_bonus = 20
sharpness = NONE
- embedding = null
+ embed_type = null
/obj/projectile/bullet/shotgun_beanbag/a40mm
name = "rubber slug"
@@ -55,7 +55,7 @@
range = 7
icon_state = "spark"
color = COLOR_YELLOW
- embedding = null
+ embed_type = null
/obj/projectile/bullet/shotgun_frag12
name ="frag12 slug"
@@ -84,7 +84,7 @@
damage = 3
stamina = 11
sharpness = NONE
- embedding = null
+ embed_type = null
speed = 1.2
stamina_falloff_tile = -0.25
ricochets_max = 4
@@ -106,7 +106,7 @@
name = "incapacitating pellet"
damage = 1
stamina = 6
- embedding = null
+ embed_type = null
// Mech Scattershot
diff --git a/code/modules/projectiles/projectile/bullets/smg.dm b/code/modules/projectiles/projectile/bullets/smg.dm
index 8befcb690ecfa..ce78abf0662b4 100644
--- a/code/modules/projectiles/projectile/bullets/smg.dm
+++ b/code/modules/projectiles/projectile/bullets/smg.dm
@@ -33,7 +33,7 @@
name = "4.6x30mm armor-piercing bullet"
damage = 15
armour_penetration = 40
- embedding = null
+ embed_type = null
/obj/projectile/bullet/incendiary/c46x30mm
name = "4.6x30mm incendiary bullet"
diff --git a/code/modules/projectiles/projectile/bullets/special.dm b/code/modules/projectiles/projectile/bullets/special.dm
index f595c3e116510..2af0fe7b9cb9d 100644
--- a/code/modules/projectiles/projectile/bullets/special.dm
+++ b/code/modules/projectiles/projectile/bullets/special.dm
@@ -9,7 +9,7 @@
icon = 'icons/obj/service/hydroponics/harvest.dmi'
icon_state = "banana"
range = 200
- embedding = null
+ embed_type = null
shrapnel_type = null
/obj/projectile/bullet/honker/Initialize(mapload)
diff --git a/code/modules/projectiles/projectile/energy/photon.dm b/code/modules/projectiles/projectile/energy/photon.dm
new file mode 100644
index 0000000000000..7caac3e062e01
--- /dev/null
+++ b/code/modules/projectiles/projectile/energy/photon.dm
@@ -0,0 +1,58 @@
+#define MULTIPLY_PIXELSPEED 0.8
+
+/obj/projectile/energy/photon
+ name = "photon bolt"
+ icon_state = "solarflare"
+ damage_type = STAMINA
+ armor_flag = ENERGY
+ impact_effect_type = /obj/effect/temp_visual/impact_effect/blue_laser
+ damage = 5 //It's literally a weaker tesla bolt, which is already weak. Don't worry, we'll fix that.
+ range = 20
+ speed = 1
+ pixel_speed_multiplier = 1
+ projectile_piercing = PASSMOB
+ light_color = LIGHT_COLOR_DEFAULT
+ light_system = OVERLAY_LIGHT
+ light_power = 5
+ light_range = 6
+
+
+/obj/projectile/energy/photon/Initialize(mapload)
+ . = ..()
+ RegisterSignals(src, list(COMSIG_MOVABLE_CROSS, COMSIG_MOVABLE_CROSS_OVER), PROC_REF(blast_touched))
+ RegisterSignal(src, COMSIG_ATOM_ENTERED, PROC_REF(scorch_earth))
+ set_light_on(TRUE)
+
+/**
+ * Handle side effects for the phonon bolt.
+ * behaves like a higher power direct flash if hit, and sparks silicons like they're getting microwaved.
+ */
+/obj/projectile/energy/photon/proc/blast_touched(datum/source, atom/flashed)
+ SIGNAL_HANDLER
+ if(isliving(flashed))
+ var/mob/living/flashed_creature = flashed
+ flashed_creature.flash_act(intensity = 3, affect_silicon = TRUE, length = 6)
+ flashed_creature.adjust_confusion(1.5 SECONDS)
+ if(issilicon(flashed))
+ do_sparks(rand(1, 4), FALSE, src)
+
+/**
+ * When traveling to a new turf, throws a probability to generate a hotspot across it's path.
+ */
+/obj/projectile/energy/photon/proc/scorch_earth(turf/open/floor/source, atom/movable/arrived, atom/old_loc, list/atom/old_locs)
+ SIGNAL_HANDLER
+ if(prob(40))
+ new /obj/effect/hotspot(arrived)
+
+/obj/projectile/energy/photon/Range()
+ . = ..()
+ pixel_speed_multiplier *= MULTIPLY_PIXELSPEED
+
+/obj/projectile/energy/photon/on_range()
+ do_sparks(rand(4, 9), FALSE, src)
+ playsound(loc, 'sound/weapons/solarflare.ogg', 100, FALSE, 8, 0.9)
+ for(var/mob/living/flashed_mob in viewers(5, loc))
+ flashed_mob.flash_act()
+ return ..()
+
+#undef MULTIPLY_PIXELSPEED
diff --git a/code/modules/projectiles/projectile/special/rocket.dm b/code/modules/projectiles/projectile/special/rocket.dm
index db452385c987c..fe04fc7d9865e 100644
--- a/code/modules/projectiles/projectile/special/rocket.dm
+++ b/code/modules/projectiles/projectile/special/rocket.dm
@@ -2,7 +2,7 @@
name ="explosive bolt"
icon_state= "bolter"
damage = 50
- embedding = null
+ embed_type = null
shrapnel_type = null
/obj/projectile/bullet/gyro/on_hit(atom/target, blocked = 0, pierce_hit)
@@ -17,7 +17,7 @@
icon_state= "missile"
damage = 50
sharpness = NONE
- embedding = null
+ embed_type = null
shrapnel_type = null
ricochets_max = 0
/// Whether we do extra damage when hitting a mech or silicon
diff --git a/code/modules/projectiles/projectile/special/temperature.dm b/code/modules/projectiles/projectile/special/temperature.dm
index 2a8b6ca6b6938..182bb715466d3 100644
--- a/code/modules/projectiles/projectile/special/temperature.dm
+++ b/code/modules/projectiles/projectile/special/temperature.dm
@@ -61,3 +61,4 @@
var/turf/location = get_turf(src)
new /obj/effect/hotspot(location)
location.hotspot_expose(700, 50, 1)
+ return ..()
diff --git a/code/modules/reagents/chemistry/holder/holder.dm b/code/modules/reagents/chemistry/holder/holder.dm
index 3fe2e482f3efd..46d00e765a343 100644
--- a/code/modules/reagents/chemistry/holder/holder.dm
+++ b/code/modules/reagents/chemistry/holder/holder.dm
@@ -235,6 +235,7 @@
var/total_removed_amount = 0
var/remove_amount = 0
var/list/cached_reagents = reagent_list
+ var/list/removed_reagents = list()
for(var/datum/reagent/cached_reagent as anything in cached_reagents)
//check for specific type or subtypes
if(!include_subtypes)
@@ -243,19 +244,26 @@
else if(!istype(cached_reagent, reagent_type))
continue
+ //reduce the volume
remove_amount = min(cached_reagent.volume, amount)
cached_reagent.volume -= remove_amount
- update_total()
- if(!safety)//So it does not handle reactions when it need not to
- handle_reactions()
- SEND_SIGNAL(src, COMSIG_REAGENTS_REM_REAGENT, QDELING(cached_reagent) ? reagent_type : cached_reagent, amount)
-
+ //record the changes
+ removed_reagents += cached_reagent
total_removed_amount += remove_amount
//if we reached here means we have found our specific reagent type so break
if(!include_subtypes)
- return total_removed_amount
+ break
+
+ //inform others about our reagents being removed
+ for(var/datum/reagent/removed_reagent as anything in cached_reagents)
+ SEND_SIGNAL(src, COMSIG_REAGENTS_REM_REAGENT, removed_reagent, amount)
+
+ //update the holder & handle reactions
+ update_total()
+ if(!safety)
+ handle_reactions()
return round(total_removed_amount, CHEMICAL_VOLUME_ROUNDING)
@@ -432,8 +440,6 @@
target_holder = target.reagents
target_atom = target
- var/cached_amount = amount
-
// Prevents small amount problems, as well as zero and below zero amounts.
amount = round(min(amount, total_volume, target_holder.maximum_volume - target_holder.total_volume), CHEMICAL_QUANTISATION_LEVEL)
if(amount <= 0)
@@ -469,7 +475,7 @@
if(preserve_data)
trans_data = copy_data(reagent)
- if(reagent.intercept_reagents_transfer(target_holder, cached_amount))
+ if(reagent.intercept_reagents_transfer(target_holder, amount))
continue
transfered_amount = target_holder.add_reagent(reagent.type, transfer_amount * multiplier, trans_data, chem_temp, reagent.purity, reagent.ph, no_react = TRUE, ignore_splitting = reagent.chemical_flags & REAGENT_DONOTSPLIT) //we only handle reaction after every reagent has been transferred.
if(!transfered_amount)
@@ -504,8 +510,6 @@
log_target.add_hiddenprint(transferred_by) //log prints so admins can figure out who touched it last.
log_combat(transferred_by, log_target, "transferred reagents to", my_atom, "which had [get_external_reagent_log_string(transfer_log)]")
- update_total()
- target_holder.update_total()
if(!no_react)
target_holder.handle_reactions()
src.handle_reactions()
diff --git a/code/modules/reagents/chemistry/holder/reactions.dm b/code/modules/reagents/chemistry/holder/reactions.dm
index 80241173deebf..3fbcb57a43424 100644
--- a/code/modules/reagents/chemistry/holder/reactions.dm
+++ b/code/modules/reagents/chemistry/holder/reactions.dm
@@ -339,6 +339,7 @@
my_atom.visible_message(span_notice("[iconhtml] \The [my_atom]'s power is consumed in the reaction."))
extract.name = "used slime extract"
extract.desc = "This extract has been used up."
+ extract.grind_results.Cut()
//finish the reaction
selected_reaction.on_reaction(src, null, multiplier)
diff --git a/code/modules/reagents/chemistry/machinery/chem_dispenser.dm b/code/modules/reagents/chemistry/machinery/chem_dispenser.dm
index cbd6449269ccc..22e569af66021 100644
--- a/code/modules/reagents/chemistry/machinery/chem_dispenser.dm
+++ b/code/modules/reagents/chemistry/machinery/chem_dispenser.dm
@@ -278,6 +278,9 @@
var/datum/reagents/holder = beaker.reagents
var/to_dispense = max(0, min(amount, holder.maximum_volume - holder.total_volume))
+ if(!to_dispense)
+ say("The container is full!")
+ return
if(!cell.use(to_dispense * power_cost))
say("Not enough energy to complete operation!")
return
diff --git a/code/modules/reagents/chemistry/reagents.dm b/code/modules/reagents/chemistry/reagents.dm
index 11947c7060271..576d62585f7a8 100644
--- a/code/modules/reagents/chemistry/reagents.dm
+++ b/code/modules/reagents/chemistry/reagents.dm
@@ -250,7 +250,9 @@ Primarily used in reagents/reaction_agents
/// Should return a associative list where keys are taste descriptions and values are strength ratios
/datum/reagent/proc/get_taste_description(mob/living/taster)
- return list("[taste_description]" = 1)
+ if(isnull(taster) || !HAS_TRAIT(taster, TRAIT_DETECTIVES_TASTE))
+ return list("[taste_description]" = 1)
+ return list("[LOWER_TEXT(name)]" = 1)
/**
* Used when you want the default reagents purity to be equal to the normal effects
diff --git a/code/modules/reagents/chemistry/reagents/food_reagents.dm b/code/modules/reagents/chemistry/reagents/food_reagents.dm
index 2e45ecf641bc1..7c9042314458d 100644
--- a/code/modules/reagents/chemistry/reagents/food_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/food_reagents.dm
@@ -140,6 +140,7 @@
brute_heal = 0.8 //Rewards the player for eating a balanced diet.
nutriment_factor = 9 //45% as calorie dense as oil.
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
+ default_container = /obj/item/reagent_containers/condiment/protein
/datum/reagent/consumable/nutriment/fat
name = "Fat"
diff --git a/code/modules/reagents/chemistry/reagents/medicine_reagents.dm b/code/modules/reagents/chemistry/reagents/medicine_reagents.dm
index 505647fe332aa..df7f222a496fe 100644
--- a/code/modules/reagents/chemistry/reagents/medicine_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/medicine_reagents.dm
@@ -1379,7 +1379,7 @@
. = ..()
affected_mob.add_traits(list(TRAIT_SLEEPIMMUNE, TRAIT_BATON_RESISTANCE), type)
affected_mob.add_movespeed_mod_immunities(type, /datum/movespeed_modifier/damage_slowdown)
- RegisterSignal(affected_mob, COMSIG_CARBON_ENTER_STAMCRIT, PROC_REF(on_stamcrit))
+ RegisterSignal(affected_mob, COMSIG_LIVING_ENTER_STAMCRIT, PROC_REF(on_stamcrit))
/datum/reagent/medicine/changelingadrenaline/on_mob_end_metabolize(mob/living/affected_mob)
. = ..()
@@ -1387,7 +1387,7 @@
affected_mob.remove_movespeed_mod_immunities(type, /datum/movespeed_modifier/damage_slowdown)
affected_mob.remove_status_effect(/datum/status_effect/dizziness)
affected_mob.remove_status_effect(/datum/status_effect/jitter)
- UnregisterSignal(affected_mob, COMSIG_CARBON_ENTER_STAMCRIT)
+ UnregisterSignal(affected_mob, COMSIG_LIVING_ENTER_STAMCRIT)
/datum/reagent/medicine/changelingadrenaline/proc/on_stamcrit(mob/living/affected_mob)
SIGNAL_HANDLER
diff --git a/code/modules/reagents/chemistry/reagents/other_reagents.dm b/code/modules/reagents/chemistry/reagents/other_reagents.dm
index df14037ac1e8f..29822e3158bec 100644
--- a/code/modules/reagents/chemistry/reagents/other_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/other_reagents.dm
@@ -127,6 +127,16 @@
if(data["blood_DNA"])
bloodsplatter.add_blood_DNA(list(data["blood_DNA"] = data["blood_type"]))
+/datum/reagent/blood/get_taste_description(mob/living/taster)
+ if(isnull(taster))
+ return ..()
+ if(!HAS_TRAIT(taster, TRAIT_DETECTIVES_TASTE))
+ return ..()
+ var/blood_type = data?["blood_type"]
+ if(!blood_type)
+ return ..()
+ return list("[blood_type] type blood" = 1)
+
/datum/reagent/consumable/liquidgibs
name = "Liquid Gibs"
color = "#CC4633"
@@ -871,7 +881,7 @@
return
to_chat(affected_mob, span_warning("You grit your teeth in pain as your body rapidly mutates!"))
affected_mob.visible_message("[affected_mob] suddenly transforms!")
- randomize_human(affected_mob)
+ randomize_human_normie(affected_mob)
/datum/reagent/aslimetoxin
name = "Advanced Mutation Toxin"
diff --git a/code/modules/reagents/reagent_containers.dm b/code/modules/reagents/reagent_containers.dm
index e1d78353bca28..e95b5eea82089 100644
--- a/code/modules/reagents/reagent_containers.dm
+++ b/code/modules/reagents/reagent_containers.dm
@@ -266,9 +266,6 @@
reagents.expose_temperature(1000)
return ..() | COMPONENT_MICROWAVE_SUCCESS
-/obj/item/reagent_containers/fire_act(temperature, volume)
- reagents.expose_temperature(temperature)
-
/// Updates the icon of the container when the reagents change. Eats signal args
/obj/item/reagent_containers/proc/on_reagent_change(datum/reagents/holder, ...)
SIGNAL_HANDLER
diff --git a/code/modules/reagents/reagent_containers/condiment.dm b/code/modules/reagents/reagent_containers/condiment.dm
index ccabc2c410646..c8652afda8f0e 100644
--- a/code/modules/reagents/reagent_containers/condiment.dm
+++ b/code/modules/reagents/reagent_containers/condiment.dm
@@ -368,6 +368,13 @@
list_reagents = list(/datum/reagent/consumable/grounding_solution = 50)
fill_icon_thresholds = null
+/obj/item/reagent_containers/condiment/protein
+ name = "protein powder"
+ desc = "Fuel for your inner Hulk - because you can't spell 'swole' without 'whey'!"
+ icon_state = "protein"
+ list_reagents = list(/datum/reagent/consumable/nutriment/protein = 40)
+ fill_icon_thresholds = null
+
//technically condiment packs but they are non transparent
/obj/item/reagent_containers/condiment/creamer
diff --git a/code/modules/reagents/reagent_containers/cups/_cup.dm b/code/modules/reagents/reagent_containers/cups/_cup.dm
index fb3aa38271802..87df7765233ec 100644
--- a/code/modules/reagents/reagent_containers/cups/_cup.dm
+++ b/code/modules/reagents/reagent_containers/cups/_cup.dm
@@ -228,6 +228,8 @@
worn_icon_state = "beaker"
custom_materials = list(/datum/material/glass=SMALL_MATERIAL_AMOUNT*5)
fill_icon_thresholds = list(0, 1, 20, 40, 60, 80, 100)
+ pickup_sound = 'sound/items/handling/beaker_pickup.ogg'
+ drop_sound = 'sound/items/handling/beaker_place.ogg'
/obj/item/reagent_containers/cup/beaker/Initialize(mapload)
. = ..()
diff --git a/code/modules/reagents/reagent_containers/cups/glassbottle.dm b/code/modules/reagents/reagent_containers/cups/glassbottle.dm
index a293aac7aa1b1..b246cc01e07e9 100644
--- a/code/modules/reagents/reagent_containers/cups/glassbottle.dm
+++ b/code/modules/reagents/reagent_containers/cups/glassbottle.dm
@@ -42,9 +42,8 @@
/obj/item/reagent_containers/cup/glass/bottle/Initialize(mapload, vol)
. = ..()
- AddComponent(/datum/component/slapcrafting,\
- slapcraft_recipes = list(/datum/crafting_recipe/molotov)\
- )
+ var/static/list/recipes = list(/datum/crafting_recipe/molotov)
+ AddElement(/datum/element/slapcrafting, recipes)
/obj/item/reagent_containers/cup/glass/bottle/small
name = "small glass bottle"
diff --git a/code/modules/reagents/reagent_containers/cups/soda.dm b/code/modules/reagents/reagent_containers/cups/soda.dm
index 5bf0eb782c54d..2e85b2273f45e 100644
--- a/code/modules/reagents/reagent_containers/cups/soda.dm
+++ b/code/modules/reagents/reagent_containers/cups/soda.dm
@@ -23,9 +23,7 @@
/obj/item/reagent_containers/cup/soda_cans/Initialize(mapload, vol)
. = ..()
- AddComponent(/datum/component/slapcrafting,\
- slapcraft_recipes = list(/datum/crafting_recipe/improv_explosive)\
- )
+ AddElement(/datum/element/slapcrafting, string_list(list(/datum/crafting_recipe/improv_explosive)))
/obj/item/reagent_containers/cup/soda_cans/random/Initialize(mapload)
..()
diff --git a/code/modules/recycling/disposal/construction.dm b/code/modules/recycling/disposal/construction.dm
index 903a59a930557..13f43b1f4a419 100644
--- a/code/modules/recycling/disposal/construction.dm
+++ b/code/modules/recycling/disposal/construction.dm
@@ -109,7 +109,7 @@
var/turf/T = get_turf(src)
if(T.underfloor_accessibility < UNDERFLOOR_INTERACTABLE && isfloorturf(T))
- var/obj/item/crowbar/held_crowbar = user.is_holding_item_of_type(/obj/item/crowbar)
+ var/obj/item/crowbar/held_crowbar = user.is_holding_tool_quality(TOOL_CROWBAR)
if(!held_crowbar || !T.crowbar_act(user, held_crowbar))
to_chat(user, span_warning("You can only attach the [pipename] if the floor plating is removed!"))
return TRUE
diff --git a/code/modules/religion/burdened/psyker.dm b/code/modules/religion/burdened/psyker.dm
index c370424fa7d2c..b256b4f9d4ac3 100644
--- a/code/modules/religion/burdened/psyker.dm
+++ b/code/modules/religion/burdened/psyker.dm
@@ -252,7 +252,7 @@
ricochet_auto_aim_angle = 10
ricochet_auto_aim_range = 3
wound_bonus = -10
- embedding = null
+ embed_type = null
/obj/projectile/bullet/c38/holy/on_hit(atom/target, blocked, pierce_hit)
. = ..()
diff --git a/code/modules/research/designs/autolathe/engineering_designs.dm b/code/modules/research/designs/autolathe/engineering_designs.dm
index dc87b747959ca..c376a1ac1c55a 100644
--- a/code/modules/research/designs/autolathe/engineering_designs.dm
+++ b/code/modules/research/designs/autolathe/engineering_designs.dm
@@ -10,18 +10,6 @@
)
departmental_flags = DEPARTMENT_BITFLAG_ENGINEERING
-/datum/design/sparker
- name = "Sparker WallFrame"
- id = "sparker"
- build_type = PROTOLATHE | AWAY_LATHE | AUTOLATHE
- materials = list(/datum/material/iron = HALF_SHEET_MATERIAL_AMOUNT, /datum/material/glass = HALF_SHEET_MATERIAL_AMOUNT)
- build_path = /obj/item/wallframe/sparker
- category = list(
- RND_CATEGORY_INITIAL,
- RND_CATEGORY_CONSTRUCTION + RND_SUBCATEGORY_CONSTRUCTION_MOUNTS,
- )
- departmental_flags = DEPARTMENT_BITFLAG_ENGINEERING
-
/datum/design/tracker_electronics
name = "Solar Tracking Electronics"
id = "solar_tracker"
@@ -95,18 +83,6 @@
)
departmental_flags = DEPARTMENT_BITFLAG_ENGINEERING
-/datum/design/turret_control_frame
- name = "Turret Control Frame"
- id = "turret_control"
- build_type = AUTOLATHE | PROTOLATHE | AWAY_LATHE
- materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT*6)
- build_path = /obj/item/wallframe/turret_control
- category = list(
- RND_CATEGORY_INITIAL,
- RND_CATEGORY_CONSTRUCTION + RND_SUBCATEGORY_CONSTRUCTION_MOUNTS,
- )
- departmental_flags = DEPARTMENT_BITFLAG_ENGINEERING
-
/datum/design/large_welding_tool
name = "Industrial Welding Tool"
id = "large_welding_tool"
@@ -119,55 +95,6 @@
)
departmental_flags = DEPARTMENT_BITFLAG_ENGINEERING
-/datum/design/camera_assembly
- name = "Camera Assembly"
- id = "camera_assembly"
- build_type = AUTOLATHE | PROTOLATHE | AWAY_LATHE
- materials = list(/datum/material/iron = SMALL_MATERIAL_AMOUNT*4, /datum/material/glass = SMALL_MATERIAL_AMOUNT*2.5)
- build_path = /obj/item/wallframe/camera
- category = list(
- RND_CATEGORY_INITIAL,
- RND_CATEGORY_CONSTRUCTION + RND_SUBCATEGORY_CONSTRUCTION_MOUNTS,
- )
- departmental_flags = DEPARTMENT_BITFLAG_ENGINEERING
-
-/datum/design/newscaster_frame
- name = "Newscaster Frame"
- id = "newscaster_frame"
- build_type = AUTOLATHE | PROTOLATHE | AWAY_LATHE
- materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT*7, /datum/material/glass = SHEET_MATERIAL_AMOUNT*4)
- build_path = /obj/item/wallframe/newscaster
- category = list(
- RND_CATEGORY_INITIAL,
- RND_CATEGORY_CONSTRUCTION + RND_SUBCATEGORY_CONSTRUCTION_MOUNTS,
- )
- departmental_flags = DEPARTMENT_BITFLAG_ENGINEERING
-
-/datum/design/status_display_frame
- name = "Status Display Frame"
- id = "status_display_frame"
- build_type = AUTOLATHE | PROTOLATHE | AWAY_LATHE
- materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT*7, /datum/material/glass = SHEET_MATERIAL_AMOUNT*4)
- build_path = /obj/item/wallframe/status_display
- category = list(
- RND_CATEGORY_INITIAL,
- RND_CATEGORY_CONSTRUCTION + RND_SUBCATEGORY_CONSTRUCTION_MOUNTS,
- )
- departmental_flags = DEPARTMENT_BITFLAG_ENGINEERING
-
-
-/datum/design/intercom_frame
- name = "Intercom Frame"
- id = "intercom_frame"
- build_type = AUTOLATHE | PROTOLATHE | AWAY_LATHE
- materials = list(/datum/material/iron = SMALL_MATERIAL_AMOUNT*0.75, /datum/material/glass =SMALL_MATERIAL_AMOUNT*0.25)
- build_path = /obj/item/wallframe/intercom
- category = list(
- RND_CATEGORY_INITIAL,
- RND_CATEGORY_CONSTRUCTION + RND_SUBCATEGORY_CONSTRUCTION_MOUNTS,
- )
- departmental_flags = DEPARTMENT_BITFLAG_ENGINEERING
-
/datum/design/earmuffs
name = "Earmuffs"
id = "earmuffs"
@@ -323,130 +250,3 @@
RND_CATEGORY_TOOLS + RND_SUBCATEGORY_TOOLS_ATMOSPHERICS,
)
departmental_flags = DEPARTMENT_BITFLAG_ENGINEERING
-
-/datum/design/requests_console
- name = "Requests Console Frame"
- id = "requests_console"
- build_type = AUTOLATHE | PROTOLATHE
- materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT*7, /datum/material/glass = SHEET_MATERIAL_AMOUNT*4)
- build_path = /obj/item/wallframe/requests_console
- category = list(
- RND_CATEGORY_INITIAL,
- RND_CATEGORY_CONSTRUCTION + RND_SUBCATEGORY_CONSTRUCTION_MOUNTS
- )
- departmental_flags = DEPARTMENT_BITFLAG_ENGINEERING
-
-/datum/design/light_switch_frame
- name = "Light Switch Frame"
- id = "light_switch_frame"
- build_type = AUTOLATHE | PROTOLATHE | AWAY_LATHE
- materials = list(/datum/material/iron = SMALL_MATERIAL_AMOUNT*0.75, /datum/material/glass =SMALL_MATERIAL_AMOUNT*0.25)
- build_path = /obj/item/wallframe/light_switch
- category = list(
- RND_CATEGORY_INITIAL,
- RND_CATEGORY_CONSTRUCTION + RND_SUBCATEGORY_CONSTRUCTION_MOUNTS
- )
- departmental_flags = DEPARTMENT_BITFLAG_ENGINEERING
-
-/datum/design/telescreen_turbine
- name = "Turbine Telescreen"
- id = "telescreen_turbine"
- build_type = PROTOLATHE
- materials = list(
- /datum/material/iron = SHEET_MATERIAL_AMOUNT*5,
- /datum/material/glass =SHEET_MATERIAL_AMOUNT * 2.5,
- )
- build_path = /obj/item/wallframe/telescreen/turbine
- category = list(
- RND_CATEGORY_INITIAL,
- RND_CATEGORY_CONSTRUCTION + RND_SUBCATEGORY_CONSTRUCTION_MOUNTS,
- )
- departmental_flags = DEPARTMENT_BITFLAG_ENGINEERING
-
-/datum/design/telescreen_engine
- name = "Engine Telescreen"
- id = "telescreen_engine"
- build_type = PROTOLATHE
- materials = list(
- /datum/material/iron = SHEET_MATERIAL_AMOUNT*5,
- /datum/material/glass =SHEET_MATERIAL_AMOUNT * 2.5,
- )
- build_path = /obj/item/wallframe/telescreen/engine
- category = list(
- RND_CATEGORY_INITIAL,
- RND_CATEGORY_CONSTRUCTION + RND_SUBCATEGORY_CONSTRUCTION_MOUNTS,
- )
- departmental_flags = DEPARTMENT_BITFLAG_ENGINEERING
-
-/datum/design/telescreen_auxbase
- name = "Auxiliary Base Telescreen"
- id = "telescreen_auxbase"
- build_type = PROTOLATHE
- materials = list(
- /datum/material/iron = SHEET_MATERIAL_AMOUNT*5,
- /datum/material/glass =SHEET_MATERIAL_AMOUNT * 2.5,
- )
- build_path = /obj/item/wallframe/telescreen/auxbase
- category = list(
- RND_CATEGORY_INITIAL,
- RND_CATEGORY_CONSTRUCTION + RND_SUBCATEGORY_CONSTRUCTION_MOUNTS,
- )
- departmental_flags = DEPARTMENT_BITFLAG_ENGINEERING
-
-/datum/design/tram_controller
- name = "Tram Controller Cabinet"
- id = "tram_controller"
- build_type = PROTOLATHE
- materials = list(
- /datum/material/titanium = SHEET_MATERIAL_AMOUNT * 4,
- /datum/material/iron = SHEET_MATERIAL_AMOUNT * 2,
- /datum/material/gold = SHEET_MATERIAL_AMOUNT * 7,
- /datum/material/silver = SHEET_MATERIAL_AMOUNT * 7,
- /datum/material/diamond = SHEET_MATERIAL_AMOUNT * 4,
- )
- build_path = /obj/item/wallframe/tram/controller
- category = list(
- RND_CATEGORY_INITIAL,
- RND_CATEGORY_CONSTRUCTION + RND_SUBCATEGORY_CONSTRUCTION_MOUNTS,
- )
- departmental_flags = DEPARTMENT_BITFLAG_ENGINEERING
-
-/datum/design/tram_display
- name = "Tram Indicator Display"
- id = "tram_display"
- build_type = PROTOLATHE
- materials = list(
- /datum/material/titanium = SHEET_MATERIAL_AMOUNT * 4,
- /datum/material/iron = SHEET_MATERIAL_AMOUNT * 1,
- /datum/material/glass =SHEET_MATERIAL_AMOUNT * 2,
- )
- build_path = /obj/item/wallframe/indicator_display
- category = list(
- RND_CATEGORY_INITIAL,
- RND_CATEGORY_CONSTRUCTION + RND_SUBCATEGORY_CONSTRUCTION_MOUNTS,
- )
- departmental_flags = DEPARTMENT_BITFLAG_ENGINEERING
-
-/datum/design/tram_floor_dark
- name = "Dark Tram Tile"
- id = "tram_floor_dark"
- build_type = PROTOLATHE
- materials = list(/datum/material/plastic = SHEET_MATERIAL_AMOUNT * 0.25)
- build_path = /obj/item/stack/thermoplastic
- category = list(
- RND_CATEGORY_INITIAL,
- RND_CATEGORY_CONSTRUCTION + RND_SUBCATEGORY_CONSTRUCTION_MATERIALS,
- )
- departmental_flags = DEPARTMENT_BITFLAG_ENGINEERING
-
-/datum/design/tram_floor_light
- name = "Light Tram Tile"
- id = "tram_floor_light"
- build_type = PROTOLATHE
- materials = list(/datum/material/plastic = SHEET_MATERIAL_AMOUNT * 0.25)
- build_path = /obj/item/stack/thermoplastic/light
- category = list(
- RND_CATEGORY_INITIAL,
- RND_CATEGORY_CONSTRUCTION + RND_SUBCATEGORY_CONSTRUCTION_MATERIALS,
- )
- departmental_flags = DEPARTMENT_BITFLAG_ENGINEERING
diff --git a/code/modules/research/designs/autolathe/multi-department_designs.dm b/code/modules/research/designs/autolathe/multi-department_designs.dm
index ea1c94ff89c27..734dcbc97e084 100644
--- a/code/modules/research/designs/autolathe/multi-department_designs.dm
+++ b/code/modules/research/designs/autolathe/multi-department_designs.dm
@@ -483,43 +483,6 @@
)
departmental_flags = DEPARTMENT_BITFLAG_ENGINEERING | DEPARTMENT_BITFLAG_SCIENCE
-/datum/design/circuit
- name = "Blue Circuit Tile"
- id = "circuit"
- build_type = AUTOLATHE | PROTOLATHE | AWAY_LATHE
- materials = list(/datum/material/iron =SMALL_MATERIAL_AMOUNT*5, /datum/material/glass =SMALL_MATERIAL_AMOUNT*5)
- build_path = /obj/item/stack/tile/circuit
- category = list(
- RND_CATEGORY_INITIAL,
- RND_CATEGORY_CONSTRUCTION + RND_SUBCATEGORY_CONSTRUCTION_MATERIALS,
- )
- departmental_flags = DEPARTMENT_BITFLAG_ENGINEERING | DEPARTMENT_BITFLAG_SCIENCE
-
-
-/datum/design/circuitgreen
- name = "Green Circuit Tile"
- id = "circuitgreen"
- build_type = AUTOLATHE | PROTOLATHE | AWAY_LATHE
- materials = list(/datum/material/iron =SMALL_MATERIAL_AMOUNT*5, /datum/material/glass =SMALL_MATERIAL_AMOUNT*5)
- build_path = /obj/item/stack/tile/circuit/green
- category = list(
- RND_CATEGORY_INITIAL,
- RND_CATEGORY_CONSTRUCTION + RND_SUBCATEGORY_CONSTRUCTION_MATERIALS,
- )
- departmental_flags = DEPARTMENT_BITFLAG_ENGINEERING | DEPARTMENT_BITFLAG_SCIENCE
-
-/datum/design/circuitred
- name = "Red Circuit Tile"
- id = "circuitred"
- build_type = AUTOLATHE | PROTOLATHE | AWAY_LATHE
- materials = list(/datum/material/iron =SMALL_MATERIAL_AMOUNT*5, /datum/material/glass =SMALL_MATERIAL_AMOUNT*5)
- build_path = /obj/item/stack/tile/circuit/red
- category = list(
- RND_CATEGORY_INITIAL,
- RND_CATEGORY_CONSTRUCTION + RND_SUBCATEGORY_CONSTRUCTION_MATERIALS,
- )
- departmental_flags = DEPARTMENT_BITFLAG_ENGINEERING | DEPARTMENT_BITFLAG_SCIENCE
-
/datum/design/conveyor_belt
name = "Conveyor Belt"
id = "conveyor_belt"
diff --git a/code/modules/research/designs/computer_part_designs.dm b/code/modules/research/designs/computer_part_designs.dm
index 8dab08c046d45..7a8242642a13a 100644
--- a/code/modules/research/designs/computer_part_designs.dm
+++ b/code/modules/research/designs/computer_part_designs.dm
@@ -5,10 +5,11 @@
/datum/design/portabledrive/basic
name = "Data Disk"
id = "portadrive_basic"
- build_type = IMPRINTER | AWAY_IMPRINTER
- materials = list(/datum/material/glass = SMALL_MATERIAL_AMOUNT*8)
+ build_type = AUTOLATHE | PROTOLATHE | AWAY_LATHE
+ materials = list(/datum/material/glass = SHEET_MATERIAL_AMOUNT)
build_path = /obj/item/computer_disk
category = list(
+ RND_CATEGORY_INITIAL,
RND_CATEGORY_MODULAR_COMPUTERS + RND_SUBCATEGORY_MODULAR_COMPUTERS_PARTS
)
departmental_flags = DEPARTMENT_BITFLAG_SCIENCE | DEPARTMENT_BITFLAG_ENGINEERING
@@ -16,10 +17,11 @@
/datum/design/portabledrive/advanced
name = "Advanced Data Disk"
id = "portadrive_advanced"
- build_type = IMPRINTER | AWAY_IMPRINTER
- materials = list(/datum/material/glass = HALF_SHEET_MATERIAL_AMOUNT*1.5)
+ build_type = AUTOLATHE | PROTOLATHE | AWAY_LATHE
+ materials = list(/datum/material/glass = SHEET_MATERIAL_AMOUNT * 2)
build_path = /obj/item/computer_disk/advanced
category = list(
+ RND_CATEGORY_INITIAL,
RND_CATEGORY_MODULAR_COMPUTERS + RND_SUBCATEGORY_MODULAR_COMPUTERS_PARTS
)
departmental_flags = DEPARTMENT_BITFLAG_SCIENCE | DEPARTMENT_BITFLAG_ENGINEERING
@@ -27,10 +29,11 @@
/datum/design/portabledrive/super
name = "Super Data Disk"
id = "portadrive_super"
- build_type = IMPRINTER | AWAY_IMPRINTER
- materials = list(/datum/material/glass = SHEET_MATERIAL_AMOUNT*1.5)
+ build_type = AUTOLATHE | PROTOLATHE | AWAY_LATHE
+ materials = list(/datum/material/glass = SHEET_MATERIAL_AMOUNT * 4)
build_path = /obj/item/computer_disk/super
category = list(
+ RND_CATEGORY_INITIAL,
RND_CATEGORY_MODULAR_COMPUTERS + RND_SUBCATEGORY_MODULAR_COMPUTERS_PARTS
)
departmental_flags = DEPARTMENT_BITFLAG_SCIENCE | DEPARTMENT_BITFLAG_ENGINEERING
diff --git a/code/modules/research/designs/machine_designs.dm b/code/modules/research/designs/machine_designs.dm
index 8f857f77e8286..b8b11e7b44f67 100644
--- a/code/modules/research/designs/machine_designs.dm
+++ b/code/modules/research/designs/machine_designs.dm
@@ -582,6 +582,16 @@
)
departmental_flags = DEPARTMENT_BITFLAG_SERVICE
+/datum/design/board/vatgrower
+ name = "Growing Vat Board"
+ desc = "The circuit board for a growing vat."
+ id = "vatgrower"
+ build_path = /obj/item/circuitboard/machine/vatgrower
+ category = list(
+ RND_CATEGORY_MACHINE + RND_SUBCATEGORY_MACHINE_RESEARCH
+ )
+ departmental_flags = DEPARTMENT_BITFLAG_SCIENCE
+
/datum/design/board/monkey_recycler
name = "Monkey Recycler Board"
desc = "The circuit board for a monkey recycler."
@@ -1178,6 +1188,36 @@
)
departmental_flags = DEPARTMENT_BITFLAG_SCIENCE | DEPARTMENT_BITFLAG_ENGINEERING
+/datum/design/board/scrubber
+ name = "Portable Air Scrubber Board"
+ desc = "The circuit board for a portable air scrubber."
+ id = "scrubber"
+ build_path = /obj/item/circuitboard/machine/scrubber
+ category = list(
+ RND_CATEGORY_MACHINE + RND_SUBCATEGORY_MACHINE_ATMOS
+ )
+ departmental_flags = DEPARTMENT_BITFLAG_SCIENCE | DEPARTMENT_BITFLAG_ENGINEERING
+
+/datum/design/board/pump
+ name = "Portable Air Pump Board"
+ desc = "The circuit board for a portable air pump."
+ id = "pump"
+ build_path = /obj/item/circuitboard/machine/pump
+ category = list(
+ RND_CATEGORY_MACHINE + RND_SUBCATEGORY_MACHINE_ATMOS
+ )
+ departmental_flags = DEPARTMENT_BITFLAG_SCIENCE | DEPARTMENT_BITFLAG_ENGINEERING
+
+/datum/design/board/pipe_scrubber
+ name = "Portable Pipe Scrubber Board"
+ desc = "The circuit board for a portable pipe scrubber."
+ id = "pipe_scrubber"
+ build_path = /obj/item/circuitboard/machine/pipe_scrubber
+ category = list(
+ RND_CATEGORY_MACHINE + RND_SUBCATEGORY_MACHINE_ATMOS
+ )
+ departmental_flags = DEPARTMENT_BITFLAG_SCIENCE | DEPARTMENT_BITFLAG_ENGINEERING
+
/datum/design/board/bookbinder
name = "Book Binder"
desc = "The circuit board for a book binder"
diff --git a/code/modules/research/designs/mechfabricator_designs.dm b/code/modules/research/designs/mechfabricator_designs.dm
index 0583269f1c6be..19b3403908f81 100644
--- a/code/modules/research/designs/mechfabricator_designs.dm
+++ b/code/modules/research/designs/mechfabricator_designs.dm
@@ -907,6 +907,104 @@
RND_CATEGORY_MECHFAB_SAVANNAH_IVANOV + RND_SUBCATEGORY_MECHFAB_CHASSIS
)
+//Justice (emaged only)
+/datum/design/justice_chassis
+ name = "Exosuit Chassis (\"Justice\")"
+ id = "justice_chassis"
+ build_type = MECHFAB
+ build_path = /obj/item/mecha_parts/chassis/justice
+ materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*20)
+ construction_time = 10 SECONDS
+ category = list(
+ RND_CATEGORY_MECHFAB_JUSTICE + RND_SUBCATEGORY_MECHFAB_CHASSIS
+ )
+
+/datum/design/justice_torso
+ name = "Exosuit Torso (\"Justice\")"
+ id = "justice_torso"
+ build_type = MECHFAB
+ build_path = /obj/item/mecha_parts/part/justice_torso
+ materials = list(
+ /datum/material/iron = SHEET_MATERIAL_AMOUNT * 50,
+ /datum/material/silver = SHEET_MATERIAL_AMOUNT * 5,
+ )
+ construction_time = 30 SECONDS
+ category = list(
+ RND_CATEGORY_MECHFAB_JUSTICE + RND_SUBCATEGORY_MECHFAB_CHASSIS
+ )
+
+/datum/design/justice_left_arm
+ name = "Exosuit Left Arm (\"Justice\")"
+ id = "justice_left_arm"
+ build_type = MECHFAB
+ build_path = /obj/item/mecha_parts/part/justice_left_arm
+ materials = list(
+ /datum/material/iron = SHEET_MATERIAL_AMOUNT * 5,
+ /datum/material/silver = SHEET_MATERIAL_AMOUNT * 2,
+ )
+ construction_time = 10 SECONDS
+ category = list(
+ RND_CATEGORY_MECHFAB_JUSTICE + RND_SUBCATEGORY_MECHFAB_CHASSIS
+ )
+
+/datum/design/justice_right_arm
+ name = "Exosuit Right Arm (\"Justice\")"
+ id = "justice_right_arm"
+ build_type = MECHFAB
+ build_path = /obj/item/mecha_parts/part/justice_right_arm
+ materials = list(
+ /datum/material/iron=SHEET_MATERIAL_AMOUNT*5,
+ /datum/material/silver=SHEET_MATERIAL_AMOUNT*2,
+ )
+ construction_time = 10 SECONDS
+ category = list(
+ RND_CATEGORY_MECHFAB_JUSTICE + RND_SUBCATEGORY_MECHFAB_CHASSIS
+ )
+
+/datum/design/justice_left_leg
+ name = "Exosuit Left Leg (\"Justice\")"
+ id = "justice_left_leg"
+ build_type = MECHFAB
+ build_path = /obj/item/mecha_parts/part/justice_left_leg
+ materials = list(
+ /datum/material/iron=SHEET_MATERIAL_AMOUNT*5,
+ /datum/material/titanium=SHEET_MATERIAL_AMOUNT*2,
+ )
+ construction_time = 10 SECONDS
+ category = list(
+ RND_CATEGORY_MECHFAB_JUSTICE + RND_SUBCATEGORY_MECHFAB_CHASSIS
+ )
+
+/datum/design/justice_right_leg
+ name = "Exosuit Right Leg (\"Justice\")"
+ id = "justice_right_leg"
+ build_type = MECHFAB
+ build_path = /obj/item/mecha_parts/part/justice_right_leg
+ materials = list(
+ /datum/material/iron=SHEET_MATERIAL_AMOUNT*5,
+ /datum/material/titanium=SHEET_MATERIAL_AMOUNT*2,
+ )
+ construction_time = 10 SECONDS
+ category = list(
+ RND_CATEGORY_MECHFAB_JUSTICE + RND_SUBCATEGORY_MECHFAB_CHASSIS
+ )
+
+/datum/design/justice_armor
+ name = "Exosuit Armor (\"Justice\")"
+ id = "justice_armor"
+ build_type = MECHFAB
+ build_path = /obj/item/mecha_parts/part/justice_armor
+ materials = list(
+ /datum/material/silver=SHEET_MATERIAL_AMOUNT*10,
+ /datum/material/titanium=SHEET_MATERIAL_AMOUNT*10,
+ /datum/material/plastic=SHEET_MATERIAL_AMOUNT*5,
+ /datum/material/diamond=SHEET_MATERIAL_AMOUNT*1,
+ )
+ construction_time = 20 SECONDS
+ category = list(
+ RND_CATEGORY_MECHFAB_JUSTICE + RND_SUBCATEGORY_MECHFAB_CHASSIS
+ )
+
//Clarke
/datum/design/clarke_chassis
name = "Exosuit Chassis (\"Clarke\")"
@@ -2338,14 +2436,14 @@
RND_CATEGORY_MODSUIT_MODULES + RND_SUBCATEGORY_MODSUIT_MODULES_SUPPLY
)
-/datum/design/module/mod_organ_thrower
- name = "Organ Thrower Module"
- id = "mod_organ_thrower"
+/datum/design/module/mod_organizer
+ name = "Organizer Module"
+ id = "mod_organizer"
materials = list(
/datum/material/iron =HALF_SHEET_MATERIAL_AMOUNT,
/datum/material/glass =HALF_SHEET_MATERIAL_AMOUNT,
)
- build_path = /obj/item/mod/module/organ_thrower
+ build_path = /obj/item/mod/module/organizer
category = list(
RND_CATEGORY_MODSUIT_MODULES + RND_SUBCATEGORY_MODSUIT_MODULES_MEDICAL
)
@@ -2512,6 +2610,18 @@
RND_CATEGORY_MODSUIT_MODULES + RND_SUBCATEGORY_MODSUIT_MODULES_SECURITY
)
+/datum/design/module/mirage
+ name = "Mirage Grenade Dispenser Module"
+ id = "mod_mirage_grenade"
+ materials = list(
+ /datum/material/iron =HALF_SHEET_MATERIAL_AMOUNT,
+ /datum/material/bluespace =HALF_SHEET_MATERIAL_AMOUNT,
+ )
+ build_path = /obj/item/mod/module/dispenser/mirage
+ category = list(
+ RND_CATEGORY_MODSUIT_MODULES + RND_SUBCATEGORY_MODSUIT_MODULES_SECURITY
+ )
+
//MODsuit bepis modules
/datum/design/module/disposal
name = "Disposal Connector Module"
diff --git a/code/modules/research/designs/misc_designs.dm b/code/modules/research/designs/misc_designs.dm
index 0ea6c41e16448..d9feba5179e08 100644
--- a/code/modules/research/designs/misc_designs.dm
+++ b/code/modules/research/designs/misc_designs.dm
@@ -166,18 +166,6 @@
)
departmental_flags = DEPARTMENT_BITFLAG_SERVICE
-/datum/design/air_horn
- name = "Air Horn"
- desc = "Damn son, where'd you find this?"
- id = "air_horn"
- build_type = PROTOLATHE | AWAY_LATHE
- materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT*2, /datum/material/bananium =HALF_SHEET_MATERIAL_AMOUNT)
- build_path = /obj/item/bikehorn/airhorn
- category = list(
- RND_CATEGORY_EQUIPMENT + RND_SUBCATEGORY_EQUIPMENT_SERVICE
- )
- departmental_flags = DEPARTMENT_BITFLAG_SERVICE
-
/datum/design/clown_firing_pin
name = "Hilarious Firing Pin"
id = "clown_firing_pin"
diff --git a/code/modules/research/designs/tool_designs.dm b/code/modules/research/designs/tool_designs.dm
index 649d0c5c09dfc..901bc304dc1d7 100644
--- a/code/modules/research/designs/tool_designs.dm
+++ b/code/modules/research/designs/tool_designs.dm
@@ -226,17 +226,6 @@
)
departmental_flags = DEPARTMENT_BITFLAG_SERVICE
-/datum/design/plumbing_rcd_sci
- name = "Research Plumbing Constructor"
- id = "plumbing_rcd_sci"
- build_type = PROTOLATHE | AWAY_LATHE
- materials = list(/datum/material/iron =SHEET_MATERIAL_AMOUNT*37.5, /datum/material/glass =SHEET_MATERIAL_AMOUNT*18.75, /datum/material/plastic =HALF_SHEET_MATERIAL_AMOUNT)
- build_path = /obj/item/construction/plumbing/research
- category = list(
- RND_CATEGORY_TOOLS + RND_SUBCATEGORY_TOOLS_PLUMBING
- )
- departmental_flags = DEPARTMENT_BITFLAG_SCIENCE
-
/datum/design/plumbing_rcd_service
name = "Service Plumbing Constructor"
id = "plumbing_rcd_service"
diff --git a/code/modules/research/designs/weapon_designs.dm b/code/modules/research/designs/weapon_designs.dm
index 35d95d82d3047..f0e0978a6074a 100644
--- a/code/modules/research/designs/weapon_designs.dm
+++ b/code/modules/research/designs/weapon_designs.dm
@@ -449,3 +449,15 @@
RND_CATEGORY_WEAPONS + RND_SUBCATEGORY_WEAPONS_RANGED
)
departmental_flags = DEPARTMENT_BITFLAG_SECURITY
+
+/datum/design/photon_cannon
+ name = "Photon Cannon Part Kit (Nonlethal)"
+ desc = "A kit to reverse-engineer a photon cannon, a weapon that generates a shortly-lived miniature sun. Technically brightens up the room, effectively blinds everyone in it. Requires a flux anomaly core to finish."
+ id = "photon_cannon"
+ build_type = PROTOLATHE | AWAY_LATHE
+ materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT * 3, /datum/material/glass = SHEET_MATERIAL_AMOUNT * 7, /datum/material/gold = SHEET_MATERIAL_AMOUNT * 5)
+ build_path = /obj/item/weaponcrafting/gunkit/photon
+ category = list(
+ RND_CATEGORY_WEAPONS + RND_SUBCATEGORY_WEAPONS_KITS
+ )
+ departmental_flags = DEPARTMENT_BITFLAG_SECURITY
diff --git a/code/modules/research/experimentor.dm b/code/modules/research/experimentor.dm
index affe82275ab14..2d0ba7017d27b 100644
--- a/code/modules/research/experimentor.dm
+++ b/code/modules/research/experimentor.dm
@@ -461,9 +461,9 @@
if(exp == SCANTYPE_DISCOVER)
visible_message(span_notice("[src] scans the [exp_on], revealing its true nature!"))
playsound(src, 'sound/effects/supermatter.ogg', 50, 3, -1)
- var/obj/item/relic/R = loaded_item
- R.reveal()
- investigate_log("Experimentor has revealed a relic with [span_danger("[R.realProc]")] effect.", INVESTIGATE_EXPERIMENTOR)
+ var/obj/item/relic/loaded_artifact = loaded_item
+ loaded_artifact.reveal()
+ investigate_log("Experimentor has revealed a relic with [span_danger("[loaded_artifact.hidden_power]")] effect.", INVESTIGATE_EXPERIMENTOR)
ejectItem()
//Global reactions
@@ -553,34 +553,74 @@
#undef FAIL
-//////////////////////////////////SPECIAL ITEMS////////////////////////////////////////
+// Relic \\
/obj/item/relic
name = "strange object"
desc = "What mysteries could this hold? Maybe Research & Development could find out."
- icon = 'icons/obj/devices/assemblies.dmi'
- var/realName = "defined object"
- var/revealed = FALSE
- var/realProc
- var/reset_timer = 60
+ icon = 'icons/obj/devices/artefacts.dmi'
+ icon_state = "debug_artefact"
+ //The name this artefact will have when it's activated.
+ var/real_name = "artefact"
+ //Has this artefact been activated?
+ var/activated = FALSE
+ //What effect this artefact has when used. Randomly determined when activated.
+ var/hidden_power
+ //Minimum possible cooldown.
+ var/min_cooldown = 6 SECONDS
+ //Max possible cooldown.
+ var/max_cooldown = 30 SECONDS
+ //Cooldown length. Randomly determined at activation if it isn't determined here.
+ var/cooldown_timer
COOLDOWN_DECLARE(cooldown)
+ //What visual theme this artefact has. Current possible choices: "prototype", "necrotech"
+ var/artifact_theme = "prototype"
/obj/item/relic/Initialize(mapload)
. = ..()
- icon_state = pick("shock_kit","armor-igniter-analyzer","infra-igniter0","infra-igniter1","radio-multitool","prox-radio1","radio-radio","timer-multitool0","radio-igniter-tank")
- realName = "[pick("broken","twisted","spun","improved","silly","regular","badly made")] [pick("device","object","toy","illegal tech","weapon")]"
+ random_themed_appearance()
+
+/obj/item/relic/proc/random_themed_appearance()
+ var/themed_name_prefix
+ var/themed_name_suffix
+ if(artifact_theme == "prototype")
+ icon_state = pick("prototype1", "prototype2", "prototype3", "prototype4", "prototype5", "prototype6", "prototype7", "prototype8","prototype9")
+ themed_name_prefix = pick("experimental","prototype","artificial","handcrafted","ramshackle","odd")
+ themed_name_suffix = pick("device","assembly","gadget","gizmo","contraption","machine","widget","object")
+ real_name = "[pick(themed_name_prefix)] [pick(themed_name_suffix)]"
+ name = "strange [pick(themed_name_suffix)]"
+ if(artifact_theme == "necrotech")
+ icon_state = pick("necrotech1", "necrotech2", "necrotech3", "necrotech4", "necrotech5", "necrotech6")
+ themed_name_prefix = pick("dark","bloodied","unholy","archeotechnological","dismal","ruined","thrumming")
+ themed_name_suffix = pick("instrument","shard","fetish","bibelot","trinket","offering","relic")
+ real_name = "[pick(themed_name_prefix)] [pick(themed_name_suffix)]"
+ name = "strange relic"
+ update_appearance()
+/obj/item/relic/lavaland
+ name = "strange relic"
+ artifact_theme = "necrotech"
/obj/item/relic/proc/reveal()
- if(revealed) //Re-rolling your relics seems a bit overpowered, yes?
+ if(activated) //no rerolling
return
- revealed = TRUE
- name = realName
- reset_timer = rand(reset_timer, reset_timer * 5)
- realProc = pick(PROC_REF(teleport), PROC_REF(explode), PROC_REF(rapidDupe), PROC_REF(petSpray), PROC_REF(flash), PROC_REF(clean), PROC_REF(corgicannon))
+ activated = TRUE
+ name = real_name
+ if(!cooldown_timer)
+ cooldown_timer = rand(min_cooldown, max_cooldown)
+ if(!hidden_power)
+ hidden_power = pick(
+ PROC_REF(corgi_cannon),
+ PROC_REF(cleaning_foam),
+ PROC_REF(flashbanger),
+ PROC_REF(summon_animals),
+ PROC_REF(uncontrolled_teleport),
+ PROC_REF(heat_and_explode),
+ PROC_REF(rapid_self_dupe),
+ )
/obj/item/relic/attack_self(mob/user)
- if(!revealed)
+ if(!activated)
to_chat(user, span_notice("You aren't quite sure what this is. Maybe R&D knows what to do with it?"))
return
if(!COOLDOWN_FINISHED(src, cooldown))
@@ -588,40 +628,39 @@
return
if(loc != user)
return
- COOLDOWN_START(src, cooldown, reset_timer)
- call(src,realProc)(user)
-
-//////////////// RELIC PROCS /////////////////////////////
+ COOLDOWN_START(src, cooldown, cooldown_timer)
+ call(src, hidden_power)(user)
-/obj/item/relic/proc/throwSmoke(turf/where)
+/obj/item/relic/proc/throw_smoke(turf/where)
var/datum/effect_system/fluid_spread/smoke/smoke = new
smoke.set_up(0, holder = src, location = get_turf(where))
smoke.start()
-/obj/item/relic/proc/corgicannon(mob/user)
+// Artefact Powers \\
+
+/obj/item/relic/proc/corgi_cannon(mob/user)
playsound(src, SFX_SPARKS, rand(25,50), TRUE, SHORT_RANGE_SOUND_EXTRARANGE)
var/mob/living/basic/pet/dog/corgi/sad_corgi = new(get_turf(user))
- sad_corgi.throw_at(pick(oview(10,user)), 10, rand(3,8), callback = CALLBACK(src, PROC_REF(throwSmoke), sad_corgi))
+ sad_corgi.throw_at(pick(oview(10,user)), 10, rand(3,8), callback = CALLBACK(src, PROC_REF(throw_smoke), sad_corgi))
warn_admins(user, "Corgi Cannon", 0)
-/obj/item/relic/proc/clean(mob/user)
+/obj/item/relic/proc/cleaning_foam(mob/user)
playsound(src, SFX_SPARKS, rand(25,50), TRUE, SHORT_RANGE_SOUND_EXTRARANGE)
- var/obj/item/grenade/chem_grenade/cleaner/CL = new/obj/item/grenade/chem_grenade/cleaner(get_turf(user))
- CL.detonate()
- qdel(CL)
+ var/obj/item/grenade/chem_grenade/cleaner/spawned_foamer = new/obj/item/grenade/chem_grenade/cleaner(get_turf(user))
+ spawned_foamer.detonate()
+ qdel(spawned_foamer)
warn_admins(user, "Foam", 0)
-/obj/item/relic/proc/flash(mob/user)
+/obj/item/relic/proc/flashbanger(mob/user)
playsound(src, SFX_SPARKS, rand(25,50), TRUE, SHORT_RANGE_SOUND_EXTRARANGE)
- var/obj/item/grenade/flashbang/CB = new/obj/item/grenade/flashbang(user.loc)
- CB.detonate()
+ var/obj/item/grenade/flashbang/spawned_flashbang = new/obj/item/grenade/flashbang(user.loc)
+ spawned_flashbang.detonate()
warn_admins(user, "Flash")
-/obj/item/relic/proc/petSpray(mob/user)
+/obj/item/relic/proc/summon_animals(mob/user)
var/message = span_danger("[src] begins to shake, and in the distance the sound of rampaging animals arises!")
visible_message(message)
to_chat(user, message)
-
var/static/list/valid_animals = list(
/mob/living/basic/bear,
/mob/living/basic/bee,
@@ -637,42 +676,40 @@
/mob/living/basic/pet/fox,
)
for(var/counter in 1 to rand(1, 25))
- var/mobType = pick(valid_animals)
- new mobType(get_turf(src))
-
+ var/animal_spawn = pick(valid_animals)
+ new animal_spawn(get_turf(src))
warn_admins(user, "Mass Mob Spawn")
if(prob(60))
to_chat(user, span_warning("[src] falls apart!"))
qdel(src)
-/obj/item/relic/proc/rapidDupe(mob/user)
+/obj/item/relic/proc/rapid_self_dupe(mob/user)
audible_message("[src] emits a loud pop!")
- var/list/dupes = list()
+ var/list/dummy_artifacts = list()
for(var/counter in 1 to rand(5,10))
- var/obj/item/relic/R = new type(get_turf(src))
- R.name = name
- R.desc = desc
- R.realName = realName
- R.realProc = realProc
- R.revealed = TRUE
- dupes += R
- R.throw_at(pick(oview(7,get_turf(src))),10,1)
-
- QDEL_LIST_IN(dupes, rand(10, 100))
+ var/obj/item/relic/duped = new type(get_turf(src))
+ duped.name = name
+ duped.desc = desc
+ duped.real_name = real_name
+ duped.hidden_power = hidden_power
+ duped.activated = TRUE
+ dummy_artifacts += duped
+ duped.throw_at(pick(oview(7,get_turf(src))),10,1)
+ QDEL_LIST_IN(dummy_artifacts, rand(1 SECONDS, 10 SECONDS))
warn_admins(user, "Rapid duplicator", 0)
-/obj/item/relic/proc/explode(mob/user)
+/obj/item/relic/proc/heat_and_explode(mob/user)
to_chat(user, span_danger("[src] begins to heat up!"))
- addtimer(CALLBACK(src, PROC_REF(do_explode), user), rand(3.5 SECONDS, 10 SECONDS))
+ addtimer(CALLBACK(src, PROC_REF(blow_up), user), rand(3.5 SECONDS, 10 SECONDS))
-/obj/item/relic/proc/do_explode(mob/user)
+/obj/item/relic/proc/blow_up(mob/user)
if(loc == user)
visible_message(span_notice("\The [src]'s top opens, releasing a powerful blast!"))
explosion(src, heavy_impact_range = rand(1,5), light_impact_range = rand(1,5), flame_range = 2, flash_range = rand(1,5), adminlog = TRUE)
warn_admins(user, "Explosion")
qdel(src) //Comment this line to produce a light grenade (the bomb that keeps on exploding when used)!!
-/obj/item/relic/proc/teleport(mob/user)
+/obj/item/relic/proc/uncontrolled_teleport(mob/user)
to_chat(user, span_notice("[src] begins to vibrate!"))
addtimer(CALLBACK(src, PROC_REF(do_the_teleport), user), rand(1 SECONDS, 3 SECONDS))
@@ -680,16 +717,16 @@
var/turf/userturf = get_turf(user)
if(loc == user && !is_centcom_level(userturf.z)) //Because Nuke Ops bringing this back on their shuttle, then looting the ERT area is 2fun4you!
visible_message(span_notice("[src] twists and bends, relocating itself!"))
- throwSmoke(userturf)
+ throw_smoke(userturf)
do_teleport(user, userturf, 8, asoundin = 'sound/effects/phasein.ogg', channel = TELEPORT_CHANNEL_BLUESPACE)
- throwSmoke(get_turf(user))
+ throw_smoke(get_turf(user))
warn_admins(user, "Teleport", 0)
//Admin Warning proc for relics
-/obj/item/relic/proc/warn_admins(mob/user, RelicType, priority = 1)
- var/turf/T = get_turf(src)
- var/log_msg = "[RelicType] relic used by [key_name(user)] in [AREACOORD(T)]"
+/obj/item/relic/proc/warn_admins(mob/user, relic_type, priority = 1)
+ var/turf/location = get_turf(src)
+ var/log_msg = "[relic_type] relic used by [key_name(user)] in [AREACOORD(location)]"
if(priority) //For truly dangerous relics that may need an admin's attention. BWOINK!
- message_admins("[RelicType] relic activated by [ADMIN_LOOKUPFLW(user)] in [ADMIN_VERBOSEJMP(T)]")
+ message_admins("[relic_type] relic activated by [ADMIN_LOOKUPFLW(user)] in [ADMIN_VERBOSEJMP(location)]")
log_game(log_msg)
investigate_log(log_msg, "experimentor")
diff --git a/code/modules/research/rdmachines.dm b/code/modules/research/rdmachines.dm
index 069447c6f4539..0cfca236d24c3 100644
--- a/code/modules/research/rdmachines.dm
+++ b/code/modules/research/rdmachines.dm
@@ -72,7 +72,7 @@
return CONTEXTUAL_SCREENTIP_SET
else
if(held_item.tool_behaviour == TOOL_MULTITOOL)
- var/obj/item/multitool/tool = held_item
+ var/obj/item/multitool/tool = held_item.get_proxy_attacker_for(src, user)
if(!QDELETED(tool.buffer) && istype(tool.buffer, /datum/techweb))
context[SCREENTIP_CONTEXT_LMB] = "Upload Techweb"
context[SCREENTIP_CONTEXT_RMB] = "Upload Techweb"
diff --git a/code/modules/research/techweb/_techweb_node.dm b/code/modules/research/techweb/_techweb_node.dm
index c36eb88627137..7714946a4d290 100644
--- a/code/modules/research/techweb/_techweb_node.dm
+++ b/code/modules/research/techweb/_techweb_node.dm
@@ -40,6 +40,8 @@
var/list/experiments_to_unlock = list()
/// Whether or not this node should show on the wiki
var/show_on_wiki = TRUE
+ /// Hidden Mech nodes unlocked when mech fabricator emaged.
+ var/illegal_mech_node = FALSE
/datum/techweb_node/error_node
id = "ERROR"
diff --git a/code/modules/research/techweb/nodes/atmos_nodes.dm b/code/modules/research/techweb/nodes/atmos_nodes.dm
index e2f29171d9d98..ef4fee5ed92a7 100644
--- a/code/modules/research/techweb/nodes/atmos_nodes.dm
+++ b/code/modules/research/techweb/nodes/atmos_nodes.dm
@@ -8,6 +8,7 @@
"atmosalerts",
"thermomachine",
"space_heater",
+ "scrubber",
"generic_tank",
"oxygen_tank",
"plasma_tank",
@@ -27,6 +28,7 @@
prereq_ids = list(TECHWEB_NODE_ATMOS)
design_ids = list(
"tank_compressor",
+ "pump",
"emergency_oxygen",
"emergency_oxygen_engi",
"power_turbine_console",
@@ -47,8 +49,8 @@
description = "Experiments with high-pressure gases and electricity resulting in crystallization and controlled plasma reactions."
prereq_ids = list(TECHWEB_NODE_GAS_COMPRESSION, TECHWEB_NODE_ENERGY_MANIPULATION)
design_ids = list(
- "crystallizer",
"electrolyzer",
+ "pipe_scrubber",
"pacman",
"mech_generator",
"plasmacutter",
@@ -68,9 +70,11 @@
"HFR_interface",
"HFR_moderator_input",
"HFR_waste_output",
+ "adv_fire_extinguisher",
"bolter_wrench",
"rpd_loaded",
"engine_goggles",
+ "crystallizer",
)
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_3_POINTS)
required_experiments = list(/datum/experiment/ordnance/gaseous/bz)
@@ -88,18 +92,18 @@
"jawsoflife",
"rangedanalyzer",
"rtd_loaded",
+ "mech_rcd",
"rcd_loaded",
"rcd_ammo",
"weldingmask",
"magboots",
- "adv_fire_extinguisher",
)
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_4_POINTS)
discount_experiments = list(/datum/experiment/ordnance/gaseous/noblium = TECHWEB_TIER_4_POINTS)
/datum/techweb_node/rcd_upgrade
id = TECHWEB_NODE_RCD_UPGRADE
- display_name = "Rapid Device Upgrade Designs"
+ display_name = "Rapid Construction Device Upgrades"
description = "New designs and enhancements for RCD and RPD."
prereq_ids = list(TECHWEB_NODE_EXP_TOOLS, TECHWEB_NODE_PARTS_BLUESPACE)
design_ids = list(
diff --git a/code/modules/research/techweb/nodes/bepis_nodes.dm b/code/modules/research/techweb/nodes/bepis_nodes.dm
index ad5bb4c62bb1b..baefd8c11d048 100644
--- a/code/modules/research/techweb/nodes/bepis_nodes.dm
+++ b/code/modules/research/techweb/nodes/bepis_nodes.dm
@@ -5,6 +5,7 @@
design_ids = list(
"bright_helmet",
"rld_mini",
+ "photon_cannon",
)
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_1_POINTS)
hidden = TRUE
diff --git a/code/modules/research/techweb/nodes/biology_nodes.dm b/code/modules/research/techweb/nodes/biology_nodes.dm
index f8ee05c042c2e..3599dfb9fc5a1 100644
--- a/code/modules/research/techweb/nodes/biology_nodes.dm
+++ b/code/modules/research/techweb/nodes/biology_nodes.dm
@@ -23,6 +23,7 @@
design_ids = list(
"limbgrower",
"pandemic",
+ "vatgrower",
"petri_dish",
"swab",
"biopsy_tool",
@@ -43,7 +44,7 @@
"limbdesign_plasmaman",
)
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_3_POINTS)
- required_experiments = list(/datum/experiment/scanning/random/cytology)
+ required_experiments = list(/datum/experiment/scanning/cytology/slime)
/datum/techweb_node/gene_engineering
id = TECHWEB_NODE_GENE_ENGINEERING
@@ -58,7 +59,45 @@
"mod_dna_lock",
)
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_4_POINTS)
- discount_experiments = list(
- /datum/experiment/scanning/random/plants/traits = TECHWEB_TIER_2_POINTS,
- /datum/experiment/scanning/points/slime/hard = TECHWEB_TIER_2_POINTS,
- )
+ discount_experiments = list(/datum/experiment/scanning/people/mutant = TECHWEB_TIER_4_POINTS)
+
+// Botany root node
+/datum/techweb_node/botany_equip
+ id = TECHWEB_NODE_BOTANY_EQUIP
+ starting_node = TRUE
+ display_name = "Botany Equipment"
+ description = "Essential tools for maintaining onboard gardens, supporting plant growth in the unique environment of the space station."
+ design_ids = list(
+ "seed_extractor",
+ "plant_analyzer",
+ "watering_can",
+ "spade",
+ "cultivator",
+ "secateurs",
+ "hatchet",
+ )
+
+/datum/techweb_node/hydroponics
+ id = TECHWEB_NODE_HYDROPONICS
+ display_name = "Hydroponics"
+ description = "Research into advanced hydroponic systems for efficient and sustainable plant cultivation."
+ prereq_ids = list(TECHWEB_NODE_BOTANY_EQUIP, TECHWEB_NODE_CHEM_SYNTHESIS)
+ design_ids = list(
+ "biogenerator",
+ "hydro_tray",
+ "portaseeder",
+ )
+ research_costs = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_2_POINTS)
+
+/datum/techweb_node/selection
+ id = TECHWEB_NODE_SELECTION
+ display_name = "Artificial Selection"
+ description = "Advancement in plant cultivation techniques through artificial selection, enabling precise manipulation of plant DNA."
+ prereq_ids = list(TECHWEB_NODE_HYDROPONICS)
+ design_ids = list(
+ "flora_gun",
+ "gene_shears",
+ )
+ research_costs = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_3_POINTS)
+ required_experiments = list(/datum/experiment/scanning/random/plants/wild)
+ discount_experiments = list(/datum/experiment/scanning/random/plants/traits = TECHWEB_TIER_3_POINTS)
diff --git a/code/modules/research/techweb/nodes/cyborg_nodes.dm b/code/modules/research/techweb/nodes/cyborg_nodes.dm
index da9f485f2bf1f..447ee2dc7f3b9 100644
--- a/code/modules/research/techweb/nodes/cyborg_nodes.dm
+++ b/code/modules/research/techweb/nodes/cyborg_nodes.dm
@@ -19,6 +19,9 @@
"cybernetic_liver",
"cybernetic_heart",
)
+ experiments_to_unlock = list(
+ /datum/experiment/scanning/people/android,
+ )
/datum/techweb_node/cybernetics
id = TECHWEB_NODE_CYBERNETICS
@@ -213,7 +216,7 @@
"cybernetic_liver_tier3",
"cybernetic_heart_tier3",
)
- research_costs = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_4_POINTS)
+ research_costs = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_3_POINTS)
required_experiments = list(/datum/experiment/scanning/people/augmented_organs)
/datum/techweb_node/cyber/cyber_organs_adv
@@ -229,3 +232,4 @@
"ci-xray-moth",
)
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_5_POINTS)
+ discount_experiments = list(/datum/experiment/scanning/people/android = TECHWEB_TIER_5_POINTS)
diff --git a/code/modules/research/techweb/nodes/engi_nodes.dm b/code/modules/research/techweb/nodes/engi_nodes.dm
index cb487f41b8db1..626dd6981d0ce 100644
--- a/code/modules/research/techweb/nodes/engi_nodes.dm
+++ b/code/modules/research/techweb/nodes/engi_nodes.dm
@@ -88,6 +88,7 @@
"comm_monitor",
"comm_server",
"message_monitor",
+ "ntnet_relay",
"s_hub",
"s_messaging",
"s_server",
@@ -142,16 +143,6 @@
"wirecutters",
"light_bulb",
"light_tube",
- "intercom_frame",
- "newscaster_frame",
- "status_display_frame",
- "circuit",
- "circuitgreen",
- "circuitred",
- "tram_floor_dark",
- "tram_floor_light",
- "tram_controller",
- "tram_display",
"crossing_signal",
"guideway_sensor",
)
diff --git a/code/modules/research/techweb/nodes/mech_nodes.dm b/code/modules/research/techweb/nodes/mech_nodes.dm
index 5f3d302132333..6b7a9803ae495 100644
--- a/code/modules/research/techweb/nodes/mech_nodes.dm
+++ b/code/modules/research/techweb/nodes/mech_nodes.dm
@@ -112,7 +112,6 @@
)
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_2_POINTS)
required_experiments = list(/datum/experiment/scanning/random/mecha_equipped_scan)
- discount_experiments = list(/datum/experiment/scanning/random/mecha_damage_scan = TECHWEB_TIER_2_POINTS)
/datum/techweb_node/mech_assault
id = TECHWEB_NODE_MECH_ASSAULT
@@ -194,6 +193,22 @@
)
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_4_POINTS)
+/datum/techweb_node/justice
+ id = "mecha_justice"
+ display_name = "EXOSUIT: Justice"
+ description = "Justice exosuit designs"
+ design_ids = list(
+ "justice_armor",
+ "justice_chassis",
+ "justice_left_arm",
+ "justice_left_leg",
+ "justice_right_arm",
+ "justice_right_leg",
+ "justice_torso",
+ )
+ hidden = TRUE
+ illegal_mech_node = TRUE
+
/datum/techweb_node/mech_energy_guns
id = TECHWEB_NODE_MECH_ENERGY_GUNS
display_name = "Mech Energy Guns"
@@ -206,6 +221,7 @@
"mech_tesla",
)
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_4_POINTS)
+ discount_experiments = list(/datum/experiment/scanning/random/mecha_damage_scan = TECHWEB_TIER_4_POINTS)
/datum/techweb_node/mech_firearms
id = TECHWEB_NODE_MECH_FIREARMS
@@ -246,6 +262,5 @@
"mech_gravcatapult",
"mech_teleporter",
"mech_wormhole_gen",
- "mech_rcd",
)
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_5_POINTS)
diff --git a/code/modules/research/techweb/nodes/medbay_nodes.dm b/code/modules/research/techweb/nodes/medbay_nodes.dm
index 3f2239ced22e9..7d6db08f584e7 100644
--- a/code/modules/research/techweb/nodes/medbay_nodes.dm
+++ b/code/modules/research/techweb/nodes/medbay_nodes.dm
@@ -23,6 +23,8 @@
"stethoscope",
"beaker",
"large_beaker",
+ "chem_pack",
+ "blood_pack",
"syringe",
"dropper",
"pillbottle",
@@ -31,6 +33,8 @@
/datum/experiment/autopsy/human,
/datum/experiment/autopsy/nonhuman,
/datum/experiment/autopsy/xenomorph,
+ /datum/experiment/scanning/reagent/haloperidol,
+ /datum/experiment/scanning/reagent/cryostylane,
)
/datum/techweb_node/chem_synthesis
@@ -40,8 +44,6 @@
prereq_ids = list(TECHWEB_NODE_MEDBAY_EQUIP)
design_ids = list(
"xlarge_beaker",
- "blood_pack",
- "chem_pack",
"med_spray_bottle",
"medigel",
"medipen_refiller",
@@ -62,7 +64,6 @@
design_ids = list(
"plumbing_rcd",
"plumbing_rcd_service",
- "plumbing_rcd_sci",
"plunger",
"fluid_ducts",
"meta_beaker",
@@ -70,27 +71,13 @@
)
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_2_POINTS)
-/datum/techweb_node/cryostasis
- id = TECHWEB_NODE_CRYOSTASIS
- display_name = "Cryostasis"
- description = "The result of clown accidentally drinking a chemical, now repurposed for safely preserving crew members in suspended animation."
- prereq_ids = list(TECHWEB_NODE_PLUMBING, TECHWEB_NODE_PLASMA_CONTROL)
- design_ids = list(
- "cryotube",
- "mech_sleeper",
- "stasis",
- "cryo_grenade",
- "splitbeaker",
- )
- research_costs = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_3_POINTS)
- required_experiments = list(/datum/experiment/scanning/reagent/cryostylane)
-
/datum/techweb_node/medbay_equip_adv
id = TECHWEB_NODE_MEDBAY_EQUIP_ADV
display_name = "Advanced Medbay Equipment"
description = "State-of-the-art medical gear for keeping the crew in one piece — mostly."
- prereq_ids = list(TECHWEB_NODE_CRYOSTASIS)
+ prereq_ids = list(TECHWEB_NODE_PLUMBING)
design_ids = list(
+ "smoke_machine",
"chem_mass_spec",
"healthanalyzer_advanced",
"mod_health_analyzer",
@@ -99,4 +86,20 @@
"defibmount",
"medicalbed_emergency",
)
+ research_costs = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_3_POINTS)
+ required_experiments = list(/datum/experiment/scanning/reagent/haloperidol)
+
+/datum/techweb_node/cryostasis
+ id = TECHWEB_NODE_CRYOSTASIS
+ display_name = "Cryostasis"
+ description = "The result of clown accidentally drinking a chemical, now repurposed for safely preserving crew members in suspended animation."
+ prereq_ids = list(TECHWEB_NODE_FUSION)
+ design_ids = list(
+ "cryotube",
+ "mech_sleeper",
+ "stasis",
+ "cryo_grenade",
+ "splitbeaker",
+ )
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_4_POINTS)
+ discount_experiments = list(/datum/experiment/scanning/reagent/cryostylane = TECHWEB_TIER_4_POINTS)
diff --git a/code/modules/research/techweb/nodes/modsuit_nodes.dm b/code/modules/research/techweb/nodes/modsuit_nodes.dm
index 2e74663eed419..d4e30e9cd5d8f 100644
--- a/code/modules/research/techweb/nodes/modsuit_nodes.dm
+++ b/code/modules/research/techweb/nodes/modsuit_nodes.dm
@@ -58,7 +58,7 @@
"mod_plating_medical",
"mod_quick_carry",
"mod_injector",
- "mod_organ_thrower",
+ "mod_organizer",
"mod_patienttransport",
)
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_2_POINTS)
@@ -83,6 +83,7 @@
description = "Security suits for space crime handling."
prereq_ids = list(TECHWEB_NODE_MOD_EQUIP)
design_ids = list(
+ "mod_mirage_grenade",
"mod_plating_security",
"mod_stealth",
"mod_mag_harness",
diff --git a/code/modules/research/techweb/nodes/research_nodes.dm b/code/modules/research/techweb/nodes/research_nodes.dm
index 82a64f8c82338..0a37fb19868a3 100644
--- a/code/modules/research/techweb/nodes/research_nodes.dm
+++ b/code/modules/research/techweb/nodes/research_nodes.dm
@@ -13,7 +13,6 @@
"destructive_analyzer",
"destructive_scanner",
"experi_scanner",
- "ntnet_relay",
"laptop",
"portadrive_basic",
"portadrive_advanced",
@@ -92,4 +91,5 @@
"gravitygun",
"polymorph_belt"
)
- research_costs = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_4_POINTS)
+ research_costs = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_5_POINTS)
+ discount_experiments = list(/datum/experiment/scanning/points/anomalies = TECHWEB_TIER_5_POINTS)
diff --git a/code/modules/research/techweb/nodes/security_nodes.dm b/code/modules/research/techweb/nodes/security_nodes.dm
index b876b3ec677ea..2d3dd63864f25 100644
--- a/code/modules/research/techweb/nodes/security_nodes.dm
+++ b/code/modules/research/techweb/nodes/security_nodes.dm
@@ -4,6 +4,7 @@
display_name = "Basic Arms"
description = "Ballistics can be unpredictable in space."
design_ids = list(
+ "toy_armblade",
"toygun",
"c38_rubber",
"sec_38",
@@ -21,7 +22,6 @@
description = "All the essentials to subdue a mime."
prereq_ids = list(TECHWEB_NODE_BASIC_ARMS)
design_ids = list(
- "camera_assembly",
"secdata",
"mining",
"prisonmanage",
@@ -30,12 +30,10 @@
"security_photobooth",
"photobooth",
"scanner_gate",
- "turret_control",
"pepperspray",
"dragnet_beacon",
"inspector",
"evidencebag",
- "handcuffs_s",
"zipties",
"seclite",
"electropack",
@@ -48,10 +46,12 @@
description = "When you are on the opposing side of a revolutionary movement."
prereq_ids = list(TECHWEB_NODE_SEC_EQUIP)
design_ids = list(
+ "clown_firing_pin",
"pin_testing",
"pin_loyalty",
"tele_shield",
"ballistic_shield",
+ "handcuffs_s",
"bola_energy",
)
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_2_POINTS)
@@ -68,7 +68,6 @@
)
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_3_POINTS)
required_experiments = list(/datum/experiment/ordnance/explosive/lowyieldbomb)
- discount_experiments = list(/datum/experiment/ordnance/explosive/highyieldbomb = TECHWEB_TIER_3_POINTS)
/datum/techweb_node/exotic_ammo
id = TECHWEB_NODE_EXOTIC_AMMO
@@ -81,6 +80,7 @@
"techshotshell",
)
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_4_POINTS)
+ discount_experiments = list(/datum/experiment/ordnance/explosive/highyieldbomb = TECHWEB_TIER_4_POINTS)
/datum/techweb_node/electric_weapons
id = TECHWEB_NODE_ELECTRIC_WEAPONS
diff --git a/code/modules/research/techweb/nodes/service_nodes.dm b/code/modules/research/techweb/nodes/service_nodes.dm
index da7c48f0878c7..1553f3a0c20d8 100644
--- a/code/modules/research/techweb/nodes/service_nodes.dm
+++ b/code/modules/research/techweb/nodes/service_nodes.dm
@@ -52,19 +52,6 @@
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_2_POINTS)
discount_experiments = list(/datum/experiment/scanning/random/janitor_trash = TECHWEB_TIER_2_POINTS)
-/datum/techweb_node/toys
- id = TECHWEB_NODE_TOYS
- display_name = "New Toys"
- description = "For new pranks."
- prereq_ids = list(TECHWEB_NODE_OFFICE_EQUIP)
- design_ids = list(
- "smoke_machine",
- "toy_armblade",
- "air_horn",
- "clown_firing_pin",
- )
- research_costs = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_1_POINTS)
-
/datum/techweb_node/consoles
id = TECHWEB_NODE_CONSOLES
display_name = "Civilian Consoles"
@@ -96,7 +83,7 @@
id = TECHWEB_NODE_GAMING
display_name = "Gaming"
description = "For the slackers on the station."
- prereq_ids = list(TECHWEB_NODE_TOYS, TECHWEB_NODE_CONSOLES)
+ prereq_ids = list(TECHWEB_NODE_CONSOLES)
design_ids = list(
"arcade_battle",
"arcade_orion",
@@ -195,44 +182,3 @@
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_3_POINTS)
// only available if you've done the first fishing experiment (thus unlocking fishing tech), but not a strict requirement to get the tech
discount_experiments = list(/datum/experiment/scanning/fish/second = TECHWEB_TIER_3_POINTS)
-
-// Botany root node
-/datum/techweb_node/botany_equip
- id = TECHWEB_NODE_BOTANY_EQUIP
- starting_node = TRUE
- display_name = "Botany Equipment"
- description = "Essential tools for maintaining onboard gardens, supporting plant growth in the unique environment of the space station."
- design_ids = list(
- "seed_extractor",
- "plant_analyzer",
- "watering_can",
- "spade",
- "cultivator",
- "secateurs",
- "hatchet",
- )
-
-/datum/techweb_node/hydroponics
- id = TECHWEB_NODE_HYDROPONICS
- display_name = "Hydroponics"
- description = "Research into advanced hydroponic systems for efficient and sustainable plant cultivation."
- prereq_ids = list(TECHWEB_NODE_BOTANY_EQUIP, TECHWEB_NODE_CHEM_SYNTHESIS)
- design_ids = list(
- "biogenerator",
- "hydro_tray",
- "portaseeder",
- )
- research_costs = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_2_POINTS)
-
-/datum/techweb_node/selection
- id = TECHWEB_NODE_SELECTION
- display_name = "Artificial Selection"
- description = "Advancement in plant cultivation techniques through artificial selection, enabling precise manipulation of plant DNA."
- prereq_ids = list(TECHWEB_NODE_HYDROPONICS)
- design_ids = list(
- "flora_gun",
- "gene_shears",
- )
- research_costs = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_3_POINTS)
- required_experiments = list(/datum/experiment/scanning/random/plants/wild)
- discount_experiments = list(/datum/experiment/scanning/random/plants/traits = TECHWEB_TIER_3_POINTS)
diff --git a/code/modules/research/techweb/nodes/surgery_nodes.dm b/code/modules/research/techweb/nodes/surgery_nodes.dm
index 2bd1f6d5df6d0..0b8812191e2a9 100644
--- a/code/modules/research/techweb/nodes/surgery_nodes.dm
+++ b/code/modules/research/techweb/nodes/surgery_nodes.dm
@@ -56,7 +56,7 @@
"surgery_viral_bond",
)
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_3_POINTS)
- required_experiments = list(/datum/experiment/autopsy/nonhuman)
+ discount_experiments = list(/datum/experiment/autopsy/nonhuman = TECHWEB_TIER_3_POINTS)
/datum/techweb_node/surgery_tools
id = TECHWEB_NODE_SURGERY_TOOLS
diff --git a/code/modules/research/xenobiology/crossbreeding/_clothing.dm b/code/modules/research/xenobiology/crossbreeding/_clothing.dm
index c174856a62a31..9bd9e2d881b1c 100644
--- a/code/modules/research/xenobiology/crossbreeding/_clothing.dm
+++ b/code/modules/research/xenobiology/crossbreeding/_clothing.dm
@@ -38,8 +38,13 @@ Slimecrossing Armor
icon = 'icons/obj/science/slimecrossing.dmi'
icon_state = "prismglasses"
actions_types = list(/datum/action/item_action/change_prism_colour, /datum/action/item_action/place_light_prism)
+ forced_glass_color = TRUE
var/glasses_color = COLOR_WHITE
+/obj/item/clothing/glasses/prism_glasses/Initialize(mapload)
+ . = ..()
+ AddElement(/datum/element/wearable_client_colour, /datum/client_colour/glass_colour, ITEM_SLOT_EYES, glasses_color, forced_glass_color)
+
/obj/item/clothing/glasses/prism_glasses/item_action_slot_check(slot)
if(slot & ITEM_SLOT_EYES)
return TRUE
@@ -76,7 +81,9 @@ Slimecrossing Armor
var/new_color = input(owner, "Choose the lens color:", "Color change",glasses.glasses_color) as color|null
if(!new_color)
return
+ RemoveElement(/datum/element/wearable_client_colour, /datum/client_colour/glass_colour, ITEM_SLOT_EYES, glasses.glasses_color, glasses.forced_glass_color)
glasses.glasses_color = new_color
+ AddElement(/datum/element/wearable_client_colour, /datum/client_colour/glass_colour, ITEM_SLOT_EYES, new_color, glasses.forced_glass_color)
/datum/action/item_action/place_light_prism
name = "Fabricate Light Prism"
diff --git a/code/modules/research/xenobiology/vatgrowing/biopsy_tool.dm b/code/modules/research/xenobiology/vatgrowing/biopsy_tool.dm
index 8b0b007adee40..d04a7f852da3f 100644
--- a/code/modules/research/xenobiology/vatgrowing/biopsy_tool.dm
+++ b/code/modules/research/xenobiology/vatgrowing/biopsy_tool.dm
@@ -1,7 +1,7 @@
///Tool capable of taking biological samples from mobs
/obj/item/biopsy_tool
name = "biopsy tool"
- desc = "Don't worry, it won't sting."
+ desc = "Used to retrieve cell lines from organisms. Don't worry, it won't sting."
icon = 'icons/obj/science/vatgrowing.dmi'
icon_state = "biopsy"
worn_icon_state = "biopsy"
diff --git a/code/modules/research/xenobiology/vatgrowing/microscope.dm b/code/modules/research/xenobiology/vatgrowing/microscope.dm
index df065698436a4..c3c7369981e19 100644
--- a/code/modules/research/xenobiology/vatgrowing/microscope.dm
+++ b/code/modules/research/xenobiology/vatgrowing/microscope.dm
@@ -128,7 +128,5 @@
reqs = list(
/obj/item/stack/sheet/glass = 1,
/obj/item/stack/sheet/plastic = 1,
- /obj/item/stock_parts/scanning_module = 1,
- /obj/item/flashlight = 1,
)
category = CAT_CHEMISTRY
diff --git a/code/modules/research/xenobiology/vatgrowing/samples/_micro_organism.dm b/code/modules/research/xenobiology/vatgrowing/samples/_micro_organism.dm
index b8af4dd062c41..46d643aa532a7 100644
--- a/code/modules/research/xenobiology/vatgrowing/samples/_micro_organism.dm
+++ b/code/modules/research/xenobiology/vatgrowing/samples/_micro_organism.dm
@@ -31,7 +31,7 @@
var/resulting_atom_count = 1
///Handles growth of the micro_organism. This only runs if the micro organism is in the growing vat. Reagents is the growing vats reagents
-/datum/micro_organism/cell_line/proc/handle_growth(obj/machinery/plumbing/growing_vat/vat)
+/datum/micro_organism/cell_line/proc/handle_growth(obj/machinery/vatgrower/vat)
if(!try_eat(vat.reagents))
return FALSE
growth = max(growth, growth + calculate_growth(vat.reagents, vat.biological_sample)) //Prevent you from having minus growth.
@@ -74,7 +74,7 @@
. -= virus_suspectibility
///Called once a cell line reaches 100 growth. Then we check if any cell_line is too far so we can perform an epic fail roll
-/datum/micro_organism/cell_line/proc/finish_growing(obj/machinery/plumbing/growing_vat/vat)
+/datum/micro_organism/cell_line/proc/finish_growing(obj/machinery/vatgrower/vat)
var/risk = 0 //Penalty for failure, goes up based on how much growth the other cell_lines have
for(var/datum/micro_organism/cell_line/cell_line in vat.biological_sample.micro_organisms)
@@ -90,7 +90,7 @@
succeed_growing(vat)
return TRUE
-/datum/micro_organism/cell_line/proc/fuck_up_growing(obj/machinery/plumbing/growing_vat/vat)
+/datum/micro_organism/cell_line/proc/fuck_up_growing(obj/machinery/vatgrower/vat)
vat.visible_message(span_warning("The biological sample in [vat] seems to have dissipated!"))
if(prob(50))
new /obj/effect/gibspawner/generic(get_turf(vat)) //Spawn some gibs.
@@ -98,7 +98,7 @@
return
QDEL_NULL(vat.biological_sample)
-/datum/micro_organism/cell_line/proc/succeed_growing(obj/machinery/plumbing/growing_vat/vat)
+/datum/micro_organism/cell_line/proc/succeed_growing(obj/machinery/vatgrower/vat)
var/datum/effect_system/fluid_spread/smoke/smoke = new
smoke.set_up(0, holder = vat, location = vat.loc)
smoke.start()
@@ -112,18 +112,18 @@
///Overriden to show more info like needs, supplementary and supressive reagents and also growth.
/datum/micro_organism/cell_line/get_details(show_details)
- . += "[span_notice("[desc] - growth progress: [growth]%")]\n"
+ . += "[span_notice("[desc] - growth progress: [growth]%")]"
if(show_details)
- . += return_reagent_text("It requires:", required_reagents)
- . += return_reagent_text("It likes:", supplementary_reagents)
- . += return_reagent_text("It hates:", suppressive_reagents)
+ . += "\n- " + return_reagent_text("Requires:", required_reagents)
+ . += "\n- " + return_reagent_text("Likes:", supplementary_reagents)
+ . += "\n- " + return_reagent_text("Hates:", suppressive_reagents)
///Return a nice list of all the reagents in a specific category with a specific prefix. This needs to be reworked because the formatting sucks ass.
/datum/micro_organism/cell_line/proc/return_reagent_text(prefix_text = "It requires:", list/reagentlist)
if(!reagentlist.len)
return
- var/all_reagents_text
+ var/list/reagent_names = list()
for(var/i in reagentlist)
var/datum/reagent/reagent = i
- all_reagents_text += " - [initial(reagent.name)]\n"
- return span_notice("[prefix_text]\n[all_reagents_text]")
+ reagent_names += initial(reagent.name)
+ return span_notice("[prefix_text] [jointext(reagent_names, ", ")]")
diff --git a/code/modules/research/xenobiology/vatgrowing/samples/_sample.dm b/code/modules/research/xenobiology/vatgrowing/samples/_sample.dm
index eda9d2771ebf0..7838977bd4d48 100644
--- a/code/modules/research/xenobiology/vatgrowing/samples/_sample.dm
+++ b/code/modules/research/xenobiology/vatgrowing/samples/_sample.dm
@@ -31,7 +31,7 @@
return TRUE
///Call handle_growth on all our microorganisms.
-/datum/biological_sample/proc/handle_growth(obj/machinery/plumbing/growing_vat/vat)
+/datum/biological_sample/proc/handle_growth(obj/machinery/vatgrower/vat)
for(var/datum/micro_organism/cell_line/organism in micro_organisms) //Types because we don't grow viruses.
organism.handle_growth(vat)
diff --git a/code/modules/research/xenobiology/vatgrowing/samples/cell_lines/common.dm b/code/modules/research/xenobiology/vatgrowing/samples/cell_lines/common.dm
index 8dd4594d47d65..571440f84427f 100644
--- a/code/modules/research/xenobiology/vatgrowing/samples/cell_lines/common.dm
+++ b/code/modules/research/xenobiology/vatgrowing/samples/cell_lines/common.dm
@@ -450,7 +450,7 @@
virus_suspectibility = 0.5
resulting_atom = /mob/living/simple_animal/hostile/vatbeast
-/datum/micro_organism/cell_line/vat_beast/succeed_growing(obj/machinery/plumbing/growing_vat/vat)
+/datum/micro_organism/cell_line/vat_beast/succeed_growing(obj/machinery/vatgrower/vat)
. = ..()
qdel(vat)
@@ -476,11 +476,11 @@
virus_suspectibility = 0
-/datum/micro_organism/cell_line/netherworld/succeed_growing(obj/machinery/plumbing/growing_vat/vat)
+/datum/micro_organism/cell_line/netherworld/succeed_growing(obj/machinery/vatgrower/vat)
resulting_atom = pick(/mob/living/basic/creature, /mob/living/basic/migo, /mob/living/basic/blankbody) //i looked myself, pretty much all of them are reasonably strong and somewhat on the same level. except migo is the jackpot and the blank body is whiff.
return ..()
-/datum/micro_organism/cell_line/clown/fuck_up_growing(obj/machinery/plumbing/growing_vat/vat)
+/datum/micro_organism/cell_line/clown/fuck_up_growing(obj/machinery/vatgrower/vat)
vat.visible_message(span_warning("The biological sample in [vat] seems to have created something horrific!"))
var/mob/selected_mob = pick(list(/mob/living/basic/clown/mutant/slow, /mob/living/basic/clown/fleshclown))
@@ -656,7 +656,7 @@
virus_suspectibility = 0
resulting_atom = /obj/item/queen_bee/bought
-/datum/micro_organism/cell_line/queen_bee/fuck_up_growing(obj/machinery/plumbing/growing_vat/vat) //we love job hazards
+/datum/micro_organism/cell_line/queen_bee/fuck_up_growing(obj/machinery/vatgrower/vat) //we love job hazards
vat.visible_message(span_warning("You hear angry buzzing coming from the inside of the vat!"))
for(var/i in 1 to 5)
new /mob/living/basic/bee(get_turf(vat))
diff --git a/code/modules/plumbing/plumbers/vatgrower.dm b/code/modules/research/xenobiology/vatgrowing/vatgrower.dm
similarity index 56%
rename from code/modules/plumbing/plumbers/vatgrower.dm
rename to code/modules/research/xenobiology/vatgrowing/vatgrower.dm
index 1dcfb08e0cce0..5b35bd10edf5e 100644
--- a/code/modules/plumbing/plumbers/vatgrower.dm
+++ b/code/modules/research/xenobiology/vatgrowing/vatgrower.dm
@@ -1,33 +1,52 @@
///Used to make mobs from microbiological samples. Grow grow grow.
-/obj/machinery/plumbing/growing_vat
+/obj/machinery/vatgrower
name = "growing vat"
desc = "Tastes just like the chef's soup."
+ icon = 'icons/obj/science/vatgrowing.dmi'
icon_state = "growing_vat"
- buffer = 300
-
+ density = TRUE
+ pass_flags_self = PASSMACHINE | LETPASSTHROW
+ circuit = /obj/item/circuitboard/machine/vatgrower
+ use_power = NO_POWER_USE
+ ///Soup container reagents
+ var/reagent_volume = 300
+ var/reagent_flags = OPENCONTAINER | DUNKABLE
///List of all microbiological samples in this soup.
var/datum/biological_sample/biological_sample
///If the vat will restart the sample upon completion
var/resampler_active = FALSE
-///Add that sexy demnand component
-/obj/machinery/plumbing/growing_vat/Initialize(mapload, bolt, layer)
+/obj/machinery/vatgrower/Initialize(mapload, bolt, layer)
. = ..()
- AddComponent(/datum/component/plumbing/simple_demand, bolt, layer)
-
-/obj/machinery/plumbing/growing_vat/create_reagents(max_vol, flags)
+ create_reagents(reagent_volume, reagent_flags)
+
+ AddComponent(/datum/component/simple_rotation)
+ AddComponent(/datum/component/plumbing/simple_demand)
+
+ var/static/list/hovering_item_typechecks = list(
+ /obj/item/petri_dish = list(
+ SCREENTIP_CONTEXT_LMB = "Add Sample",
+ ),
+ /obj/item/reagent_containers = list(
+ SCREENTIP_CONTEXT_LMB = "Pour Reagents",
+ ),
+ )
+ AddElement(/datum/element/contextual_screentip_item_typechecks, hovering_item_typechecks)
+ AddElement(/datum/element/contextual_screentip_bare_hands, lmb_text = "Toggle Resampler", rmb_text = "Flush Soup")
+
+/obj/machinery/vatgrower/create_reagents(max_vol, flags)
. = ..()
RegisterSignals(reagents, list(COMSIG_REAGENTS_NEW_REAGENT, COMSIG_REAGENTS_ADD_REAGENT, COMSIG_REAGENTS_DEL_REAGENT, COMSIG_REAGENTS_REM_REAGENT), PROC_REF(on_reagent_change))
RegisterSignal(reagents, COMSIG_QDELETING, PROC_REF(on_reagents_del))
/// Handles properly detaching signal hooks.
-/obj/machinery/plumbing/growing_vat/proc/on_reagents_del(datum/reagents/reagents)
+/obj/machinery/vatgrower/proc/on_reagents_del(datum/reagents/reagents)
SIGNAL_HANDLER
UnregisterSignal(reagents, list(COMSIG_REAGENTS_NEW_REAGENT, COMSIG_REAGENTS_ADD_REAGENT, COMSIG_REAGENTS_DEL_REAGENT, COMSIG_REAGENTS_REM_REAGENT, COMSIG_QDELETING))
return NONE
///When we process, we make use of our reagents to try and feed the samples we have.
-/obj/machinery/plumbing/growing_vat/process(seconds_per_tick)
+/obj/machinery/vatgrower/process(seconds_per_tick)
if(!is_operational)
return
if(!biological_sample)
@@ -39,35 +58,71 @@
audible_message(pick(list(span_notice("[src] grumbles!"), span_notice("[src] makes a splashing noise!"), span_notice("[src] sloshes!"))))
use_energy(active_power_usage * seconds_per_tick)
-///Handles the petri dish depositing into the vat.
-/obj/machinery/plumbing/growing_vat/attacked_by(obj/item/I, mob/living/user)
- if(!istype(I, /obj/item/petri_dish))
- return ..()
+/obj/machinery/vatgrower/item_interaction(mob/living/user, obj/item/tool, list/modifiers)
+ . = ..()
+ if(istype(tool, /obj/item/petri_dish))
+ return deposit_sample(user, tool)
- var/obj/item/petri_dish/petri = I
+/obj/machinery/vatgrower/screwdriver_act(mob/living/user, obj/item/tool)
+ . = ..()
+ if(default_deconstruction_screwdriver(user, icon_state, icon_state, tool))
+ return ITEM_INTERACT_SUCCESS
- if(!petri.sample)
- return ..()
+/obj/machinery/vatgrower/crowbar_act(mob/living/user, obj/item/tool)
+ . = ..()
+ if(default_deconstruction_crowbar(tool))
+ return ITEM_INTERACT_SUCCESS
- if(biological_sample)
- to_chat(user, span_warning("There is already a sample in the vat!"))
+/obj/machinery/vatgrower/wrench_act(mob/living/user, obj/item/tool)
+ . = ..()
+ if(default_unfasten_wrench(user, tool))
+ return ITEM_INTERACT_SUCCESS
+
+/obj/machinery/vatgrower/attack_hand(mob/living/user, list/modifiers)
+ . = ..()
+ playsound(src, 'sound/machines/click.ogg', 30, TRUE)
+ if(obj_flags & EMAGGED)
return
- deposit_sample(user, petri)
+ resampler_active = !resampler_active
+ balloon_alert(user, "resampler [resampler_active ? "activated" : "deactivated"]")
+ update_appearance()
+
+/obj/machinery/vatgrower/attack_hand_secondary(mob/user, list/modifiers)
+ . = ..()
+ if(. == SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN)
+ return
+ if(!anchored)
+ return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
+ var/warning = tgui_alert(user, "Are you sure you want to empty the soup container?","Flush soup container?", list("Flush", "Cancel"))
+ if(warning == "Flush" && user.can_perform_action(src))
+ reagents.clear_reagents()
+ if(biological_sample)
+ QDEL_NULL(biological_sample)
+ balloon_alert(user, "container empty")
+ update_appearance()
+ return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
///Creates a clone of the supplied sample and puts it in the vat
-/obj/machinery/plumbing/growing_vat/proc/deposit_sample(mob/user, obj/item/petri_dish/petri)
+/obj/machinery/vatgrower/proc/deposit_sample(mob/user, obj/item/petri_dish/petri)
+ if(!petri.sample)
+ balloon_alert(user, "dish empty")
+ return ITEM_INTERACT_FAILURE
+ if(biological_sample)
+ balloon_alert(user, "already has a sample")
+ return ITEM_INTERACT_FAILURE
biological_sample = new
for(var/datum/micro_organism/m in petri.sample.micro_organisms)
biological_sample.micro_organisms += new m.type()
biological_sample.sample_layers = petri.sample.sample_layers
biological_sample.sample_color = petri.sample.sample_color
- to_chat(user, span_warning("You put some of the sample in the vat!"))
+ balloon_alert(user, "added sample")
playsound(src, 'sound/effects/bubbles.ogg', 50, TRUE)
update_appearance()
RegisterSignal(biological_sample, COMSIG_SAMPLE_GROWTH_COMPLETED, PROC_REF(on_sample_growth_completed))
+ return ITEM_INTERACT_SUCCESS
///Adds text for when there is a sample in the vat
-/obj/machinery/plumbing/growing_vat/examine_more(mob/user)
+/obj/machinery/vatgrower/examine(mob/user)
. = ..()
if(!biological_sample)
return
@@ -76,18 +131,14 @@
var/datum/micro_organism/MO = i
. += MO.get_details(HAS_TRAIT(user, TRAIT_RESEARCH_SCANNER))
-/obj/machinery/plumbing/growing_vat/plunger_act(obj/item/plunger/P, mob/living/user, reinforced)
- . = ..()
- QDEL_NULL(biological_sample)
-
/// Call update icon when reagents change to update the reagent content icons. Eats signal args.
-/obj/machinery/plumbing/growing_vat/proc/on_reagent_change(datum/reagents/holder, ...)
+/obj/machinery/vatgrower/proc/on_reagent_change(datum/reagents/holder, ...)
SIGNAL_HANDLER
update_appearance()
return NONE
///Adds overlays to show the reagent contents
-/obj/machinery/plumbing/growing_vat/update_overlays()
+/obj/machinery/vatgrower/update_overlays()
. = ..()
var/static/image/on_overlay
var/static/image/off_overlay
@@ -113,16 +164,7 @@
var/mutable_appearance/bubbles_overlay = mutable_appearance(icon, "vat_bubbles")
. += bubbles_overlay
-/obj/machinery/plumbing/growing_vat/attack_hand(mob/living/user, list/modifiers)
- . = ..()
- playsound(src, 'sound/machines/click.ogg', 30, TRUE)
- if(obj_flags & EMAGGED)
- return
- resampler_active = !resampler_active
- balloon_alert_to_viewers("resampler [resampler_active ? "activated" : "deactivated"]")
- update_appearance()
-
-/obj/machinery/plumbing/growing_vat/emag_act(mob/user, obj/item/card/emag/emag_card)
+/obj/machinery/vatgrower/emag_act(mob/user, obj/item/card/emag/emag_card)
if(obj_flags & EMAGGED)
return FALSE
obj_flags |= EMAGGED
@@ -131,7 +173,7 @@
flick("growing_vat_emagged", src)
return TRUE
-/obj/machinery/plumbing/growing_vat/proc/on_sample_growth_completed()
+/obj/machinery/vatgrower/proc/on_sample_growth_completed()
SIGNAL_HANDLER
if(resampler_active)
addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(playsound), get_turf(src), 'sound/effects/servostep.ogg', 100, 1), 1.5 SECONDS)
diff --git a/code/modules/research/xenobiology/xenobiology.dm b/code/modules/research/xenobiology/xenobiology.dm
index 34d7f4d507a46..9f22520d6ab10 100644
--- a/code/modules/research/xenobiology/xenobiology.dm
+++ b/code/modules/research/xenobiology/xenobiology.dm
@@ -10,7 +10,7 @@
throwforce = 0
throw_speed = 3
throw_range = 6
- grind_results = list()
+ grind_results = list(/datum/reagent/toxin/slimejelly = 20)
///uses before it goes inert
var/extract_uses = 1
///deletion timer, for delayed reactions
@@ -44,11 +44,6 @@
. = ..()
create_reagents(100, INJECTABLE | DRAWABLE)
-/obj/item/slime_extract/on_grind()
- . = ..()
- if(extract_uses)
- grind_results[/datum/reagent/toxin/slimejelly] = 20
-
/**
* Effect when activated by a Luminescent.
*
diff --git a/code/modules/shuttle/shuttle_rotate.dm b/code/modules/shuttle/shuttle_rotate.dm
index 734e2337df518..cb7cad65b6ba1 100644
--- a/code/modules/shuttle/shuttle_rotate.dm
+++ b/code/modules/shuttle/shuttle_rotate.dm
@@ -12,7 +12,7 @@ If ever any of these procs are useful for non-shuttles, rename it to proc/rotate
setDir(angle2dir(rotation+dir2angle(dir)))
//resmooth if need be.
- if(params & ROTATE_SMOOTH && smoothing_flags & (SMOOTH_CORNERS|SMOOTH_BITMASK))
+ if(params & ROTATE_SMOOTH && smoothing_flags & USES_SMOOTHING)
QUEUE_SMOOTH(src)
//rotate the pixel offsets too.
diff --git a/code/modules/shuttle/special.dm b/code/modules/shuttle/special.dm
index e5b4b0eb02439..484fa32ac8777 100644
--- a/code/modules/shuttle/special.dm
+++ b/code/modules/shuttle/special.dm
@@ -403,7 +403,6 @@
desc = "Communism powerful force."
icon = 'icons/effects/96x96.dmi'
icon_state = "communist"
- layer = ABOVE_OPEN_TURF_LAYER
pixel_x = -32
pixel_y = -32
diff --git a/code/modules/spells/spell_types/jaunt/bloodcrawl.dm b/code/modules/spells/spell_types/jaunt/bloodcrawl.dm
index 60a5d013217f9..08c1f5a831f7b 100644
--- a/code/modules/spells/spell_types/jaunt/bloodcrawl.dm
+++ b/code/modules/spells/spell_types/jaunt/bloodcrawl.dm
@@ -164,6 +164,10 @@
var/consume_sound = 'sound/magic/demon_consume.ogg'
/// consume count (statistics and stuff)
var/consume_count = 0
+ /// Apply damage every 20 seconds if we bloodcrawling
+ var/jaunt_damage_timer
+ /// When demon first appears, it does not take damage while in Jaunt. He also doesn't take damage while he's eating someone.
+ var/resist_jaunt_damage = TRUE
/datum/action/cooldown/spell/jaunt/bloodcrawl/slaughter_demon/try_enter_jaunt(obj/effect/decal/cleanable/blood, mob/living/jaunter)
// Save this before the actual jaunt
@@ -174,12 +178,14 @@
if(!.)
return
+ jaunt_damage_timer = addtimer(CALLBACK(src, PROC_REF(damage_for_lazy_demon), jaunter), 20 SECONDS, TIMER_STOPPABLE)
+
var/turf/jaunt_turf = get_turf(jaunter)
// if we're not pulling anyone, or we can't what we're pulling
- if(!isliving(coming_with))
+ if(!ishuman(coming_with))
return
- var/mob/living/victim = coming_with
+ var/mob/living/carbon/human/victim = coming_with
if(victim.stat == CONSCIOUS)
jaunt_turf.visible_message(
@@ -204,6 +210,28 @@
return TRUE
+/datum/action/cooldown/spell/jaunt/bloodcrawl/slaughter_demon/on_jaunt_exited(obj/effect/dummy/phased_mob/jaunt, mob/living/unjaunter)
+ deltimer(jaunt_damage_timer)
+ resist_jaunt_damage = FALSE
+ return ..()
+
+/**
+ * Apply damage to demon when he using bloodcrawl.
+ * Every 20 SECONDS check if demon still crawling and update timer.
+ */
+/datum/action/cooldown/spell/jaunt/bloodcrawl/slaughter_demon/proc/damage_for_lazy_demon(mob/living/lazy_demon)
+ if(QDELETED(lazy_demon))
+ return
+ if(resist_jaunt_damage)
+ return
+ if(isturf(lazy_demon.loc))
+ return
+ if(isnull(jaunt_damage_timer))
+ return
+ lazy_demon.apply_damage(lazy_demon.maxHealth * 0.05, BRUTE)
+ jaunt_damage_timer = addtimer(CALLBACK(src, PROC_REF(damage_for_lazy_demon), lazy_demon), 20 SECONDS, TIMER_STOPPABLE)
+ to_chat(lazy_demon, span_warning("You feel your flesh dissolving into the sea of blood. You shouldn't stay in Blood Crawl for too long!"))
+
/**
* Consumes the [victim] from the [jaunter], fully healing them
* and calling [proc/on_victim_consumed] if successful.)
@@ -236,12 +264,18 @@
* Called when a victim starts to be consumed.
*/
/datum/action/cooldown/spell/jaunt/bloodcrawl/slaughter_demon/proc/on_victim_start_consume(mob/living/victim, mob/living/jaunter)
+ if(!iscarbon(jaunter))
+ resist_jaunt_damage = TRUE
+ deltimer(jaunt_damage_timer)
to_chat(jaunter, span_danger("You begin to feast on [victim]... You can not move while you are doing this."))
/**
* Called when a victim is successfully consumed.
*/
/datum/action/cooldown/spell/jaunt/bloodcrawl/slaughter_demon/proc/on_victim_consumed(mob/living/victim, mob/living/jaunter)
+ if(!iscarbon(jaunter))
+ resist_jaunt_damage = FALSE
+ jaunt_damage_timer = addtimer(CALLBACK(src, PROC_REF(damage_for_lazy_demon), jaunter), 20 SECONDS, TIMER_STOPPABLE)
to_chat(jaunter, span_danger("You devour [victim]. Your health is fully restored."))
qdel(victim)
diff --git a/code/modules/station_goals/bsa.dm b/code/modules/station_goals/bsa.dm
index c83f710d1df1d..e2feaa2bc26ed 100644
--- a/code/modules/station_goals/bsa.dm
+++ b/code/modules/station_goals/bsa.dm
@@ -50,13 +50,10 @@ GLOBAL_VAR_INIT(bsa_unlock, FALSE)
. = ..()
AddComponent(/datum/component/simple_rotation)
-/obj/machinery/bsa/back/multitool_act(mob/living/user, obj/item/I)
- if(!multitool_check_buffer(user, I)) //make sure it has a data buffer
- return
- var/obj/item/multitool/M = I
+/obj/machinery/bsa/back/multitool_act(mob/living/user, obj/item/multitool/M)
M.set_buffer(src)
balloon_alert(user, "saved to multitool buffer")
- return TRUE
+ return ITEM_INTERACT_SUCCESS
/obj/machinery/bsa/front
name = "Bluespace Artillery Bore"
@@ -67,13 +64,10 @@ GLOBAL_VAR_INIT(bsa_unlock, FALSE)
. = ..()
AddComponent(/datum/component/simple_rotation)
-/obj/machinery/bsa/front/multitool_act(mob/living/user, obj/item/I)
- if(!multitool_check_buffer(user, I)) //make sure it has a data buffer
- return
- var/obj/item/multitool/M = I
+/obj/machinery/bsa/front/multitool_act(mob/living/user, obj/item/multitool/M)
M.set_buffer(src)
balloon_alert(user, "saved to multitool buffer")
- return TRUE
+ return ITEM_INTERACT_SUCCESS
/obj/machinery/bsa/middle
name = "Bluespace Artillery Fusor"
@@ -86,22 +80,19 @@ GLOBAL_VAR_INIT(bsa_unlock, FALSE)
. = ..()
AddComponent(/datum/component/simple_rotation)
-/obj/machinery/bsa/middle/multitool_act(mob/living/user, obj/item/I)
- if(!multitool_check_buffer(user, I))
- return
- var/obj/item/multitool/M = I
- if(M.buffer)
- if(istype(M.buffer, /obj/machinery/bsa/back))
- back_ref = WEAKREF(M.buffer)
- to_chat(user, span_notice("You link [src] with [M.buffer]."))
- M.set_buffer(null)
- else if(istype(M.buffer, /obj/machinery/bsa/front))
- front_ref = WEAKREF(M.buffer)
- to_chat(user, span_notice("You link [src] with [M.buffer]."))
- M.set_buffer(null)
- else
- to_chat(user, span_warning("[I]'s data buffer is empty!"))
- return TRUE
+/obj/machinery/bsa/middle/multitool_act(mob/living/user, obj/item/multitool/tool)
+ . = NONE
+
+ if(istype(tool.buffer, /obj/machinery/bsa/back))
+ back_ref = WEAKREF(tool.buffer)
+ to_chat(user, span_notice("You link [src] with [tool.buffer]."))
+ tool.set_buffer(null)
+ return ITEM_INTERACT_SUCCESS
+ else if(istype(tool.buffer, /obj/machinery/bsa/front))
+ front_ref = WEAKREF(tool.buffer)
+ to_chat(user, span_notice("You link [src] with [tool.buffer]."))
+ tool.set_buffer(null)
+ return ITEM_INTERACT_SUCCESS
/obj/machinery/bsa/middle/proc/check_completion()
var/obj/machinery/bsa/front/front = front_ref?.resolve()
diff --git a/code/modules/surgery/blood_filter.dm b/code/modules/surgery/blood_filter.dm
index ae5b438492fbc..ca395ae45ab53 100644
--- a/code/modules/surgery/blood_filter.dm
+++ b/code/modules/surgery/blood_filter.dm
@@ -40,7 +40,7 @@
*/
/datum/surgery_step/filter_blood/proc/has_filterable_chems(mob/living/carbon/target, obj/item/blood_filter/bloodfilter)
if(!length(target.reagents?.reagent_list))
- bloodfilter.audible_message(span_notice("The [bloodfilter] pings as it reports no chemicals detected in [target]'s blood."))
+ bloodfilter.audible_message(span_notice("[bloodfilter] pings as it reports no chemicals detected in [target]'s blood."))
playsound(get_turf(target), 'sound/machines/ping.ogg', 75, TRUE, falloff_exponent = 12, falloff_distance = 1)
return FALSE
@@ -58,7 +58,7 @@
implements = list(/obj/item/blood_filter = 95)
repeatable = TRUE
time = 2.5 SECONDS
- success_sound = 'sound/machines/fan_loop.ogg'
+ success_sound = 'sound/machines/card_slide.ogg'
/datum/surgery_step/filter_blood/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery)
display_pain(target, "Ты чувствуешь ужасную боль в груди!")
@@ -68,7 +68,7 @@
if(target.reagents?.total_volume)
for(var/datum/reagent/chem as anything in target.reagents.reagent_list)
if(!length(bloodfilter.whitelist) || (chem.type in bloodfilter.whitelist))
- target.reagents.remove_reagent(chem.type, min(chem.volume * 0.22, 10))
+ target.reagents.remove_reagent(chem.type, clamp(round(chem.volume * 0.22, 0.2), 0.4, 10))
display_results(
user,
target,
diff --git a/code/modules/surgery/bodyparts/_bodyparts.dm b/code/modules/surgery/bodyparts/_bodyparts.dm
index c03a930395ab9..06599c2fb4809 100644
--- a/code/modules/surgery/bodyparts/_bodyparts.dm
+++ b/code/modules/surgery/bodyparts/_bodyparts.dm
@@ -351,7 +351,7 @@
check_list += "\t [span_boldwarning("Your [name] is suffering [wound.a_or_from] [LOWER_TEXT(wound.name)]!!!")]"
for(var/obj/item/embedded_thing in embedded_objects)
- var/stuck_word = embedded_thing.isEmbedHarmless() ? "stuck" : "embedded"
+ var/stuck_word = embedded_thing.is_embed_harmless() ? "stuck" : "embedded"
check_list += "\t There is \a [embedded_thing] [stuck_word] in your [name]!"
/obj/item/bodypart/blob_act()
@@ -1096,15 +1096,15 @@
if(embed in embedded_objects) // go away
return
// We don't need to do anything with projectile embedding, because it will never reach this point
- RegisterSignal(embed, COMSIG_ITEM_EMBEDDING_UPDATE, PROC_REF(embedded_object_changed))
embedded_objects += embed
+ RegisterSignal(embed, COMSIG_ITEM_EMBEDDING_UPDATE, PROC_REF(embedded_object_changed))
refresh_bleed_rate()
/// INTERNAL PROC, DO NOT USE
/// Cleans up any attachment we have to the embedded object, removes it from our list
/obj/item/bodypart/proc/_unembed_object(obj/item/unembed)
- UnregisterSignal(unembed, COMSIG_ITEM_EMBEDDING_UPDATE)
embedded_objects -= unembed
+ UnregisterSignal(unembed, COMSIG_ITEM_EMBEDDING_UPDATE)
refresh_bleed_rate()
/obj/item/bodypart/proc/embedded_object_changed(obj/item/embedded_source)
@@ -1157,7 +1157,7 @@
cached_bleed_rate += 0.5
for(var/obj/item/embeddies in embedded_objects)
- if(!embeddies.isEmbedHarmless())
+ if(!embeddies.is_embed_harmless())
cached_bleed_rate += 0.25
for(var/datum/wound/iter_wound as anything in wounds)
diff --git a/code/modules/surgery/bodyparts/head.dm b/code/modules/surgery/bodyparts/head.dm
index a8c482754116f..50a79aaa319b2 100644
--- a/code/modules/surgery/bodyparts/head.dm
+++ b/code/modules/surgery/bodyparts/head.dm
@@ -141,7 +141,7 @@
if (!can_dismember)
return FALSE
- if(owner.stat < HARD_CRIT)
+ if(!HAS_TRAIT(owner, TRAIT_CURSED) && owner.stat < HARD_CRIT)
return FALSE
return ..()
diff --git a/code/modules/surgery/bodyparts/helpers.dm b/code/modules/surgery/bodyparts/helpers.dm
index 863cdd9cb61c2..a0955af4e46c8 100644
--- a/code/modules/surgery/bodyparts/helpers.dm
+++ b/code/modules/surgery/bodyparts/helpers.dm
@@ -139,7 +139,7 @@
/mob/living/carbon/proc/has_embedded_objects(include_harmless=FALSE)
for(var/obj/item/bodypart/bodypart as anything in bodyparts)
for(var/obj/item/embedded in bodypart.embedded_objects)
- if(!include_harmless && embedded.isEmbedHarmless())
+ if(!include_harmless && embedded.is_embed_harmless())
continue
return TRUE
diff --git a/code/modules/surgery/bodyparts/parts.dm b/code/modules/surgery/bodyparts/parts.dm
index 14ccfb9d93f00..072673d23d345 100644
--- a/code/modules/surgery/bodyparts/parts.dm
+++ b/code/modules/surgery/bodyparts/parts.dm
@@ -48,7 +48,7 @@
old_owner.gib(DROP_ALL_REMAINS)
/obj/item/bodypart/chest/can_dismember(obj/item/item)
- if(owner.stat < HARD_CRIT || !contents.len)
+ if((!HAS_TRAIT(owner, TRAIT_CURSED) && owner.stat < HARD_CRIT) || !contents.len)
return FALSE
return ..()
diff --git a/code/modules/surgery/experimental_dissection.dm b/code/modules/surgery/experimental_dissection.dm
index ecdfab30b544c..93bacb6792d62 100644
--- a/code/modules/surgery/experimental_dissection.dm
+++ b/code/modules/surgery/experimental_dissection.dm
@@ -1,5 +1,5 @@
///How many research points you gain from dissecting a Human.
-#define BASE_HUMAN_REWARD 500
+#define BASE_HUMAN_REWARD 10
/datum/surgery/advanced/experimental_dissection
name = "Экспериментальное препарирование"
diff --git a/code/modules/surgery/organs/internal/cyberimp/augments_internal.dm b/code/modules/surgery/organs/internal/cyberimp/augments_internal.dm
index 89c6f9c96a411..a50912416ef87 100644
--- a/code/modules/surgery/organs/internal/cyberimp/augments_internal.dm
+++ b/code/modules/surgery/organs/internal/cyberimp/augments_internal.dm
@@ -119,12 +119,12 @@
/obj/item/organ/internal/cyberimp/brain/anti_stun/on_mob_remove(mob/living/carbon/implant_owner)
. = ..()
UnregisterSignal(implant_owner, signalCache)
- UnregisterSignal(implant_owner, COMSIG_CARBON_ENTER_STAMCRIT)
+ UnregisterSignal(implant_owner, COMSIG_LIVING_ENTER_STAMCRIT)
/obj/item/organ/internal/cyberimp/brain/anti_stun/on_mob_insert(mob/living/carbon/receiver)
. = ..()
RegisterSignals(receiver, signalCache, PROC_REF(on_signal))
- RegisterSignal(receiver, COMSIG_CARBON_ENTER_STAMCRIT, PROC_REF(on_stamcrit))
+ RegisterSignal(receiver, COMSIG_LIVING_ENTER_STAMCRIT, PROC_REF(on_stamcrit))
/obj/item/organ/internal/cyberimp/brain/anti_stun/proc/on_signal(datum/source, amount)
SIGNAL_HANDLER
@@ -139,23 +139,23 @@
/obj/item/organ/internal/cyberimp/brain/anti_stun/proc/clear_stuns()
if(isnull(owner) || (organ_flags & ORGAN_FAILING) || !COOLDOWN_FINISHED(src, implant_cooldown))
return
-
+
owner.SetStun(0)
owner.SetKnockdown(0)
owner.SetImmobilized(0)
owner.SetParalyzed(0)
owner.setStaminaLoss(0)
addtimer(CALLBACK(owner, TYPE_PROC_REF(/mob/living, setStaminaLoss), 0), stun_resistance_time)
-
+
var/datum/effect_system/spark_spread/sparks = new /datum/effect_system/spark_spread
sparks.set_up(5, 1, src)
sparks.start()
owner.add_traits(list(TRAIT_IGNOREDAMAGESLOWDOWN, TRAIT_BATON_RESISTANCE, TRAIT_STUNIMMUNE), REF(src))
- addtimer(TRAIT_CALLBACK_REMOVE(owner, TRAIT_IGNOREDAMAGESLOWDOWN, REF(src)), stun_resistance_time)
+ addtimer(TRAIT_CALLBACK_REMOVE(owner, TRAIT_IGNOREDAMAGESLOWDOWN, REF(src)), stun_resistance_time)
addtimer(TRAIT_CALLBACK_REMOVE(owner, TRAIT_BATON_RESISTANCE, REF(src)), stun_resistance_time)
addtimer(TRAIT_CALLBACK_REMOVE(owner, TRAIT_STUNIMMUNE, REF(src)), stun_resistance_time)
-
+
COOLDOWN_START(src, implant_cooldown, 60 SECONDS)
addtimer(CALLBACK(src, PROC_REF(implant_ready)),60 SECONDS)
@@ -171,7 +171,7 @@
addtimer(CALLBACK(src, PROC_REF(reboot)), 90 / severity)
/obj/item/organ/internal/cyberimp/brain/anti_stun/proc/reboot()
- organ_flags &= ~ORGAN_FAILING
+ organ_flags &= ~ORGAN_FAILING
implant_ready()
//[[[[MOUTH]]]]
diff --git a/code/modules/surgery/organs/internal/tongue/_tongue.dm b/code/modules/surgery/organs/internal/tongue/_tongue.dm
index 87dfb124a61a4..cbced0939aad4 100644
--- a/code/modules/surgery/organs/internal/tongue/_tongue.dm
+++ b/code/modules/surgery/organs/internal/tongue/_tongue.dm
@@ -184,23 +184,11 @@
liked_foodtypes = GORE | MEAT | SEAFOOD | NUTS | BUGS
disliked_foodtypes = GRAIN | DAIRY | CLOTH | GROSS
voice_filter = @{"[0:a] asplit [out0][out2]; [out0] asetrate=%SAMPLE_RATE%*0.9,aresample=%SAMPLE_RATE%,atempo=1/0.9,aformat=channel_layouts=mono,volume=0.2 [p0]; [out2] asetrate=%SAMPLE_RATE%*1.1,aresample=%SAMPLE_RATE%,atempo=1/1.1,aformat=channel_layouts=mono,volume=0.2[p2]; [p0][0][p2] amix=inputs=3"}
+ var/static/list/speech_replacements = list(new /regex("s+", "g") = "sss", new /regex("S+", "g") = "SSS", new /regex(@"(\w)x", "g") = "$1kss", new /regex(@"(\w)X", "g") = "$1KSSS", new /regex(@"\bx([\-|r|R]|\b)", "g") = "ecks$1", new /regex(@"\bX([\-|r|R]|\b)", "g") = "ECKS$1")
-/obj/item/organ/internal/tongue/lizard/modify_speech(datum/source, list/speech_args)
- var/static/regex/lizard_hiss = new("s+", "g")
- var/static/regex/lizard_hiSS = new("S+", "g")
- var/static/regex/lizard_kss = new(@"(\w)x", "g")
- var/static/regex/lizard_kSS = new(@"(\w)X", "g")
- var/static/regex/lizard_ecks = new(@"\bx([\-|r|R]|\b)", "g")
- var/static/regex/lizard_eckS = new(@"\bX([\-|r|R]|\b)", "g")
- var/message = speech_args[SPEECH_MESSAGE]
- if(message[1] != "*")
- message = lizard_hiss.Replace(message, "sss")
- message = lizard_hiSS.Replace(message, "SSS")
- message = lizard_kss.Replace(message, "$1kss")
- message = lizard_kSS.Replace(message, "$1KSS")
- message = lizard_ecks.Replace(message, "ecks$1")
- message = lizard_eckS.Replace(message, "ECKS$1")
- speech_args[SPEECH_MESSAGE] = message
+/obj/item/organ/internal/tongue/lizard/New(class, timer, datum/mutation/human/copymut)
+ . = ..()
+ AddComponent(/datum/component/speechmod, replacements = speech_replacements)
/obj/item/organ/internal/tongue/lizard/silver
name = "silver tongue"
diff --git a/code/modules/surgery/surgery.dm b/code/modules/surgery/surgery.dm
index 073208d633510..b3bab097f6cae 100644
--- a/code/modules/surgery/surgery.dm
+++ b/code/modules/surgery/surgery.dm
@@ -124,10 +124,6 @@
if(isnull(step))
return FALSE
var/obj/item/tool = user.get_active_held_item()
- if(istype(tool, /obj/item/borg/cyborg_omnitool)) //catches borg surgeries
- var/obj/item/borg/cyborg_omnitool/toolarm = tool
- if(toolarm.selected)
- tool = toolarm.selected
if(step.try_op(user, target, user.zone_selected, tool, src, try_to_fail))
return TRUE
if(tool && tool.item_flags & SURGICAL_TOOL) //Just because you used the wrong tool it doesn't mean you meant to whack the patient with it
diff --git a/code/modules/surgery/tools.dm b/code/modules/surgery/tools.dm
index 6c3ee9c014713..2bd8f485887f8 100644
--- a/code/modules/surgery/tools.dm
+++ b/code/modules/surgery/tools.dm
@@ -286,8 +286,8 @@
//saws are very accurate and fast at butchering
var/static/list/slapcraft_recipe_list = list(/datum/crafting_recipe/chainsaw)
- AddComponent(
- /datum/component/slapcrafting,\
+ AddElement(
+ /datum/element/slapcrafting,\
slapcraft_recipes = slapcraft_recipe_list,\
)
@@ -316,6 +316,7 @@
attack_verb_continuous = list("slaps")
attack_verb_simple = list("slap")
interaction_flags_atom = parent_type::interaction_flags_atom | INTERACT_ATOM_IGNORE_MOBILITY
+ tool_behaviour = TOOL_DRAPES
/obj/item/surgical_drapes/Initialize(mapload)
. = ..()
diff --git a/code/modules/transport/tram/tram_floors.dm b/code/modules/transport/tram/tram_floors.dm
index 9f0b6907fe9c1..f267ccf5cdc84 100644
--- a/code/modules/transport/tram/tram_floors.dm
+++ b/code/modules/transport/tram/tram_floors.dm
@@ -183,7 +183,7 @@
integrity_failure = 0.75
armor_type = /datum/armor/tram_floor
layer = TRAM_FLOOR_LAYER
- plane = FLOOR_PLANE
+ plane = GAME_PLANE
obj_flags = BLOCK_Z_OUT_DOWN | BLOCK_Z_OUT_UP
appearance_flags = PIXEL_SCALE|KEEP_TOGETHER
var/secured = TRUE
diff --git a/code/modules/transport/tram/tram_signals.dm b/code/modules/transport/tram/tram_signals.dm
index db8aa17ddcb3e..faf4a46e11e28 100644
--- a/code/modules/transport/tram/tram_signals.dm
+++ b/code/modules/transport/tram/tram_signals.dm
@@ -473,6 +473,7 @@
icon_state = "sensor-base"
desc = "Uses an infrared beam to detect passing trams. Works when paired with a sensor on the other side of the track."
layer = TRAM_RAIL_LAYER
+ plane = FLOOR_PLANE
use_power = NO_POWER_USE
circuit = /obj/item/circuitboard/machine/guideway_sensor
/// Sensors work in a married pair
diff --git a/code/modules/transport/transport_module.dm b/code/modules/transport/transport_module.dm
index 268452743e7b2..104601f2f6ca3 100644
--- a/code/modules/transport/transport_module.dm
+++ b/code/modules/transport/transport_module.dm
@@ -14,7 +14,7 @@
armor_type = /datum/armor/transport_module
max_integrity = 50
layer = TRAM_FLOOR_LAYER
- plane = FLOOR_PLANE
+ plane = GAME_PLANE
smoothing_flags = SMOOTH_BITMASK
smoothing_groups = SMOOTH_GROUP_INDUSTRIAL_LIFT
canSmoothWith = SMOOTH_GROUP_INDUSTRIAL_LIFT
diff --git a/code/modules/unit_tests/_unit_tests.dm b/code/modules/unit_tests/_unit_tests.dm
index 593b949e2b22f..8fafe30ac7b93 100644
--- a/code/modules/unit_tests/_unit_tests.dm
+++ b/code/modules/unit_tests/_unit_tests.dm
@@ -180,6 +180,7 @@
#include "map_landmarks.dm"
#include "mapload_space_verification.dm"
#include "mapping.dm"
+#include "mapping_nearstation_test.dm"
#include "mecha_damage.dm"
#include "medical_wounds.dm"
#include "merge_type.dm"
@@ -213,6 +214,7 @@
#include "pills.dm"
#include "plane_double_transform.dm"
#include "plane_dupe_detector.dm"
+#include "plane_sanity.dm"
#include "plantgrowth_tests.dm"
#include "preference_species.dm"
#include "preferences.dm"
diff --git a/code/modules/unit_tests/cyborg_tool.dm b/code/modules/unit_tests/cyborg_tool.dm
index 711f0948aee0f..39aed619ec50d 100644
--- a/code/modules/unit_tests/cyborg_tool.dm
+++ b/code/modules/unit_tests/cyborg_tool.dm
@@ -5,7 +5,7 @@
/datum/unit_test/cyborg_tool/Run()
var/mob/living/carbon/human/consistent/not_a_borg = allocate(__IMPLIED_TYPE__)
var/obj/item/borg/cyborg_omnitool/engineering/tool = allocate(__IMPLIED_TYPE__)
- tool.selected = allocate(/obj/item/wrench/cyborg)
+ tool.tool_behaviour = TOOL_WRENCH
not_a_borg.put_in_active_hand(tool)
diff --git a/code/modules/unit_tests/focus_only_tests.dm b/code/modules/unit_tests/focus_only_tests.dm
index 15d04ee885233..31f34d9f2fb94 100644
--- a/code/modules/unit_tests/focus_only_tests.dm
+++ b/code/modules/unit_tests/focus_only_tests.dm
@@ -47,3 +47,6 @@
/// Checks that maploaded mobs with either the `atmos_requirements` or `body_temp_sensitive`
/datum/unit_test/focus_only/atmos_and_temp_requirements
+
+/// Ensures only whitelisted planes can have TOPDOWN_LAYERing, and vis versa
+/datum/unit_test/focus_only/topdown_filtering
diff --git a/code/modules/unit_tests/lootpanel.dm b/code/modules/unit_tests/lootpanel.dm
index 1903c22d54652..41374a1950c02 100644
--- a/code/modules/unit_tests/lootpanel.dm
+++ b/code/modules/unit_tests/lootpanel.dm
@@ -2,8 +2,8 @@
abstract_type = /datum/unit_test/lootpanel
/datum/unit_test/lootpanel/contents/Run()
- var/datum/client_interface/mock_client = new()
- var/datum/lootpanel/panel = new(mock_client)
+ var/datum/client_interface/mock_client = allocate(/datum/client_interface)
+ var/datum/lootpanel/panel = allocate(/datum/lootpanel, mock_client)
var/mob/living/carbon/human/labrat = allocate(/mob/living/carbon/human/consistent)
mock_client.mob = labrat
var/turf/one_over = locate(run_loc_floor_bottom_left.x + 1, run_loc_floor_bottom_left.y, run_loc_floor_bottom_left.z)
@@ -32,4 +32,3 @@
TEST_ASSERT_EQUAL(length(panel.contents), 2, "Panel shouldnt dupe searchables if reopened")
mock_client.mob = null
-
diff --git a/code/modules/unit_tests/mapload_space_verification.dm b/code/modules/unit_tests/mapload_space_verification.dm
index 35556c85fb482..843de4f8fdde2 100644
--- a/code/modules/unit_tests/mapload_space_verification.dm
+++ b/code/modules/unit_tests/mapload_space_verification.dm
@@ -32,6 +32,8 @@
var/area/turf_area = get_area(iterated_turf)
if(!isspaceturf(iterated_turf) || is_type_in_typecache(turf_area, excluded_area_typecache))
continue // Alright, so let's assume we have intended behavior. If something yorks, we'll get a bare `/area` (maploader?) or a mapper is doing something they shouldn't be doing.
+ if(HAS_TRAIT(iterated_turf, TRAIT_HYPERSPACE_STOPPED))
+ continue // This means that a shuttle with a noop template turf is just temporarily parked ontop of us and that we're not actually a part of it. We don't have to care about it as it will leave us alone when it flies away.
// We need turf_area.type for the error message because we have fifteen million ruin areas named "Unexplored Location" and it's completely unhelpful here.
TEST_FAIL("Space turf [iterated_turf.type] found in non-allowed area ([turf_area.type]) at [AREACOORD(iterated_turf)]! Please ensure that all space turfs are in an /area/space!")
diff --git a/code/modules/unit_tests/mapping_nearstation_test.dm b/code/modules/unit_tests/mapping_nearstation_test.dm
new file mode 100644
index 0000000000000..d428b826634b5
--- /dev/null
+++ b/code/modules/unit_tests/mapping_nearstation_test.dm
@@ -0,0 +1,40 @@
+///Detects movables that may have been accidentally placed in space, as well as movables which do not have the proper nearspace area (meaning they aren't lit properly.)
+/datum/unit_test/mapping_nearstation_test
+ priority = TEST_PRE
+
+/datum/unit_test/mapping_nearstation_test/Run()
+ if(SSmapping.is_planetary())
+ return //No need to test for orphaned spaced atoms on this map.
+
+ var/list/safe_atoms = typecacheof(list(
+ /atom/movable/mirage_holder,
+ /obj/docking_port,
+ /obj/effect/landmark,
+ /obj/effect/abstract,
+ /obj/effect/mapping_error,
+ )) //Mapping stuff that we don't actually have to be concerned about.
+ var/list/safe_areas = typecacheof(list(
+ /area/misc/testroom,
+ /area/station/holodeck,
+ ))
+
+ for(var/station_z in SSmapping.levels_by_trait(ZTRAIT_STATION))
+ var/list/turfs_to_check = Z_TURFS(station_z)
+ for(var/turf/station_turf as anything in turfs_to_check)
+ var/area/turf_area = station_turf.loc
+ if(turf_area.static_lighting || is_type_in_typecache(turf_area, safe_areas)) //Only care about turfs that don't have lighting enabled.
+ continue
+ var/has_thing = FALSE
+ for(var/atom/movable/thing_on_the_turf as anything in station_turf.contents) //Find an item on the turf, this can help the mapper identify the turf more easily when combined with the exact coords.
+ if(is_type_in_typecache(thing_on_the_turf, safe_atoms))
+ continue
+ TEST_FAIL("[station_turf.x], [station_turf.y], [station_turf.z]: [thing_on_the_turf.type] with area of type [turf_area.type]")
+ has_thing = TRUE
+ break
+ if(!has_thing && !isspaceturf(station_turf) && !istype(station_turf, /turf/open/openspace)) //In case it's just a turf without an area
+ if(istype(station_turf, /turf/open/floor/engine/hull/ceiling))
+ TEST_FAIL("[station_turf.x], [station_turf.y], [station_turf.z]: [station_turf.type] with area of type [turf_area.type]. The turf on the z-level below is a shuttle dock and generated me! An error landmark has been generated on the map for easier debugging!")
+ else
+ TEST_FAIL("[station_turf.x], [station_turf.y], [station_turf.z]: [station_turf.type] with area of type [turf_area.type]")
+ if(!succeeded)
+ TEST_FAIL("Movable Atom located without a proper area. Please verify they are supposed to be there. If they are correct, change the area to /area/space/nearstation (or the correct surrounding type).")
diff --git a/code/modules/unit_tests/plane_sanity.dm b/code/modules/unit_tests/plane_sanity.dm
new file mode 100644
index 0000000000000..890a0531908a4
--- /dev/null
+++ b/code/modules/unit_tests/plane_sanity.dm
@@ -0,0 +1,10 @@
+/// Ensures we have no invalid plane/layer combos post init
+/datum/unit_test/plane_layer_sanity
+ priority = TEST_LONGER
+
+/datum/unit_test/plane_layer_sanity/Run()
+ // This fucker's gonna be slow, I'm sorry
+ for(var/mutable_appearance/appearance)
+ check_topdown_validity(appearance)
+ for(var/atom/thing)
+ check_topdown_validity(thing)
diff --git a/code/modules/unit_tests/screenshots/screenshot_antag_icons_syndicateinfiltrator.png b/code/modules/unit_tests/screenshots/screenshot_antag_icons_syndicateinfiltrator.png
index 0a9a5ef08e4ee..4c9212509d2ea 100644
Binary files a/code/modules/unit_tests/screenshots/screenshot_antag_icons_syndicateinfiltrator.png and b/code/modules/unit_tests/screenshots/screenshot_antag_icons_syndicateinfiltrator.png differ
diff --git a/code/modules/unit_tests/screenshots/screenshot_antag_icons_syndicatesleeperagent.png b/code/modules/unit_tests/screenshots/screenshot_antag_icons_syndicatesleeperagent.png
index 0a9a5ef08e4ee..4c9212509d2ea 100644
Binary files a/code/modules/unit_tests/screenshots/screenshot_antag_icons_syndicatesleeperagent.png and b/code/modules/unit_tests/screenshots/screenshot_antag_icons_syndicatesleeperagent.png differ
diff --git a/code/modules/unit_tests/screenshots/screenshot_antag_icons_traitor.png b/code/modules/unit_tests/screenshots/screenshot_antag_icons_traitor.png
index 0a9a5ef08e4ee..4c9212509d2ea 100644
Binary files a/code/modules/unit_tests/screenshots/screenshot_antag_icons_traitor.png and b/code/modules/unit_tests/screenshots/screenshot_antag_icons_traitor.png differ
diff --git a/code/modules/unit_tests/screenshots/screenshot_dynamic_human_icons_syndicate_commando.png b/code/modules/unit_tests/screenshots/screenshot_dynamic_human_icons_syndicate_commando.png
index c29a1e742f1f4..adc462ace50d1 100644
Binary files a/code/modules/unit_tests/screenshots/screenshot_dynamic_human_icons_syndicate_commando.png and b/code/modules/unit_tests/screenshots/screenshot_dynamic_human_icons_syndicate_commando.png differ
diff --git a/code/modules/unit_tests/unit_test.dm b/code/modules/unit_tests/unit_test.dm
index afce8392d5828..ed2510b829434 100644
--- a/code/modules/unit_tests/unit_test.dm
+++ b/code/modules/unit_tests/unit_test.dm
@@ -286,6 +286,10 @@ GLOBAL_VAR_INIT(focused_tests, focused_tests())
returnable_list += typesof(/obj/effect/anomaly/grav/high)
//See above
returnable_list += typesof(/obj/effect/timestop)
+ //Sparks can ignite a number of things, causing a fire to burn the floor away. Only you can prevent CI fires
+ returnable_list += typesof(/obj/effect/particle_effect/sparks)
+ //See above - These are one of those things.
+ returnable_list += typesof(/obj/effect/decal/cleanable/fuel_pool)
//Invoke async in init, skippppp
returnable_list += typesof(/mob/living/silicon/robot/model)
//This lad also sleeps
diff --git a/code/modules/uplink/uplink_devices.dm b/code/modules/uplink/uplink_devices.dm
index 4d539be433de2..596ea9e1ffad4 100644
--- a/code/modules/uplink/uplink_devices.dm
+++ b/code/modules/uplink/uplink_devices.dm
@@ -48,11 +48,11 @@
hidden_uplink.uplink_handler.debug_mode = TRUE
/obj/item/uplink/nuclear
- uplink_flag = UPLINK_NUKE_OPS
+ uplink_flag = UPLINK_ALL_SYNDIE_OPS
/obj/item/uplink/nuclear/debug
name = "debug nuclear uplink"
- uplink_flag = UPLINK_NUKE_OPS
+ uplink_flag = UPLINK_ALL_SYNDIE_OPS
/obj/item/uplink/nuclear/debug/Initialize(mapload, owner, tc_amount = 9000, datum/uplink_handler/uplink_handler_override = null)
. = ..()
@@ -68,6 +68,10 @@
var/datum/component/uplink/hidden_uplink = GetComponent(/datum/component/uplink)
hidden_uplink.allow_restricted = FALSE
+///A subtype used for lone ops, with some of the stuff they shouldn't/can't access removed from purchase.
+/obj/item/uplink/loneop
+ uplink_flag = UPLINK_LONE_OP
+
/obj/item/uplink/clownop
uplink_flag = UPLINK_CLOWN_OPS
diff --git a/code/modules/uplink/uplink_items/ammunition.dm b/code/modules/uplink/uplink_items/ammunition.dm
index 5326880d31be6..705204f98a15b 100644
--- a/code/modules/uplink/uplink_items/ammunition.dm
+++ b/code/modules/uplink/uplink_items/ammunition.dm
@@ -13,14 +13,14 @@
cost = 2
surplus = 0
illegal_tech = FALSE
- purchasable_from = ~UPLINK_NUKE_OPS
+ purchasable_from = ~UPLINK_SERIOUS_OPS
/datum/uplink_item/ammo/pistol
name = "9mm Handgun Magazine"
desc = "An additional 8-round 9mm magazine, compatible with the Makarov pistol."
item = /obj/item/ammo_box/magazine/m9mm
cost = 1
- purchasable_from = ~(UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS)
+ purchasable_from = ~UPLINK_ALL_SYNDIE_OPS
illegal_tech = FALSE
/datum/uplink_item/ammo/pistolap
@@ -29,7 +29,7 @@
These rounds are less effective at injuring the target but penetrate protective gear."
item = /obj/item/ammo_box/magazine/m9mm/ap
cost = 2
- purchasable_from = ~(UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS)
+ purchasable_from = ~UPLINK_ALL_SYNDIE_OPS
/datum/uplink_item/ammo/pistolhp
name = "9mm Hollow Point Magazine"
@@ -37,7 +37,7 @@
These rounds are more damaging but ineffective against armour."
item = /obj/item/ammo_box/magazine/m9mm/hp
cost = 3
- purchasable_from = ~(UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS)
+ purchasable_from = ~UPLINK_ALL_SYNDIE_OPS
/datum/uplink_item/ammo/pistolfire
name = "9mm Incendiary Magazine"
@@ -45,7 +45,7 @@
Loaded with incendiary rounds which inflict little damage, but ignite the target."
item = /obj/item/ammo_box/magazine/m9mm/fire
cost = 2
- purchasable_from = ~(UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS)
+ purchasable_from = ~UPLINK_ALL_SYNDIE_OPS
/datum/uplink_item/ammo/revolver
name = ".357 Speed Loader"
@@ -53,5 +53,5 @@
For when you really need a lot of things dead."
item = /obj/item/ammo_box/a357
cost = 4
- purchasable_from = ~(UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS | UPLINK_SPY) //nukies get their own version
+ purchasable_from = ~(UPLINK_ALL_SYNDIE_OPS | UPLINK_SPY) //nukies get their own version
illegal_tech = FALSE
diff --git a/code/modules/uplink/uplink_items/bundle.dm b/code/modules/uplink/uplink_items/bundle.dm
index b708af62b69c9..f92435f379532 100644
--- a/code/modules/uplink/uplink_items/bundle.dm
+++ b/code/modules/uplink/uplink_items/bundle.dm
@@ -62,7 +62,7 @@
item = /obj/item/storage/box/syndicate/bundle_a
cost = 20
stock_key = UPLINK_SHARED_STOCK_KITS
- purchasable_from = ~(UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS | UPLINK_SPY)
+ purchasable_from = ~(UPLINK_ALL_SYNDIE_OPS | UPLINK_SPY)
/datum/uplink_item/bundles_tc/bundle_b
name = "Syndi-kit Special"
@@ -73,7 +73,7 @@
item = /obj/item/storage/box/syndicate/bundle_b
cost = 20
stock_key = UPLINK_SHARED_STOCK_KITS
- purchasable_from = ~(UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS | UPLINK_SPY)
+ purchasable_from = ~(UPLINK_ALL_SYNDIE_OPS | UPLINK_SPY)
/datum/uplink_item/bundles_tc/surplus
name = "Syndicate Surplus Crate"
@@ -82,7 +82,7 @@
Contents are sorted to always be worth 30 TC. The Syndicate will only provide one surplus item per agent."
item = /obj/structure/closet/crate // will be replaced in purchase()
cost = 20
- purchasable_from = ~(UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS | UPLINK_SPY)
+ purchasable_from = ~(UPLINK_ALL_SYNDIE_OPS | UPLINK_SPY)
stock_key = UPLINK_SHARED_STOCK_SURPLUS
/// Value of items inside the crate in TC
var/crate_tc_value = 30
@@ -171,5 +171,5 @@
The Syndicate will only provide one surplus item per agent."
cost = 20
item = /obj/item/syndicrate_key
- purchasable_from = ~(UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS | UPLINK_SPY)
+ purchasable_from = ~(UPLINK_ALL_SYNDIE_OPS | UPLINK_SPY)
stock_key = UPLINK_SHARED_STOCK_SURPLUS
diff --git a/code/modules/uplink/uplink_items/dangerous.dm b/code/modules/uplink/uplink_items/dangerous.dm
index 970741876bb7a..11cca6c442661 100644
--- a/code/modules/uplink/uplink_items/dangerous.dm
+++ b/code/modules/uplink/uplink_items/dangerous.dm
@@ -13,7 +13,7 @@
item = /obj/item/gun/ballistic/automatic/pistol/toy/riot
cost = 2
surplus = 10
- purchasable_from = ~UPLINK_NUKE_OPS
+ purchasable_from = ~UPLINK_SERIOUS_OPS
/datum/uplink_item/dangerous/pistol
name = "Makarov Pistol"
@@ -21,7 +21,7 @@
with suppressors."
item = /obj/item/gun/ballistic/automatic/pistol
cost = 7
- purchasable_from = ~(UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS)
+ purchasable_from = ~UPLINK_ALL_SYNDIE_OPS
/datum/uplink_item/dangerous/throwingweapons
name = "Box of Throwing Weapons"
@@ -49,7 +49,7 @@
progression_minimum = 20 MINUTES
item = /obj/item/melee/powerfist
cost = 6
- purchasable_from = ~(UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS)
+ purchasable_from = ~UPLINK_ALL_SYNDIE_OPS
/datum/uplink_item/dangerous/rapid
name = "Gloves of the North Star"
@@ -66,7 +66,7 @@
item = /obj/item/dualsaber
cost = 13
- purchasable_from = ~(UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS) //nukies get their own version
+ purchasable_from = ~UPLINK_ALL_SYNDIE_OPS //nukies get their own version
/datum/uplink_item/dangerous/doublesword/get_discount_value(discount_type)
switch(discount_type)
@@ -85,7 +85,7 @@
item = /obj/item/guardian_creator/tech
cost = 18
surplus = 0
- purchasable_from = ~(UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS)
+ purchasable_from = ~UPLINK_ALL_SYNDIE_OPS
restricted = TRUE
refundable = TRUE
@@ -95,7 +95,7 @@
item = /obj/item/gun/ballistic/revolver/syndicate
cost = 13
surplus = 50
- purchasable_from = ~(UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS) //nukies get their own version
+ purchasable_from = ~UPLINK_ALL_SYNDIE_OPS //nukies get their own version
/datum/uplink_item/dangerous/cat
name = "Feral cat grenade"
diff --git a/code/modules/uplink/uplink_items/device_tools.dm b/code/modules/uplink/uplink_items/device_tools.dm
index 7f87d93464e48..c1b0f8b2e0215 100644
--- a/code/modules/uplink/uplink_items/device_tools.dm
+++ b/code/modules/uplink/uplink_items/device_tools.dm
@@ -89,7 +89,7 @@
item = /obj/item/computer_disk/syndicate/camera_app
cost = 1
surplus = 90
- purchasable_from = ~(UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS)
+ purchasable_from = ~UPLINK_ALL_SYNDIE_OPS
/datum/uplink_item/device_tools/military_belt
name = "Chest Rig"
@@ -122,7 +122,7 @@
item = /obj/item/computer_disk/virus/frame
cost = 4
restricted = TRUE
- purchasable_from = ~(UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS)
+ purchasable_from = ~UPLINK_ALL_SYNDIE_OPS
/datum/uplink_item/device_tools/frame/spawn_item(spawn_path, mob/user, datum/uplink_handler/uplink_handler, atom/movable/source)
. = ..()
@@ -138,7 +138,7 @@
cost = 1
surplus = 0
restricted = TRUE
- purchasable_from = ~(UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS)
+ purchasable_from = ~UPLINK_ALL_SYNDIE_OPS
/datum/uplink_item/device_tools/failsafe/spawn_item(spawn_path, mob/user, datum/uplink_handler/uplink_handler, atom/movable/source)
var/datum/component/uplink/uplink = source.GetComponent(/datum/component/uplink)
@@ -173,7 +173,7 @@
and wavelength, which controls the delay before the effect kicks in."
item = /obj/item/healthanalyzer/rad_laser
cost = 3
- purchasable_from = ~(UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS)
+ purchasable_from = ~UPLINK_ALL_SYNDIE_OPS
/datum/uplink_item/device_tools/suspiciousphone
name = "Protocol CRAB-17 Phone"
@@ -246,7 +246,7 @@
item = /obj/item/sbeacondrop
cost = 10
surplus = 0 // not while there isnt one on any station
- purchasable_from = ~(UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS)
+ purchasable_from = ~UPLINK_ALL_SYNDIE_OPS
/datum/uplink_item/device_tools/powersink
name = "Power Sink"
diff --git a/code/modules/uplink/uplink_items/job.dm b/code/modules/uplink/uplink_items/job.dm
index 98378a9422a03..b010755d67e9e 100644
--- a/code/modules/uplink/uplink_items/job.dm
+++ b/code/modules/uplink/uplink_items/job.dm
@@ -4,7 +4,7 @@
/datum/uplink_item/role_restricted
category = /datum/uplink_category/role_restricted
- purchasable_from = ~(UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS)
+ purchasable_from = ~UPLINK_ALL_SYNDIE_OPS
/datum/uplink_item/role_restricted/haunted_magic_eightball
name = "Haunted Magic Eightball"
@@ -390,3 +390,4 @@
restricted = TRUE
refundable = FALSE
purchasable_from = parent_type::purchasable_from & ~UPLINK_SPY
+
diff --git a/code/modules/uplink/uplink_items/nukeops.dm b/code/modules/uplink/uplink_items/nukeops.dm
index eff0fb933eaca..4f50994bc40ec 100644
--- a/code/modules/uplink/uplink_items/nukeops.dm
+++ b/code/modules/uplink/uplink_items/nukeops.dm
@@ -7,7 +7,7 @@
/datum/uplink_item/weapon_kits
category = /datum/uplink_category/weapon_kits
surplus = 40
- purchasable_from = UPLINK_NUKE_OPS
+ purchasable_from = UPLINK_SERIOUS_OPS
// ~~ Ammunition Categories ~~
@@ -18,7 +18,7 @@
/datum/uplink_item/ammo_nuclear
category = /datum/uplink_category/ammo_nuclear
surplus = 40
- purchasable_from = UPLINK_NUKE_OPS
+ purchasable_from = UPLINK_SERIOUS_OPS
// Basic: Run of the mill ammunition for various firearms
/datum/uplink_item/ammo_nuclear/basic
@@ -55,14 +55,14 @@
cost = 22 //freedom 5, doormag 3, c-4 1, stimpack 5, shield modsuit module 8
limited_stock = 1
cant_discount = TRUE
- purchasable_from = UPLINK_NUKE_OPS
+ purchasable_from = UPLINK_SERIOUS_OPS
//Low-cost firearms: Around 8 TC each. Meant for easy squad weapon purchases
/datum/uplink_item/weapon_kits/low_cost
cost = 8
surplus = 40
- purchasable_from = UPLINK_NUKE_OPS
+ purchasable_from = UPLINK_SERIOUS_OPS
// ~~ Bulldog Shotgun ~~
@@ -139,7 +139,7 @@
/datum/uplink_item/weapon_kits/medium_cost
cost = 14
surplus = 20
- purchasable_from = UPLINK_NUKE_OPS
+ purchasable_from = UPLINK_SERIOUS_OPS
// ~~ C-20r Submachine Gun ~~
@@ -172,7 +172,7 @@
Loaded with incendiary rounds which inflict little damage, but ignite the target."
item = /obj/item/ammo_box/magazine/smgm45/incen
cost = 4
- purchasable_from = UPLINK_NUKE_OPS
+ purchasable_from = UPLINK_SERIOUS_OPS
// ~~ Energy Sword and Shield & CQC ~~
@@ -186,7 +186,7 @@
name = "CQC Equipment Case (Very Hard)"
desc = "Contains a manual that instructs you in the ways of CQC, or Close Quarters Combat. Comes with a stealth implant, a pack of smokes and a snazzy bandana (use it with the hat stabilizers in your MODsuit)."
item = /obj/item/storage/toolbox/guncase/cqc
- purchasable_from = UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS
+ purchasable_from = UPLINK_ALL_SYNDIE_OPS
surplus = 0
// ~~ Syndicate Revolver ~~
@@ -247,7 +247,7 @@
/datum/uplink_item/weapon_kits/high_cost
cost = 18
surplus = 10
- purchasable_from = UPLINK_NUKE_OPS
+ purchasable_from = UPLINK_SERIOUS_OPS
// ~~ L6 SAW Machine Gun ~~
@@ -377,14 +377,14 @@
desc = "A horribly outdated automatic weapon. Why would you want to use this? Comes with...rations."
item = /obj/item/gun/ballistic/automatic/plastikov
cost = 2
- purchasable_from = UPLINK_NUKE_OPS
+ purchasable_from = UPLINK_SERIOUS_OPS
/datum/uplink_item/ammo_nuclear/surplus_smg
name = "Surplus SMG Magazine (Surplus)"
desc = "A cylindrical magazine designed for the PP-95 SMG."
item = /obj/item/ammo_box/magazine/plastikov9mm
cost = 1
- purchasable_from = UPLINK_NUKE_OPS
+ purchasable_from = UPLINK_SERIOUS_OPS
illegal_tech = FALSE
// Explosives and Grenades
@@ -393,8 +393,7 @@
/datum/uplink_item/explosives/grenades
cost = 15
surplus = 35
- purchasable_from = UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS
-
+ purchasable_from = UPLINK_ALL_SYNDIE_OPS
/datum/uplink_item/explosives/grenades/buzzkill
name = "Buzzkill Grenade Box"
desc = "A box with three grenades that release a swarm of angry bees upon activation. These bees indiscriminately attack friend or foe \
@@ -421,7 +420,7 @@
name = "Grenadier's Belt and Grenade Launcher Kit (Hard)"
desc = "A belt containing 26 lethally dangerous and destructive grenades, along with a grenade launcher to fire them. Comes with an extra multitool and screwdriver."
item = /obj/item/storage/box/syndie_kit/demoman
- purchasable_from = UPLINK_NUKE_OPS
+ purchasable_from = UPLINK_SERIOUS_OPS
// ~~ Detonator: In case you lose the old one ~~
@@ -433,7 +432,7 @@
the blast radius before using the detonator."
item = /obj/item/syndicatedetonator
cost = 1
- purchasable_from = UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS
+ purchasable_from = UPLINK_ALL_SYNDIE_OPS
// Support (Borgs and Reinforcements)
@@ -445,7 +444,7 @@
category = /datum/uplink_category/reinforcements
surplus = 0
cost = 35
- purchasable_from = UPLINK_NUKE_OPS
+ purchasable_from = UPLINK_SERIOUS_OPS
restricted = TRUE
refundable = TRUE
@@ -482,6 +481,7 @@
If you're a meathead who's just here to kill people and don't care about strategising or intel, you'll still have someone to bear witness to your murder-spree!"
item = /obj/item/antag_spawner/nuke_ops/overwatch
cost = 12
+ purchasable_from = UPLINK_FIREBASE_OPS
// ~~ Disposable Sentry Gun ~~
// Technically not a spawn but it is a kind of reinforcement...I guess.
@@ -501,7 +501,7 @@
desc = "A box containing x-ray eyes, a CNS Rebooter and Reviver implant. Comes with an autosurgeon for each."
item = /obj/item/storage/box/cyber_implants
cost = 20 //worth 24 TC
- purchasable_from = UPLINK_NUKE_OPS
+ purchasable_from = UPLINK_SERIOUS_OPS
/datum/uplink_item/bundles_tc/medical
name = "Medical bundle"
@@ -509,7 +509,7 @@
a Donksoft LMG, a box of riot darts and a magboot MODsuit module to rescue your friends in no-gravity environments."
item = /obj/item/storage/backpack/duffelbag/syndie/med/medicalbundle
cost = 25 // normally 31
- purchasable_from = UPLINK_NUKE_OPS
+ purchasable_from = UPLINK_SERIOUS_OPS
/datum/uplink_item/bundles_tc/firestarter
name = "Spetsnaz Pyro bundle"
@@ -518,7 +518,7 @@
Order NOW and comrade Boris will throw in an extra tracksuit."
item = /obj/item/storage/backpack/duffelbag/syndie/firestarter
cost = 30
- purchasable_from = UPLINK_NUKE_OPS
+ purchasable_from = UPLINK_SERIOUS_OPS
/datum/uplink_item/bundles_tc/induction_kit
name = "Syndicate Induction Kit"
@@ -538,7 +538,7 @@
A lighter is also included, though you must supply your own smokes."
item = /obj/item/storage/box/syndie_kit/cowboy
cost = 18
- purchasable_from = UPLINK_NUKE_OPS
+ purchasable_from = UPLINK_SERIOUS_OPS
// Mech related gear
@@ -549,7 +549,7 @@
/datum/uplink_item/mech
category = /datum/uplink_category/mech
surplus = 0
- purchasable_from = UPLINK_NUKE_OPS
+ purchasable_from = UPLINK_SERIOUS_OPS
restricted = TRUE
// ~~ Mechs ~~
@@ -575,21 +575,21 @@
desc = "A duffel bag containing ammo for four full reloads of the scattershotm which is equipped on standard Dark Gygax and Mauler exosuits. Also comes with some support equipment for maintaining the mech, including tools and an inducer."
item = /obj/item/storage/backpack/duffelbag/syndie/ammo/mech
cost = 4
- purchasable_from = UPLINK_NUKE_OPS
+ purchasable_from = UPLINK_SERIOUS_OPS
/datum/uplink_item/mech/support_bag/mauler
name = "Mauler Ammo Bag"
desc = "A duffel bag containing ammo for three full reloads of the LMG, scattershot carbine, and SRM-8 missile laucher that are equipped on a standard Mauler exosuit."
item = /obj/item/storage/backpack/duffelbag/syndie/ammo/mauler
cost = 6
- purchasable_from = UPLINK_NUKE_OPS
+ purchasable_from = UPLINK_SERIOUS_OPS
// Stealthy Tools
/datum/uplink_item/stealthy_tools/syndigaloshes/nuke
item = /obj/item/clothing/shoes/chameleon/noslip
cost = 4
- purchasable_from = UPLINK_NUKE_OPS
+ purchasable_from = UPLINK_SERIOUS_OPS
/datum/uplink_item/stealthy_weapons/romerol_kit
name = "Romerol"
@@ -598,7 +598,7 @@
along with slurred speech, aggression, and the ability to infect others with this agent."
item = /obj/item/storage/box/syndie_kit/romerol
cost = 25
- purchasable_from = UPLINK_CLOWN_OPS|UPLINK_NUKE_OPS
+ purchasable_from = UPLINK_ALL_SYNDIE_OPS
cant_discount = TRUE
// Modsuits
@@ -608,7 +608,7 @@
desc = "An upgraded, elite version of the Syndicate MODsuit. It features fireproofing, and also \
provides the user with superior armor and mobility compared to the standard Syndicate MODsuit."
item = /obj/item/mod/control/pre_equipped/elite
- purchasable_from = UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS | UPLINK_SPY
+ purchasable_from = (UPLINK_ALL_SYNDIE_OPS | UPLINK_SPY)
/datum/uplink_item/suits/energy_shield
name = "MODsuit Energy Shield Module"
@@ -616,28 +616,28 @@
before needing to recharge. Used wisely, this module will keep you alive for a lot longer."
item = /obj/item/mod/module/energy_shield
cost = 8
- purchasable_from = UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS | UPLINK_SPY
+ purchasable_from = (UPLINK_ALL_SYNDIE_OPS | UPLINK_SPY)
/datum/uplink_item/suits/emp_shield
name = "MODsuit Advanced EMP Shield Module"
desc = "An advanced EMP shield module for a MODsuit. It protects your entire body from electromagnetic pulses."
item = /obj/item/mod/module/emp_shield/advanced
cost = 5
- purchasable_from = UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS | UPLINK_SPY
+ purchasable_from = (UPLINK_ALL_SYNDIE_OPS | UPLINK_SPY)
/datum/uplink_item/suits/injector
name = "MODsuit Injector Module"
desc = "An injector module for a MODsuit. It is an extendable piercing injector with 30u capacity."
item = /obj/item/mod/module/injector
cost = 2
- purchasable_from = UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS | UPLINK_SPY
+ purchasable_from = (UPLINK_ALL_SYNDIE_OPS | UPLINK_SPY)
/datum/uplink_item/suits/holster
name = "MODsuit Holster Module"
desc = "A holster module for a MODsuit. It can stealthily store any not too heavy gun inside it."
item = /obj/item/mod/module/holster
cost = 2
- purchasable_from = UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS | UPLINK_SPY
+ purchasable_from = (UPLINK_ALL_SYNDIE_OPS | UPLINK_SPY)
/datum/uplink_item/device_tools/medgun_mod
name = "Medbeam Gun Module"
@@ -645,7 +645,7 @@
operatives in the fight, even while under fire. Don't cross the streams!"
item = /obj/item/mod/module/medbeam
cost = 15
- purchasable_from = UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS
+ purchasable_from = UPLINK_ALL_SYNDIE_OPS
/datum/uplink_item/suits/syndi_intellicard
name = "Pre-Loaded Syndicate Intellicard"
@@ -653,7 +653,7 @@
However, due to failsafes activated during the extraction process, the AI is unable to interact with electronics from anywhere but direct proximity..."
item = /obj/item/aicard/syndie/loaded
cost = 12
- purchasable_from = UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS
+ purchasable_from = UPLINK_ALL_SYNDIE_OPS
refundable = TRUE
/datum/uplink_item/suits/synd_ai_upgrade
@@ -661,7 +661,7 @@
desc = "...unless you buy the Syndicate Upgrade! This data chip allows the captured AI to increase its interaction range by two tiles per application. The Syndicate recommends three or four purchases at most, for a total of seven or infinite meters of range."
item = /obj/item/computer_disk/syndie_ai_upgrade
cost = 4
- purchasable_from = UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS
+ purchasable_from = UPLINK_ALL_SYNDIE_OPS
cant_discount = TRUE
refundable = TRUE
@@ -675,6 +675,7 @@
surplus = 0
purchasable_from = UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS
restricted = TRUE
+ purchasable_from = UPLINK_FIREBASE_OPS
/datum/uplink_item/device_tools/syndie_jaws_of_life
name = "Syndicate Jaws of Life"
@@ -682,7 +683,7 @@
In its crowbar configuration, it can be used to force open airlocks. Very useful for entering the station or its departments."
item = /obj/item/crowbar/power/syndicate
cost = 4
- purchasable_from = UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS | UPLINK_SPY
+ purchasable_from = UPLINK_SERIOUS_OPS | UPLINK_SPY
/datum/uplink_item/device_tools/medkit
name = "Syndicate Combat Medic Kit"
@@ -691,7 +692,7 @@
for faster healing on the field. Also comes with basic medical tools and sterlizer."
item = /obj/item/storage/medkit/tactical
cost = 4
- purchasable_from = UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS
+ purchasable_from = UPLINK_SERIOUS_OPS
/datum/uplink_item/device_tools/medkit/premium
name = "Syndicate Combat Medical Suite"
@@ -701,7 +702,7 @@
and some helpful MODsuit modules for for field medical use and operative physiopharmaceutical augmentation."
item = /obj/item/storage/medkit/tactical/premium
cost = 15
- purchasable_from = UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS
+ purchasable_from = UPLINK_SERIOUS_OPS
/datum/uplink_item/device_tools/potion
name = "Syndicate Sentience Potion"
@@ -709,7 +710,7 @@
desc = "A potion recovered at great risk by undercover Syndicate operatives and then subsequently modified with Syndicate technology. \
Using it will make any animal sentient, and bound to serve you, as well as implanting an internal radio for communication and an internal ID card for opening doors."
cost = 4
- purchasable_from = UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS | UPLINK_SPY
+ purchasable_from = UPLINK_SERIOUS_OPS | UPLINK_SPY
restricted = TRUE
// Implants
@@ -734,7 +735,7 @@
This will permanently destroy your body, however."
item = /obj/item/storage/box/syndie_kit/imp_microbomb
cost = 2
- purchasable_from = UPLINK_NUKE_OPS | UPLINK_SPY
+ purchasable_from = UPLINK_SERIOUS_OPS | UPLINK_SPY
/datum/uplink_item/implants/nuclear/macrobomb
name = "Macrobomb Implant"
@@ -750,7 +751,7 @@
Prevents collapsing from critical condition, but explodes after a while."
item = /obj/item/storage/box/syndie_kit/imp_deniability
cost = 6
- purchasable_from = UPLINK_NUKE_OPS | UPLINK_SPY
+ purchasable_from = UPLINK_SERIOUS_OPS | UPLINK_SPY
/datum/uplink_item/implants/nuclear/reviver
name = "Reviver Implant"
@@ -780,7 +781,7 @@
/datum/uplink_item/badass/costumes
surplus = 0
- purchasable_from = UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS
+ purchasable_from = UPLINK_SERIOUS_OPS
cost = 4
cant_discount = TRUE
@@ -803,7 +804,6 @@
you can blow those corpo suits away with your very own home-made explosive devices. Made in your local firebase's \
very own Ordnance Laboratory! *The Syndicate is not responsible for injuries or deaths sustained while utilizing the lab."
item = /obj/item/keycard/syndicate_bomb
- purchasable_from = UPLINK_NUKE_OPS
/datum/uplink_item/base_keys/bio_key
name = "Syndicate Bio-Weapon Laboratory Access Card"
@@ -840,4 +840,4 @@
desc = "Hat crate! Contains hats! HATS!!!"
item = /obj/structure/closet/crate/large/hats
cost = 5
- purchasable_from = UPLINK_CLOWN_OPS | UPLINK_NUKE_OPS
+ purchasable_from = UPLINK_ALL_SYNDIE_OPS
diff --git a/code/modules/uplink/uplink_items/species.dm b/code/modules/uplink/uplink_items/species.dm
index 5eb4bbdcb1776..5b76b745f9b52 100644
--- a/code/modules/uplink/uplink_items/species.dm
+++ b/code/modules/uplink/uplink_items/species.dm
@@ -4,7 +4,7 @@
/datum/uplink_item/species_restricted
category = /datum/uplink_category/species
- purchasable_from = ~(UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS | UPLINK_SPY)
+ purchasable_from = ~(UPLINK_ALL_SYNDIE_OPS | UPLINK_SPY)
/datum/uplink_item/species_restricted/moth_lantern
name = "Extra-Bright Lantern"
diff --git a/code/modules/uplink/uplink_items/stealthy.dm b/code/modules/uplink/uplink_items/stealthy.dm
index fb450fb68df93..898b82d1b6ad2 100644
--- a/code/modules/uplink/uplink_items/stealthy.dm
+++ b/code/modules/uplink/uplink_items/stealthy.dm
@@ -13,7 +13,7 @@
item = /obj/item/gun/syringe/syndicate
cost = 4
surplus = 50
- purchasable_from = ~(UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS)
+ purchasable_from = ~UPLINK_ALL_SYNDIE_OPS
/datum/uplink_item/stealthy_weapons/dehy_carp
name = "Dehydrated Space Carp"
@@ -67,7 +67,7 @@
item = /obj/item/storage/box/syndie_kit/origami_bundle
cost = 4
surplus = 0
- purchasable_from = ~UPLINK_NUKE_OPS //clown ops intentionally left in, because that seems like some s-tier shenanigans.
+ purchasable_from = ~UPLINK_SERIOUS_OPS //clown ops intentionally left in, because that seems like some s-tier shenanigans.
/datum/uplink_item/stealthy_weapons/martialarts
@@ -78,7 +78,7 @@
progression_minimum = 30 MINUTES
cost = 17
surplus = 0
- purchasable_from = ~(UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS)
+ purchasable_from = ~UPLINK_ALL_SYNDIE_OPS
/datum/uplink_item/stealthy_weapons/crossbow
name = "Miniature Energy Crossbow"
@@ -91,7 +91,7 @@
item = /obj/item/gun/energy/recharge/ebow
cost = 10
surplus = 50
- purchasable_from = ~(UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS)
+ purchasable_from = ~UPLINK_ALL_SYNDIE_OPS
/datum/uplink_item/stealthy_weapons/contrabaton
name = "Contractor Baton"
diff --git a/code/modules/uplink/uplink_items/stealthy_tools.dm b/code/modules/uplink/uplink_items/stealthy_tools.dm
index 59b8f6fca77e6..a225d04d6674e 100644
--- a/code/modules/uplink/uplink_items/stealthy_tools.dm
+++ b/code/modules/uplink/uplink_items/stealthy_tools.dm
@@ -30,7 +30,7 @@
Due to budget cuts, the shoes don't provide protection against slipping and skillchips are sold separately."
item = /obj/item/storage/box/syndie_kit/chameleon
cost = 2
- purchasable_from = ~UPLINK_NUKE_OPS //clown ops are allowed to buy this kit, since it's basically a costume
+ purchasable_from = ~UPLINK_NUKE_OPS //clown ops are allowed to buy this kit, since it's basically a costume, loneops can purchase it to blend in.
/datum/uplink_item/stealthy_tools/syndigaloshes
name = "No-Slip Chameleon Shoes"
@@ -38,7 +38,7 @@
They do not work on heavily lubricated surfaces."
item = /obj/item/clothing/shoes/chameleon/noslip
cost = 2
- purchasable_from = ~(UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS)
+ purchasable_from = ~(UPLINK_ALL_SYNDIE_OPS)
/datum/uplink_item/stealthy_tools/chameleon_proj
name = "Chameleon Projector"
@@ -75,7 +75,7 @@
item = /obj/item/reagent_containers/syringe/mulligan
cost = 4
surplus = 30
- purchasable_from = ~(UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS)
+ purchasable_from = ~(UPLINK_ALL_SYNDIE_OPS)
/datum/uplink_item/stealthy_tools/jammer
name = "Radio Jammer"
@@ -108,7 +108,7 @@
limited_stock = 1
cost = 4
restricted = TRUE
- purchasable_from = ~(UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS)
+ purchasable_from = ~(UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS) //Can still be purchased by loneops to give them an edge.
/datum/uplink_item/stealthy_tools/telecomm_blackout/spawn_item(spawn_path, mob/user, datum/uplink_handler/uplink_handler, atom/movable/source)
force_event(/datum/round_event_control/communications_blackout, "a syndicate virus")
@@ -123,7 +123,7 @@
limited_stock = 1
cost = 6
restricted = TRUE
- purchasable_from = ~(UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS)
+ purchasable_from = ~(UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS) //Can still be purchased by loneops to give them an edge.
/datum/uplink_item/stealthy_tools/blackout/spawn_item(spawn_path, mob/user, datum/uplink_handler/uplink_handler, atom/movable/source)
force_event(/datum/round_event_control/grid_check, "a syndicate virus")
diff --git a/code/modules/uplink/uplink_items/suits.dm b/code/modules/uplink/uplink_items/suits.dm
index 5d89f80506178..d940d4eb06769 100644
--- a/code/modules/uplink/uplink_items/suits.dm
+++ b/code/modules/uplink/uplink_items/suits.dm
@@ -17,7 +17,7 @@
as well as causing significant demoralization amongst Nanotrasen crew."
item = /obj/item/mod/control/pre_equipped/infiltrator
cost = 6
- purchasable_from = ~(UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS)
+ purchasable_from = ~UPLINK_ALL_SYNDIE_OPS
/datum/uplink_item/suits/space_suit
name = "Syndicate Space Suit"
@@ -32,7 +32,7 @@
desc = "The feared MODsuit of a Syndicate agent. Features armoring and a set of inbuilt modules."
item = /obj/item/mod/control/pre_equipped/traitor
cost = 8
- purchasable_from = ~(UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS) //you can't buy it in nuke, because the elite modsuit costs the same while being better
+ purchasable_from = ~UPLINK_ALL_SYNDIE_OPS //you can't buy it in nuke, because the elite modsuit costs the same while being better
/datum/uplink_item/suits/thermal
name = "MODsuit Thermal Visor Module"
@@ -76,6 +76,6 @@
provides the user with superior armor and mobility compared to the standard Syndicate MODsuit."
item = /obj/item/mod/control/pre_equipped/traitor_elite
// This one costs more than the nuke op counterpart
- purchasable_from = ~(UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS | UPLINK_SPY)
+ purchasable_from = ~UPLINK_ALL_SYNDIE_OPS
progression_minimum = 90 MINUTES
cost = 16
diff --git a/code/modules/vehicles/_vehicle.dm b/code/modules/vehicles/_vehicle.dm
index faf95e8cd73c7..7da8fc0314b10 100644
--- a/code/modules/vehicles/_vehicle.dm
+++ b/code/modules/vehicles/_vehicle.dm
@@ -30,7 +30,7 @@
var/canmove = TRUE
var/list/autogrant_actions_passenger //plain list of typepaths
var/list/autogrant_actions_controller //assoc list "[bitflag]" = list(typepaths)
- var/list/mob/occupant_actions //assoc list mob = list(type = action datum assigned to mob)
+ var/list/list/datum/action/occupant_actions //assoc list mob = list(type = action datum assigned to mob)
///This vehicle will follow us when we move (like atrailer duh)
var/obj/vehicle/trailer
var/are_legs_exposed = FALSE
diff --git a/code/modules/vehicles/mecha/_mecha.dm b/code/modules/vehicles/mecha/_mecha.dm
index f36beee83cd77..d35df3126636e 100644
--- a/code/modules/vehicles/mecha/_mecha.dm
+++ b/code/modules/vehicles/mecha/_mecha.dm
@@ -16,6 +16,7 @@
* Clicks are wither translated into mech_melee_attack (see mech_melee_attack.dm)
* Or are used to call action() on equipped gear
* Cooldown for gear is on the mech because exploits
+ * Cooldown for melee is on mech_melee_attack also because exploits
*/
/obj/vehicle/sealed/mecha
name = "exosuit"
@@ -28,7 +29,6 @@
movedelay = 1 SECONDS
move_force = MOVE_FORCE_VERY_STRONG
move_resist = MOVE_FORCE_EXTREMELY_STRONG
- COOLDOWN_DECLARE(mecha_bump_smash)
light_system = OVERLAY_LIGHT_DIRECTIONAL
light_on = FALSE
light_range = 6
@@ -137,9 +137,18 @@
var/stepsound = 'sound/mecha/mechstep.ogg'
///Sound played when the mech walks
var/turnsound = 'sound/mecha/mechturn.ogg'
+ ///Sounds for types of melee attack
+ var/brute_attack_sound = 'sound/weapons/punch4.ogg'
+ var/burn_attack_sound = 'sound/items/welder.ogg'
+ var/tox_attack_sound = 'sound/effects/spray2.ogg'
+ ///Sound on wall destroying
+ var/destroy_wall_sound = 'sound/effects/meteorimpact.ogg'
+
+ ///Melee attack verb
+ var/list/attack_verbs = list("hit", "hits", "hitting")
///Cooldown duration between melee punches
- var/melee_cooldown = 10
+ var/melee_cooldown = CLICK_CD_SLOW
///TIme taken to leave the mech
var/exit_delay = 2 SECONDS
@@ -151,6 +160,8 @@
var/is_currently_ejecting = FALSE
///Safety for weapons. Won't fire if enabled, and toggled by middle click.
var/weapons_safety = FALSE
+ ///Don't play standard sound when set safety if TRUE.
+ var/safety_sound_custom = FALSE
var/datum/effect_system/fluid_spread/smoke/smoke_system
@@ -194,9 +205,6 @@
///Wether we are strafing
var/strafe = FALSE
- ///Cooldown length between bumpsmashes
- var/smashcooldown = 3
-
///Bool for whether this mech can only be used on lavaland
var/lavaland_only = FALSE
@@ -368,7 +376,8 @@
*/
/obj/vehicle/sealed/mecha/proc/set_safety(mob/user)
weapons_safety = !weapons_safety
- SEND_SOUND(user, sound('sound/machines/beep.ogg', volume = 25))
+ if(!safety_sound_custom)
+ SEND_SOUND(user, sound('sound/machines/beep.ogg', volume = 25))
balloon_alert(user, "equipment [weapons_safety ? "safe" : "ready"]")
set_mouse_pointer()
SEND_SIGNAL(src, COMSIG_MECH_SAFETIES_TOGGLE, user, weapons_safety)
@@ -703,10 +712,9 @@
return
use_energy(melee_energy_drain)
- SEND_SIGNAL(user, COMSIG_MOB_USED_MECH_MELEE, src)
- target.mech_melee_attack(src, user)
- TIMER_COOLDOWN_START(src, COOLDOWN_MECHA_MELEE_ATTACK, melee_cooldown)
-
+ SEND_SIGNAL(user, COMSIG_MOB_USED_CLICK_MECH_MELEE, src)
+ if(target.mech_melee_attack(src, user))
+ TIMER_COOLDOWN_START(src, COOLDOWN_MECHA_MELEE_ATTACK, melee_cooldown)
/// Driver alt clicks anything while in mech
/obj/vehicle/sealed/mecha/proc/on_click_alt(mob/user, atom/target, params)
@@ -909,3 +917,9 @@
act.button_icon_state = "mech_lights_off"
balloon_alert(occupant, "lights [mecha_flags & LIGHTS_ON ? "on":"off"]")
act.build_all_button_icons()
+
+/obj/vehicle/sealed/mecha/proc/melee_attack_effect(mob/living/victim, heavy)
+ if(heavy)
+ victim.Unconscious(2 SECONDS)
+ else
+ victim.Knockdown(4 SECONDS)
diff --git a/code/modules/vehicles/mecha/combat/justice.dm b/code/modules/vehicles/mecha/combat/justice.dm
new file mode 100644
index 0000000000000..babdb8af17b99
--- /dev/null
+++ b/code/modules/vehicles/mecha/combat/justice.dm
@@ -0,0 +1,535 @@
+#define DISMEMBER_CHANCE_HIGH 50
+#define DISMEMBER_CHANCE_LOW 25
+
+#define MOVEDELAY_ANGRY 4.5
+#define MOVEDELAY_SAFETY 2.5
+
+/obj/vehicle/sealed/mecha/justice
+ name = "\improper Justice"
+ desc = "Black and red syndicate mech designed for execution orders. \
+ For safety reasons, the syndicate advises against standing too close."
+ icon_state = "justice"
+ base_icon_state = "justice"
+ movedelay = MOVEDELAY_SAFETY // fast
+ max_integrity = 200 // but weak
+ accesses = list(ACCESS_SYNDICATE)
+ armor_type = /datum/armor/mecha_justice
+ max_temperature = 40000
+ force = 60 // dangerous in melee
+ damtype = BRUTE
+ destruction_sleep_duration = 10
+ exit_delay = 10
+ wreckage = /obj/structure/mecha_wreckage/justice
+ mech_type = EXOSUIT_MODULE_JUSTICE
+ resistance_flags = LAVA_PROOF | FIRE_PROOF | ACID_PROOF
+ mecha_flags = ID_LOCK_ON | QUIET_STEPS | QUIET_TURNS | CAN_STRAFE | HAS_LIGHTS | MMI_COMPATIBLE | IS_ENCLOSED
+ destroy_wall_sound = 'sound/mecha/mech_blade_break_wall.ogg'
+ brute_attack_sound = 'sound/mecha/mech_blade_attack.ogg'
+ attack_verbs = list("cut", "cuts", "cutting")
+ weapons_safety = TRUE
+ safety_sound_custom = TRUE
+ max_equip_by_category = list(
+ MECHA_L_ARM = null,
+ MECHA_R_ARM = null,
+ MECHA_UTILITY = 3,
+ MECHA_POWER = 1,
+ MECHA_ARMOR = 2,
+ )
+ step_energy_drain = 2
+
+/datum/armor/mecha_justice
+ melee = 30
+ bullet = 20
+ laser = 20
+ energy = 30
+ fire = 100
+ acid = 100
+
+/obj/vehicle/sealed/mecha/justice/Initialize(mapload, built_manually)
+ . = ..()
+ RegisterSignal(src, COMSIG_MECHA_MELEE_CLICK, PROC_REF(justice_fatality)) //We do not hit those who are in crit or stun. We are finishing them.
+ transform = transform.Scale(1.04, 1.04)
+
+/obj/vehicle/sealed/mecha/justice/generate_actions()
+ . = ..()
+ initialize_passenger_action_type(/datum/action/vehicle/sealed/mecha/invisibility)
+ initialize_passenger_action_type(/datum/action/vehicle/sealed/mecha/charge_attack)
+
+/obj/vehicle/sealed/mecha/justice/update_icon_state()
+ . = ..()
+ if(LAZYLEN(occupants))
+ icon_state = weapons_safety ? "[base_icon_state]" : "[base_icon_state]-angry"
+ if(!has_gravity())
+ icon_state = "[icon_state]-fly"
+
+/obj/vehicle/sealed/mecha/justice/set_safety(mob/user)
+ . = ..()
+ if(weapons_safety)
+ movedelay = MOVEDELAY_SAFETY
+ else
+ movedelay = MOVEDELAY_ANGRY
+
+ playsound(src, 'sound/mecha/mech_blade_safty.ogg', 75, FALSE) //everyone need to hear this sound
+
+ update_appearance(UPDATE_ICON_STATE)
+
+/obj/vehicle/sealed/mecha/justice/Move(newloc, dir)
+ if(HAS_TRAIT(src, TRAIT_IMMOBILIZED))
+ return
+ . = ..()
+ update_appearance(UPDATE_ICON_STATE)
+
+/// Says 1 of 3 epic phrases before attacking and make a finishing blow to targets in stun or crit after 1 SECOND.
+/obj/vehicle/sealed/mecha/justice/proc/justice_fatality(datum/source, mob/living/pilot, atom/target, on_cooldown, is_adjacent)
+ SIGNAL_HANDLER
+
+ if(!ishuman(target))
+ return FALSE
+ var/mob/living/carbon/human/live_or_dead = target
+ if(live_or_dead.stat < UNCONSCIOUS && live_or_dead.getStaminaLoss() < 100)
+ return FALSE
+ var/obj/item/bodypart/check_head = live_or_dead.get_bodypart(BODY_ZONE_HEAD)
+ if(!check_head)
+ return FALSE
+ INVOKE_ASYNC(src, PROC_REF(finish_him), src, pilot, live_or_dead)
+ return TRUE
+
+/**
+ * ## finish_him
+ *
+ * Target's head is cut off (if it has one)
+ * Attack from invisibility and charged attack have higher priority.
+ * Arguments:
+ * * finisher - Mech pilot who makes an attack.
+ * * him - Target at which the mech makes an attack.
+ */
+/obj/vehicle/sealed/mecha/justice/proc/finish_him(obj/vehicle/sealed/mecha/my_mech, mob/finisher, mob/living/him)
+ say(pick("Take my Justice-Slash!", "A falling leaf...", "Justice is quite a lonely path"), forced = "Justice Mech")
+ playsound(src, 'sound/mecha/mech_stealth_pre_attack.ogg', 75, FALSE)
+ if(!do_after(finisher, 1 SECONDS, him))
+ return
+ if(QDELETED(finisher))
+ return
+ if(QDELETED(him))
+ return
+ if(QDELETED(my_mech))
+ return
+ if(!LAZYLEN(my_mech.occupants))
+ return
+ var/turf/finish_turf = get_step(him, get_dir(my_mech, him))
+ var/turf/for_line_turf = get_turf(my_mech)
+ var/obj/item/bodypart/in_your_head = him.get_bodypart(BODY_ZONE_HEAD)
+ in_your_head?.dismember(BRUTE)
+ playsound(src, brute_attack_sound, 75, FALSE)
+ for_line_turf.Beam(src, icon_state = "mech_charge", time = 8)
+ forceMove(finish_turf)
+
+/obj/vehicle/sealed/mecha/justice/melee_attack_effect(mob/living/victim, heavy)
+ if(!heavy)
+ victim.Knockdown(4 SECONDS)
+ return
+ if(!prob(DISMEMBER_CHANCE_HIGH))
+ return
+ var/obj/item/bodypart/cut_bodypart = victim.get_bodypart(pick(BODY_ZONE_R_ARM, BODY_ZONE_R_LEG, BODY_ZONE_L_ARM, BODY_ZONE_L_LEG))
+ cut_bodypart?.dismember(BRUTE)
+
+
+/obj/vehicle/sealed/mecha/justice/mob_exit(mob/M, silent, randomstep, forced)
+ . = ..()
+ if(alpha == 255)
+ return
+ animate(src, alpha = 255, time = 0.5 SECONDS)
+ playsound(src, 'sound/mecha/mech_stealth_effect.ogg' , 75, FALSE)
+
+/obj/vehicle/sealed/mecha/justice/take_damage(damage_amount, damage_type, damage_flag, sound_effect, attack_dir, armour_penetration)
+ if(LAZYLEN(occupants))
+ if(prob(60))
+ new /obj/effect/temp_visual/mech_sparks(get_turf(src))
+ playsound(src, 'sound/mecha/mech_stealth_effect.ogg' , 75, FALSE)
+ return
+ return ..()
+
+/datum/action/vehicle/sealed/mecha/invisibility
+ name = "Invisibility"
+ button_icon_state = "mech_stealth_off"
+ /// Is invisibility activated.
+ var/on = FALSE
+ /// Recharge check.
+ var/charge = TRUE
+ /// Varset for invisibility timer
+ var/invisibility_timer
+ /// Energy cost to become invisibile
+ var/energy_cost = 200
+ /// Aoe pre attack sound.
+ var/stealth_pre_attack_sound = 'sound/mecha/mech_stealth_pre_attack.ogg'
+ /// Aoe attack sound.
+ var/stealth_attack_sound = 'sound/mecha/mech_stealth_attack.ogg'
+
+/datum/action/vehicle/sealed/mecha/invisibility/set_chassis(passed_chassis)
+ . = ..()
+ RegisterSignal(chassis, COMSIG_MECH_SAFETIES_TOGGLE, PROC_REF(on_toggle_safety))
+
+/// update button icon when toggle safety.
+/datum/action/vehicle/sealed/mecha/invisibility/proc/on_toggle_safety()
+ SIGNAL_HANDLER
+
+ build_all_button_icons(UPDATE_BUTTON_STATUS)
+
+/datum/action/vehicle/sealed/mecha/invisibility/Trigger(trigger_flags)
+ . = ..()
+ if(!.)
+ return
+ on = !on
+ if(on)
+ invisibility_on()
+ else
+ invisibility_off()
+
+/datum/action/vehicle/sealed/mecha/invisibility/IsAvailable(feedback)
+ . = ..()
+ if(!.)
+ return FALSE
+ if(!chassis.has_charge(energy_cost))
+ if(feedback)
+ owner.balloon_alert(owner, "not enough energy!")
+ return FALSE
+ if(chassis.weapons_safety)
+ if(feedback)
+ owner.balloon_alert(owner, "safety is on!")
+ return FALSE
+ if(!charge)
+ if(feedback)
+ owner.balloon_alert(owner, "recharging!")
+ return FALSE
+
+ return TRUE
+
+///Called when invisibility activated.
+/datum/action/vehicle/sealed/mecha/invisibility/proc/invisibility_on()
+ new /obj/effect/temp_visual/mech_sparks(get_turf(chassis))
+ playsound(chassis, 'sound/mecha/mech_stealth_effect.ogg' , 75, FALSE)
+ check_charge_attack()
+ animate(chassis, alpha = 0, time = 0.5 SECONDS)
+ button_icon_state = "mech_stealth_on"
+ invisibility_timer = addtimer(CALLBACK(src, PROC_REF(end_stealth)), 20 SECONDS)
+ RegisterSignal(chassis, COMSIG_MECHA_MELEE_CLICK, PROC_REF(stealth_attack_aoe))
+ RegisterSignal(chassis, COMSIG_MOVABLE_BUMP, PROC_REF(bumb_on))
+ RegisterSignal(chassis, COMSIG_ATOM_BUMPED, PROC_REF(bumbed_on))
+ RegisterSignal(chassis, COMSIG_ATOM_TAKE_DAMAGE, PROC_REF(take_damage))
+ chassis.use_energy(energy_cost)
+ build_all_button_icons()
+
+///Called when invisibility deactivated.
+/datum/action/vehicle/sealed/mecha/invisibility/proc/invisibility_off()
+ new /obj/effect/temp_visual/mech_sparks(get_turf(chassis))
+ playsound(chassis, 'sound/mecha/mech_stealth_effect.ogg' , 75, FALSE)
+ invisibility_timer = null
+ charge = FALSE
+ addtimer(CALLBACK(src, PROC_REF(charge)), 5 SECONDS)
+ button_icon_state = "mech_stealth_cooldown"
+ animate(chassis, alpha = 255, time = 0.5 SECONDS)
+ UnregisterSignal(chassis, list(
+ COMSIG_MECHA_MELEE_CLICK,
+ COMSIG_MOVABLE_BUMP,
+ COMSIG_ATOM_BUMPED,
+ COMSIG_ATOM_TAKE_DAMAGE
+ ))
+ build_all_button_icons()
+
+///Check if mech use charge attack and deactivate it when we activate invisibility.
+/datum/action/vehicle/sealed/mecha/invisibility/proc/check_charge_attack()
+ for(var/mob/living/occupant in chassis.occupants)
+ var/datum/action/vehicle/sealed/mecha/charge_attack/charge_action = LAZYACCESSASSOC(chassis.occupant_actions, occupant, /datum/action/vehicle/sealed/mecha/charge_attack)
+ if(charge_action?.on)
+ charge_action.on = !on
+ charge_action.charge_attack_off()
+/**
+ * ## end_stealth
+ *
+ * Called when mech runs out of invisibility time.
+ */
+/datum/action/vehicle/sealed/mecha/invisibility/proc/end_stealth()
+ make_visible()
+
+/**
+ * ## bumb_on
+ *
+ * Called when mech bumb on somthing. If is living somthing shutdown mech invisibility.
+ */
+/datum/action/vehicle/sealed/mecha/invisibility/proc/bumb_on(obj/vehicle/sealed/mecha/our_mech, atom/obstacle)
+ SIGNAL_HANDLER
+
+ if(!isliving(obstacle))
+ return
+ make_visible()
+
+/**
+ * ## bumbed_on
+ *
+ * Called when somthing bumbed on mech. If is living somthing shutdown mech invisibility.
+ */
+/datum/action/vehicle/sealed/mecha/invisibility/proc/bumbed_on(obj/vehicle/sealed/mecha/our_mech, atom/movable/bumped_atom)
+ SIGNAL_HANDLER
+
+ if(!isliving(bumped_atom))
+ return
+ make_visible()
+
+/**
+ * ## take_damage
+ *
+ * Called when mech take damage. Shutdown mech invisibility.
+ */
+/datum/action/vehicle/sealed/mecha/invisibility/proc/take_damage(obj/vehicle/sealed/mecha/our_mech)
+ SIGNAL_HANDLER
+
+ make_visible()
+
+/**
+ * ## make_visible
+ *
+ * Called when somthing force invisibility shutdown.
+ */
+/datum/action/vehicle/sealed/mecha/invisibility/proc/make_visible()
+ if(!on)
+ return
+ on = !on
+ invisibility_off()
+
+/**
+ * Proc makes an AOE attack after 1 SECOND.
+ * Called by the mech pilot when he is in stealth mode and wants to attack.
+ * During this, mech cannot move.
+*/
+/datum/action/vehicle/sealed/mecha/invisibility/proc/stealth_attack_aoe(datum/source, mob/living/pilot, atom/target, on_cooldown, is_adjacent)
+ SIGNAL_HANDLER
+
+ if(!charge)
+ return FALSE
+ if(chassis.alpha != 0)
+ UnregisterSignal(chassis, COMSIG_MECHA_MELEE_CLICK)
+ return FALSE
+ UnregisterSignal(chassis, COMSIG_MECHA_MELEE_CLICK)
+ new /obj/effect/temp_visual/mech_attack_aoe_charge(get_turf(chassis))
+ ADD_TRAIT(chassis, TRAIT_IMMOBILIZED, REF(src))
+ playsound(chassis, stealth_pre_attack_sound, 75, FALSE)
+ addtimer(CALLBACK(src, PROC_REF(attack_in_aoe), pilot), 1 SECONDS)
+ return TRUE
+
+/**
+ * ## attack_in_aoe
+ *
+ * Brings mech out of invisibility.
+ * Deal everyone in range 3x3 35 damage and 25 chanse to cut off limb.
+ * Arguments:
+ * * pilot - occupant inside mech.
+ */
+/datum/action/vehicle/sealed/mecha/invisibility/proc/attack_in_aoe(mob/living/pilot)
+ invisibility_off()
+ new /obj/effect/temp_visual/mech_attack_aoe_attack(get_turf(chassis))
+ for(var/mob/living/something_living in range(1, get_turf(chassis)))
+ if(something_living.stat >= UNCONSCIOUS)
+ continue
+ if(something_living.getStaminaLoss() >= 100)
+ continue
+ if(something_living == pilot)
+ continue
+ if(prob(DISMEMBER_CHANCE_LOW))
+ var/obj/item/bodypart/cut_bodypart = something_living.get_bodypart(pick(BODY_ZONE_R_ARM, BODY_ZONE_R_LEG, BODY_ZONE_L_ARM, BODY_ZONE_L_LEG))
+ cut_bodypart?.dismember(BRUTE)
+ something_living.apply_damage(35, BRUTE)
+ playsound(chassis, stealth_attack_sound, 75, FALSE)
+ REMOVE_TRAIT(chassis, TRAIT_IMMOBILIZED, REF(src))
+ on = !on
+ charge = FALSE
+ button_icon_state = "mech_stealth_cooldown"
+ build_all_button_icons()
+ addtimer(CALLBACK(src, PROC_REF(charge)), 5 SECONDS)
+
+/**
+ * ## charge
+ *
+ * Recharge invisibility action after 5 SECONDS.
+ */
+/datum/action/vehicle/sealed/mecha/invisibility/proc/charge()
+ button_icon_state = "mech_stealth_off"
+ charge = TRUE
+ build_all_button_icons()
+
+/datum/action/vehicle/sealed/mecha/charge_attack
+ name = "Charge Attack"
+ button_icon_state = "mech_charge_off"
+ /// Is charge attack activated.
+ var/on = FALSE
+ /// Recharge check.
+ var/charge = TRUE
+ /// Energy cost to perform charge attack
+ var/energy_cost = 400
+ /// Maximum range of charge attack.
+ var/max_charge_range = 7
+ /// Sound when mech do charge attack.
+ var/charge_attack_sound = 'sound/mecha/mech_charge_attack.ogg'
+
+/datum/action/vehicle/sealed/mecha/charge_attack/set_chassis(passed_chassis)
+ . = ..()
+ RegisterSignal(chassis, COMSIG_MECH_SAFETIES_TOGGLE, PROC_REF(on_toggle_safety))
+
+/// update button icon when toggle safety.
+/datum/action/vehicle/sealed/mecha/charge_attack/proc/on_toggle_safety()
+ SIGNAL_HANDLER
+
+ build_all_button_icons(UPDATE_BUTTON_STATUS)
+
+/datum/action/vehicle/sealed/mecha/charge_attack/Trigger(trigger_flags)
+ . = ..()
+ if(!.)
+ return
+ on = !on
+ if(on)
+ charge_attack_on()
+ else
+ charge_attack_off()
+
+/datum/action/vehicle/sealed/mecha/charge_attack/IsAvailable(feedback)
+ . = ..()
+ if(!.)
+ return FALSE
+ if(!chassis.has_charge(energy_cost))
+ if(feedback)
+ owner.balloon_alert(owner, "not enough energy!")
+ return FALSE
+ if(chassis.weapons_safety)
+ if(feedback)
+ owner.balloon_alert(owner, "safety is on!")
+ return FALSE
+ if(!charge)
+ if(feedback)
+ owner.balloon_alert(owner, "recharging!")
+ return FALSE
+
+ return TRUE
+
+///Called when charge attack activated
+/datum/action/vehicle/sealed/mecha/charge_attack/proc/charge_attack_on()
+ check_visability()
+ button_icon_state = "mech_charge_on"
+ RegisterSignal(chassis, COMSIG_MECHA_MELEE_CLICK, PROC_REF(click_try_charge))
+ build_all_button_icons()
+
+///Called when charge attack deactivated
+/datum/action/vehicle/sealed/mecha/charge_attack/proc/charge_attack_off()
+ button_icon_state = "mech_charge_off"
+ UnregisterSignal(chassis, COMSIG_MECHA_MELEE_CLICK)
+ build_all_button_icons()
+
+///Check if mech use invisibility and deactivate it when we activate charge attack.
+/datum/action/vehicle/sealed/mecha/charge_attack/proc/check_visability()
+ for(var/who_inside in chassis.occupants)
+ var/mob/living/occupant = who_inside
+ var/datum/action/vehicle/sealed/mecha/invisibility/stealth_action = LAZYACCESSASSOC(chassis.occupant_actions, occupant, /datum/action/vehicle/sealed/mecha/invisibility)
+ if(stealth_action?.on)
+ stealth_action.make_visible()
+
+///Called when mech attacks with charge attack enabled.
+/datum/action/vehicle/sealed/mecha/charge_attack/proc/click_try_charge(datum/source, mob/living/pilot, atom/target, on_cooldown, is_adjacent)
+ SIGNAL_HANDLER
+
+ var/turf = get_turf(target)
+ if(!on)
+ UnregisterSignal(chassis, COMSIG_MECHA_MELEE_CLICK)
+ return FALSE
+ if(isnull(turf))
+ pilot.balloon_alert(pilot, "invalid direction!")
+ return FALSE
+ if(!charge)
+ pilot.balloon_alert(pilot, "recharging!")
+ return FALSE
+ else
+ if(charge_attack(pilot, turf))
+ return TRUE
+ return FALSE
+
+/**
+ * ## charge_attack
+ *
+ * Deal everyone in line for mech location to mouse location 35 damage and 25 chanse to cut off limb.
+ * Teleport mech to the end of line.
+ * Arguments:
+ * * charger - occupant inside mech.
+ * * target - occupant inside mech.
+ */
+/datum/action/vehicle/sealed/mecha/charge_attack/proc/charge_attack(mob/living/charger, turf/target)
+ var/turf/start_charge_here = get_turf(charger)
+ var/charge_range = min(get_dist_euclidean(start_charge_here, target), max_charge_range)
+ var/turf/but_we_gonna_here = get_ranged_target_turf(start_charge_here, get_dir(start_charge_here, target), floor(charge_range))
+ var/turf/here_we_go = start_charge_here
+ for(var/turf/line_turf in get_line(get_step(start_charge_here, get_dir(start_charge_here, target)), but_we_gonna_here))
+ if(get_turf(charger) == get_turf(line_turf))
+ continue
+ if(isclosedturf(line_turf))
+ break
+ var/obj/machinery/power/supermatter_crystal/funny_crystal = locate() in line_turf
+ if(funny_crystal)
+ funny_crystal.Bumped(chassis)
+ break
+ var/obj/machinery/door/airlock/like_a_wall = locate() in line_turf
+ if(like_a_wall?.density)
+ break
+ if(locate(/obj/structure/window) in line_turf)
+ break
+ for(var/mob/living/something_living in line_turf.contents)
+ if(something_living.stat >= UNCONSCIOUS || something_living.getStaminaLoss() >= 100 || something_living == charger)
+ continue
+ if(prob(DISMEMBER_CHANCE_LOW))
+ var/obj/item/bodypart/cut_bodypart = something_living.get_bodypart(pick(BODY_ZONE_R_ARM, BODY_ZONE_R_LEG, BODY_ZONE_L_ARM, BODY_ZONE_L_LEG, BODY_ZONE_HEAD))
+ cut_bodypart?.dismember(BRUTE)
+ something_living.apply_damage(35, BRUTE)
+ here_we_go = line_turf
+
+ // If the mech didn't move, it didn't charge
+ if(here_we_go == start_charge_here)
+ charger.balloon_alert(charger, "invalid direction!")
+ return FALSE
+ chassis.forceMove(here_we_go)
+ start_charge_here.Beam(chassis, icon_state = "mech_charge", time = 8)
+ playsound(chassis, charge_attack_sound, 75, FALSE)
+ on = !on
+ chassis.use_energy(energy_cost)
+ UnregisterSignal(chassis, COMSIG_MECHA_MELEE_CLICK)
+ charge = FALSE
+ button_icon_state = "mech_charge_cooldown"
+ build_all_button_icons()
+ addtimer(CALLBACK(src, PROC_REF(charge)), 5 SECONDS)
+ return TRUE
+
+/**
+ * ## charge
+ *
+ * Recharge charge attack action after 5 SECONDS.
+ */
+/datum/action/vehicle/sealed/mecha/charge_attack/proc/charge()
+ charge = TRUE
+ button_icon_state = "mech_charge_off"
+ build_all_button_icons()
+
+/obj/vehicle/sealed/mecha/justice/loaded
+ equip_by_category = list(
+ MECHA_L_ARM = null,
+ MECHA_R_ARM = null,
+ MECHA_UTILITY = list(/obj/item/mecha_parts/mecha_equipment/radio, /obj/item/mecha_parts/mecha_equipment/air_tank/full, /obj/item/mecha_parts/mecha_equipment/thrusters/ion),
+ MECHA_POWER = list(),
+ MECHA_ARMOR = list(),
+ )
+
+/obj/vehicle/sealed/mecha/justice/loaded/populate_parts()
+ cell = new /obj/item/stock_parts/power_store/cell/bluespace(src)
+ scanmod = new /obj/item/stock_parts/scanning_module/triphasic(src)
+ capacitor = new /obj/item/stock_parts/capacitor/quadratic(src)
+ servo = new /obj/item/stock_parts/servo/femto(src)
+ update_part_values()
+
+#undef DISMEMBER_CHANCE_HIGH
+#undef DISMEMBER_CHANCE_LOW
+
+#undef MOVEDELAY_ANGRY
+#undef MOVEDELAY_SAFETY
diff --git a/code/modules/vehicles/mecha/equipment/weapons/weapons.dm b/code/modules/vehicles/mecha/equipment/weapons/weapons.dm
index d13b90e5659f7..0bb691160b373 100644
--- a/code/modules/vehicles/mecha/equipment/weapons/weapons.dm
+++ b/code/modules/vehicles/mecha/equipment/weapons/weapons.dm
@@ -4,14 +4,22 @@
equipment_slot = MECHA_WEAPON
destroy_sound = 'sound/mecha/weapdestr.ogg'
mech_flags = EXOSUIT_MODULE_COMBAT
+ /// The type of bullet generated by the mecha weapon.
var/projectile
+ /// The sound of the mecha weapon firing.
var/fire_sound
+ /// How many shots are fired per action.
var/projectiles_per_shot = 1
+ /// The degrees by which each individual bullet fans out from a central point. A predictable spray of bullets.
var/variance = 0
- var/randomspread = FALSE //use random spread for machineguns, instead of shotgun scatter
+ /// Whether our bullets go off trajectory while firing randomly. Used to replicate recoil and not a structured, predictable spray.
+ var/randomspread = FALSE
+ /// The amount in deciseconds that the weapon sleeps between shots to simulate a 'burst fire'. The delay stops another bullet from being fired while sleeping.
var/projectile_delay = 0
- var/firing_effect_type = /obj/effect/temp_visual/dir_setting/firing_effect //the visual effect appearing when the weapon is fired.
- var/kickback = TRUE //Will using this weapon in no grav push mecha back.
+ //the visual effect appearing when the weapon is fired.
+ var/firing_effect_type = /obj/effect/temp_visual/dir_setting/firing_effect
+ /// Will using this weapon in no grav push mecha back.
+ var/kickback = TRUE
/obj/item/mecha_parts/mecha_equipment/weapon/special_attaching_interaction(attach_right = FALSE, obj/vehicle/sealed/mecha/mech, mob/user, checkonly = FALSE)
var/obj/item/mecha_parts/mecha_equipment/concealed_weapon_bay/bay
@@ -43,23 +51,25 @@
/obj/item/mecha_parts/mecha_equipment/weapon/action(mob/source, atom/target, list/modifiers)
if(!action_checks(target))
return FALSE
+
+ /// Find our mecha, find the opposite direction. Used for kickback while the mecha is drifting in zero-g to launch us in this direction.
var/newtonian_target = REVERSE_DIR(chassis.dir)
. = ..()//start the cooldown early because of sleeps
- for(var/i in 1 to projectiles_per_shot)
+ for(var/projectiles_to_shoot in 1 to projectiles_per_shot)
if(energy_drain && !chassis.has_charge(energy_drain))//in case we run out of energy mid-burst, such as emp
break
var/spread = 0
if(variance)
if(randomspread)
- spread = round((rand() - 0.5) * variance)
+ spread = round((rand(0 , 1) - 0.5) * variance, 1)
else
- spread = round((i / projectiles_per_shot - 0.5) * variance)
+ spread = round((projectiles_to_shoot / projectiles_per_shot - 0.5) * variance, 1)
var/obj/projectile/projectile_obj = new projectile(get_turf(src))
projectile_obj.log_override = TRUE //we log being fired ourselves a little further down.
projectile_obj.firer = chassis
projectile_obj.preparePixelProjectile(target, source, modifiers, spread)
- if(source.client && isliving(source)) //dont want it to happen from syndie mecha npc mobs, they do direct fire anyways
+ if(isliving(source) && source.client) //dont want it to happen from syndie mecha npc mobs, they do direct fire anyways
var/mob/living/shooter = source
projectile_obj.hit_prone_targets = shooter.combat_mode
projectile_obj.fire()
diff --git a/code/modules/vehicles/mecha/mech_fabricator.dm b/code/modules/vehicles/mecha/mech_fabricator.dm
index 33edb9e47c777..c2d37ee6e8348 100644
--- a/code/modules/vehicles/mecha/mech_fabricator.dm
+++ b/code/modules/vehicles/mecha/mech_fabricator.dm
@@ -52,10 +52,14 @@
//looping sound for printing items
var/datum/looping_sound/lathe_print/print_sound
+ /// Local designs that only this mechfab have(using when mechfab emaged so it's illegal designs).
+ var/list/datum/design/illegal_local_designs
+
/obj/machinery/mecha_part_fabricator/Initialize(mapload)
print_sound = new(src, FALSE)
rmat = AddComponent(/datum/component/remote_materials, mapload && link_on_init)
cached_designs = list()
+ illegal_local_designs = list()
RefreshParts() //Recalculating local material sizes if the fab isn't linked
return ..()
@@ -142,6 +146,26 @@
balloon_alert(user, "rotated to [dir2text(dir)].")
return CLICK_ACTION_SUCCESS
+/obj/machinery/mecha_part_fabricator/emag_act(mob/user, obj/item/card/emag/emag_card)
+ if(obj_flags & EMAGGED)
+ return FALSE
+ if(user.job != JOB_ROBOTICIST)
+ to_chat(user, span_warning("You clicking and typing but don’t understand what to do with it"))
+ return FALSE
+ obj_flags |= EMAGGED
+ for(var/found_illegal_mech_nods in SSresearch.techweb_nodes)
+ var/datum/techweb_node/illegal_mech_node = SSresearch.techweb_nodes[found_illegal_mech_nods]
+ if(!illegal_mech_node?.illegal_mech_node)
+ continue
+ for(var/id in illegal_mech_node.design_ids)
+ var/datum/design/illegal_mech_design = SSresearch.techweb_design_by_id(id)
+ illegal_local_designs |= illegal_mech_design
+ cached_designs |= illegal_mech_design
+ say("R$c!i&ed ERROR de#i$ns. C@n%ec$%ng to ~NULL~ se%ve$s.")
+ playsound(src, 'sound/machines/uplinkerror.ogg', 50, TRUE)
+ update_static_data_for_all_viewers()
+ return TRUE
+
/**
* Updates the `final_sets` and `buildable_parts` for the current mecha fabricator.
*/
@@ -351,7 +375,7 @@
for(var/datum/design/design in cached_designs)
var/cost = list()
- var/list/materials = design["materials"]
+ var/list/materials = design.materials
for(var/datum/material/mat in materials)
cost[mat.name] = OPTIMAL_COST(materials[mat] * component_coeff)
@@ -418,7 +442,7 @@
if(!istext(design_id))
continue
- if(!stored_research.researched_designs.Find(design_id))
+ if(!(stored_research.researched_designs.Find(design_id) || is_type_in_list(SSresearch.techweb_design_by_id(design_id), illegal_local_designs)))
continue
var/datum/design/design = SSresearch.techweb_design_by_id(design_id)
diff --git a/code/modules/vehicles/mecha/mech_melee_attack.dm b/code/modules/vehicles/mecha/mech_melee_attack.dm
index 08669ebdd0ca8..655a2f3533936 100644
--- a/code/modules/vehicles/mecha/mech_melee_attack.dm
+++ b/code/modules/vehicles/mecha/mech_melee_attack.dm
@@ -2,7 +2,7 @@
* ## Mech melee attack
* Called when a mech melees a target with fists
* Handles damaging the target & associated effects
- * return value is number of damage dealt
+ * return value is number of damage dealt. returning a value puts our mech onto attack cooldown.
* Arguments:
* * mecha_attacker: Mech attacking this target
* * user: mob that initiated the attack from inside the mech as a controller
@@ -10,28 +10,35 @@
/atom/proc/mech_melee_attack(obj/vehicle/sealed/mecha/mecha_attacker, mob/living/user)
SHOULD_CALL_PARENT(TRUE)
SEND_SIGNAL(src, COMSIG_ATOM_ATTACK_MECH, mecha_attacker, user)
- log_combat(user, src, "attacked", mecha_attacker, "(COMBAT MODE: [uppertext(user.combat_mode)] (DAMTYPE: [uppertext(mecha_attacker.damtype)])")
+ if(!isnull(user))
+ log_combat(user, src, "attacked", mecha_attacker, "(COMBAT MODE: [uppertext(user?.combat_mode)] (DAMTYPE: [uppertext(mecha_attacker.damtype)])")
return 0
/turf/closed/wall/mech_melee_attack(obj/vehicle/sealed/mecha/mecha_attacker, mob/living/user)
+ if(!user.combat_mode)
+ return 0
+
mecha_attacker.do_attack_animation(src)
switch(mecha_attacker.damtype)
if(BRUTE)
- playsound(src, 'sound/weapons/punch4.ogg', 50, TRUE)
+ playsound(src, mecha_attacker.brute_attack_sound, 50, TRUE)
if(BURN)
- playsound(src, 'sound/items/welder.ogg', 50, TRUE)
+ playsound(src, mecha_attacker.burn_attack_sound, 50, TRUE)
else
return 0
mecha_attacker.visible_message(span_danger("[mecha_attacker] hits [src]!"), span_danger("You hit [src]!"), null, COMBAT_MESSAGE_RANGE)
if(prob(hardness + mecha_attacker.force) && mecha_attacker.force > 20)
dismantle_wall(1)
- playsound(src, 'sound/effects/meteorimpact.ogg', 100, TRUE)
+ playsound(src, mecha_attacker.destroy_wall_sound, 100, TRUE)
else
add_dent(WALL_DENT_HIT)
..()
return 100 //this is an arbitrary "damage" number since the actual damage is rng dismantle
-/obj/mech_melee_attack(obj/vehicle/sealed/mecha/mecha_attacker, mob/living/user)
+/obj/structure/mech_melee_attack(obj/vehicle/sealed/mecha/mecha_attacker, mob/living/user)
+ if(!user.combat_mode)
+ return 0
+
mecha_attacker.do_attack_animation(src)
switch(mecha_attacker.damtype)
if(BRUTE)
@@ -44,13 +51,29 @@
..()
return take_damage(mecha_attacker.force * 3, mecha_attacker.damtype, "melee", FALSE, get_dir(src, mecha_attacker)) // multiplied by 3 so we can hit objs hard but not be overpowered against mobs.
+/obj/machinery/mech_melee_attack(obj/vehicle/sealed/mecha/mecha_attacker, mob/living/user)
+ if(!user.combat_mode)
+ return 0
+
+ mecha_attacker.do_attack_animation(src)
+ switch(mecha_attacker.damtype)
+ if(BRUTE)
+ playsound(src, mecha_attacker.brute_attack_sound, 50, TRUE)
+ if(BURN)
+ playsound(src, mecha_attacker.burn_attack_sound, 50, TRUE)
+ else
+ return 0
+ mecha_attacker.visible_message(span_danger("[mecha_attacker] hits [src]!"), span_danger("You hit [src]!"), null, COMBAT_MESSAGE_RANGE)
+ ..()
+ return take_damage(mecha_attacker.force * 3, mecha_attacker.damtype, "melee", FALSE, get_dir(src, mecha_attacker)) // multiplied by 3 so we can hit objs hard but not be overpowered against mobs.
+
/obj/structure/window/mech_melee_attack(obj/vehicle/sealed/mecha/mecha_attacker, mob/living/user)
if(!can_be_reached())
return 0
return ..()
/mob/living/mech_melee_attack(obj/vehicle/sealed/mecha/mecha_attacker, mob/living/user)
- if(!user.combat_mode)
+ if(istype(user) && !user.combat_mode)
step_away(src, mecha_attacker)
log_combat(user, src, "pushed", mecha_attacker)
visible_message(span_warning("[mecha_attacker] pushes [src] out of the way."), \
@@ -58,36 +81,41 @@
to_chat(mecha_attacker, span_danger("You push [src] out of the way."))
return 0
- if(HAS_TRAIT(user, TRAIT_PACIFISM))
+ if(!isnull(user) && HAS_TRAIT(user, TRAIT_PACIFISM))
to_chat(user, span_warning("You don't want to harm other living beings!"))
return 0
mecha_attacker.do_attack_animation(src)
if(mecha_attacker.damtype == BRUTE)
step_away(src, mecha_attacker, 15)
var/obj/item/bodypart/selected_zone = get_bodypart(pick(BODY_ZONE_CHEST, BODY_ZONE_CHEST, BODY_ZONE_CHEST, BODY_ZONE_HEAD))
- if(selected_zone)
- var/dmg = rand(mecha_attacker.force * 0.5, mecha_attacker.force)
- switch(mecha_attacker.damtype)
- if(BRUTE)
- if(mecha_attacker.force > 35) // durand and other heavy mechas
- Unconscious(20)
- else if(mecha_attacker.force > 20 && !IsKnockdown()) // lightweight mechas like gygax
- Knockdown(40)
+ var/dmg = rand(mecha_attacker.force * 0.5, mecha_attacker.force)
+ switch(mecha_attacker.damtype)
+ if(BRUTE)
+ if(mecha_attacker.force > 35) // durand and other heavy mechas
+ mecha_attacker.melee_attack_effect(src, heavy = TRUE)
+ else if(mecha_attacker.force > 20 && !IsKnockdown()) // lightweight mechas like gygax
+ mecha_attacker.melee_attack_effect(src, heavy = FALSE)
+ if(selected_zone)
selected_zone.receive_damage(dmg, 0, updating_health = TRUE)
- playsound(src, 'sound/weapons/punch4.ogg', 50, TRUE)
- if(FIRE)
+ else
+ apply_damage(dmg, BRUTE)
+ playsound(src, mecha_attacker.brute_attack_sound, 50, TRUE)
+ if(FIRE)
+ if(selected_zone)
selected_zone.receive_damage(0, dmg, updating_health = TRUE)
- playsound(src, 'sound/items/welder.ogg', 50, TRUE)
- if(TOX)
- playsound(src, 'sound/effects/spray2.ogg', 50, TRUE)
- if((reagents.get_reagent_amount(/datum/reagent/cryptobiolin) + mecha_attacker.force) < mecha_attacker.force*2)
- reagents.add_reagent(/datum/reagent/cryptobiolin, mecha_attacker.force/2)
- if((reagents.get_reagent_amount(/datum/reagent/toxin) + mecha_attacker.force) < mecha_attacker.force*2)
- reagents.add_reagent(/datum/reagent/toxin, mecha_attacker.force/2.5)
else
- return 0
- . = dmg
- visible_message(span_danger("[mecha_attacker.name] hits [src]!"), \
- span_userdanger("[mecha_attacker.name] hits you!"), span_hear("You hear a sickening sound of flesh hitting flesh!"), COMBAT_MESSAGE_RANGE, list(mecha_attacker))
- to_chat(mecha_attacker, span_danger("You hit [src]!"))
+ apply_damage(dmg, BURN)
+ playsound(src, mecha_attacker.burn_attack_sound, 50, TRUE)
+ if(TOX)
+ playsound(src, mecha_attacker.tox_attack_sound, 50, TRUE)
+ if((reagents.get_reagent_amount(/datum/reagent/cryptobiolin) + mecha_attacker.force) < mecha_attacker.force*2)
+ reagents.add_reagent(/datum/reagent/cryptobiolin, mecha_attacker.force/2)
+ if((reagents.get_reagent_amount(/datum/reagent/toxin) + mecha_attacker.force) < mecha_attacker.force*2)
+ reagents.add_reagent(/datum/reagent/toxin, mecha_attacker.force/2.5)
+ else
+ return 0
+ . = dmg
+ visible_message(span_danger("[mecha_attacker.name] [mecha_attacker.attack_verbs[1]] [src]!"), \
+ span_userdanger("[mecha_attacker.name] [mecha_attacker.attack_verbs[2]] you!"), span_hear("You hear a sickening sound of flesh [mecha_attacker.attack_verbs[3]] flesh!"), COMBAT_MESSAGE_RANGE, list(mecha_attacker))
+ to_chat(mecha_attacker, span_danger("You [mecha_attacker.attack_verbs[1]] [src]!"))
..()
diff --git a/code/modules/vehicles/mecha/mecha_construction_paths.dm b/code/modules/vehicles/mecha/mecha_construction_paths.dm
index f4b1a679596b9..0a305a5db66ae 100644
--- a/code/modules/vehicles/mecha/mecha_construction_paths.dm
+++ b/code/modules/vehicles/mecha/mecha_construction_paths.dm
@@ -819,3 +819,39 @@
outer_plating = /obj/item/stack/sheet/plasteel
outer_plating_amount = 5
+
+//Justice
+/datum/component/construction/unordered/mecha_chassis/justice
+ result = /datum/component/construction/mecha/justice
+ steps = list(
+ /obj/item/mecha_parts/part/justice_torso,
+ /obj/item/mecha_parts/part/justice_left_arm,
+ /obj/item/mecha_parts/part/justice_right_arm,
+ /obj/item/mecha_parts/part/justice_left_leg,
+ /obj/item/mecha_parts/part/justice_right_leg
+ )
+
+/datum/component/construction/mecha/justice
+ result = /obj/vehicle/sealed/mecha/justice
+ base_icon = "justice"
+
+ inner_plating = /obj/item/stack/telecrystal
+ inner_plating_amount = 8
+
+ outer_plating = /obj/item/mecha_parts/part/justice_armor
+ outer_plating_amount = 1
+
+/datum/component/construction/mecha/justice/get_circuit_steps()
+ return list()
+
+/datum/component/construction/mecha/justice/get_inner_plating_steps()
+ return list(
+ list(
+ "key" = inner_plating,
+ "amount" = inner_plating_amount,
+ "back_key" = TOOL_SCREWDRIVER,
+ "desc" = "The power cell is secured, and [inner_plating_amount] telecrystals can be added.",
+ "forward_message" = "added telecrystal",
+ "backward_message" = "unsecured power cell"
+ )
+ )
diff --git a/code/modules/vehicles/mecha/mecha_movement.dm b/code/modules/vehicles/mecha/mecha_movement.dm
index 53e61690aba7f..3c743bd7fb357 100644
--- a/code/modules/vehicles/mecha/mecha_movement.dm
+++ b/code/modules/vehicles/mecha/mecha_movement.dm
@@ -154,13 +154,17 @@
return
if(.) //mech was thrown/door/whatever
return
- if(bumpsmash) //Need a pilot to push the PUNCH button.
- if(COOLDOWN_FINISHED(src, mecha_bump_smash))
- var/list/mob/mobster = return_drivers()
- obstacle.mech_melee_attack(src, mobster[1])
- COOLDOWN_START(src, mecha_bump_smash, smashcooldown)
- if(!obstacle || obstacle.CanPass(src, get_dir(obstacle, src) || dir)) // The else is in case the obstacle is in the same turf.
- step(src,dir)
+
+ // Whether or not we're on our mecha melee cooldown
+ var/on_cooldown = TIMER_COOLDOWN_RUNNING(src, COOLDOWN_MECHA_MELEE_ATTACK)
+
+ if(bumpsmash && !on_cooldown)
+ // Our pilot for this evening
+ var/list/mob/mobster = return_drivers()
+ if(obstacle.mech_melee_attack(src, mobster[1]))
+ TIMER_COOLDOWN_START(src, COOLDOWN_MECHA_MELEE_ATTACK, melee_cooldown * 0.3)
+ if(!obstacle || obstacle.CanPass(src, get_dir(obstacle, src) || dir)) // The else is in case the obstacle is in the same turf.
+ step(src,dir)
if(isobj(obstacle))
var/obj/obj_obstacle = obstacle
if(!obj_obstacle.anchored && obj_obstacle.move_resist <= move_force)
diff --git a/code/modules/vehicles/mecha/mecha_parts.dm b/code/modules/vehicles/mecha/mecha_parts.dm
index 7fcee2092590c..474716568eb5b 100644
--- a/code/modules/vehicles/mecha/mecha_parts.dm
+++ b/code/modules/vehicles/mecha/mecha_parts.dm
@@ -332,6 +332,42 @@
desc="Savannah-Ivanov armor plates. They are uniquely shaped and reinforced to deal with the stresses of two pilots, grandiose leaps, and missiles."
icon_state = "savannah_ivanov_armor"
+// Justice
+
+/obj/item/mecha_parts/chassis/justice
+ name = "\improper Justice chassis"
+ construct_type = /datum/component/construction/unordered/mecha_chassis/justice
+
+/obj/item/mecha_parts/part/justice_torso
+ name="\improper Justice torso"
+ desc="A Justice torso part."
+ icon_state = "justice_torso"
+
+/obj/item/mecha_parts/part/justice_left_arm
+ name="\improper Justice left arm"
+ desc="A Justice left arm."
+ icon_state = "justice_l_arm"
+
+/obj/item/mecha_parts/part/justice_right_arm
+ name="\improper Justice right arm"
+ desc="A Justice left arm."
+ icon_state = "justice_r_arm"
+
+/obj/item/mecha_parts/part/justice_left_leg
+ name="\improper Justice left leg"
+ desc="A Justice left leg."
+ icon_state = "justice_l_leg"
+
+/obj/item/mecha_parts/part/justice_right_leg
+ name="\improper Justice right leg"
+ desc="A Justice left leg."
+ icon_state = "justice_r_leg"
+
+/obj/item/mecha_parts/part/justice_armor
+ name="Justice armor"
+ desc="Justice armor plates."
+ icon_state = "justice_armor"
+
///////// Circuitboards
/obj/item/circuitboard/mecha
diff --git a/code/modules/vehicles/mecha/mecha_ui.dm b/code/modules/vehicles/mecha/mecha_ui.dm
index 712d5f0d712bf..1113a85381361 100644
--- a/code/modules/vehicles/mecha/mecha_ui.dm
+++ b/code/modules/vehicles/mecha/mecha_ui.dm
@@ -111,16 +111,17 @@
var/module_index = 0
for(var/category in max_equip_by_category)
var/max_per_category = max_equip_by_category[category]
- for(var/i = 1 to max_per_category)
- var/equipment = equip_by_category[category]
- var/is_slot_free = islist(equipment) ? i > length(equipment) : isnull(equipment)
- if(is_slot_free)
- data += list(list(
- "slot" = category
- ))
- if(ui_selected_module_index == module_index)
- ui_selected_module_index = null
- else
+ if(max_per_category)
+ for(var/i = 1 to max_per_category)
+ var/equipment = equip_by_category[category]
+ var/is_slot_free = islist(equipment) ? i > length(equipment) : isnull(equipment)
+ if(is_slot_free)
+ data += list(list(
+ "slot" = category
+ ))
+ if(ui_selected_module_index == module_index)
+ ui_selected_module_index = null
+ continue
var/obj/item/mecha_parts/mecha_equipment/module = islist(equipment) ? equipment[i] : equipment
data += list(list(
"slot" = category,
@@ -140,7 +141,7 @@
))
if(isnull(ui_selected_module_index))
ui_selected_module_index = module_index
- module_index++
+ module_index++
return data
/obj/vehicle/sealed/mecha/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
diff --git a/code/modules/vehicles/mecha/mecha_wreckage.dm b/code/modules/vehicles/mecha/mecha_wreckage.dm
index dc0414e86063a..8f972ef47815c 100644
--- a/code/modules/vehicles/mecha/mecha_wreckage.dm
+++ b/code/modules/vehicles/mecha/mecha_wreckage.dm
@@ -245,3 +245,8 @@
/obj/item/mecha_parts/part/odysseus_right_arm,
/obj/item/mecha_parts/part/odysseus_left_leg,
/obj/item/mecha_parts/part/odysseus_right_leg)
+
+/obj/structure/mecha_wreckage/justice
+ name = "\improper Justice wreckage"
+ icon_state = "justice-broken"
+ welder_salvage = list(/obj/item/stack/sheet/iron, /obj/item/stack/rods)
diff --git a/code/modules/vehicles/scooter.dm b/code/modules/vehicles/scooter.dm
index 0b842b8ed9cbe..08edbf7886799 100644
--- a/code/modules/vehicles/scooter.dm
+++ b/code/modules/vehicles/scooter.dm
@@ -206,6 +206,21 @@
to_chat(rider, span_warning("[src] [p_are()] not powerful enough to fly upwards."))
return FALSE
+/obj/vehicle/ridden/scooter/skateboard/hoverboard/holyboarded
+ name = "holy skateboard"
+ desc = "A board blessed by the gods with the power to grind for our sins. Has the initials 'J.C.' on the underside."
+ board_item_type = /obj/item/melee/skateboard/holyboard
+ instability = 3
+ icon_state = "hoverboard_holy"
+/obj/vehicle/ridden/scooter/skateboard/hoverboard/holyboarded/post_buckle_mob(mob/living/M)
+ M.AddComponent(/datum/component/anti_magic, MAGIC_RESISTANCE|MAGIC_RESISTANCE_HOLY)
+ return ..()
+
+/obj/vehicle/ridden/scooter/skateboard/hoverboard/holyboarded/post_unbuckle_mob(mob/living/M)
+ if(!has_buckled_mobs())
+ qdel (M.GetComponent(/datum/component/anti_magic, MAGIC_RESISTANCE|MAGIC_RESISTANCE_HOLY))
+ return ..()
+
/obj/vehicle/ridden/scooter/skateboard/hoverboard/admin
name = "\improper Board Of Directors"
desc = "The engineering complexity of a spaceship concentrated inside of a board. Just as expensive, too."
diff --git a/code/modules/vehicles/vehicle_key.dm b/code/modules/vehicles/vehicle_key.dm
index f8db861449f7a..5f57895d8ac81 100644
--- a/code/modules/vehicles/vehicle_key.dm
+++ b/code/modules/vehicles/vehicle_key.dm
@@ -33,10 +33,15 @@
attack_verb_continuous = list("stubs", "pokes")
attack_verb_simple = list("stub", "poke")
sharpness = SHARP_EDGED
- embedding = list("pain_mult" = 1, "embed_chance" = 30, "fall_chance" = 70)
+ embed_type = /datum/embed_data/janicart_key
wound_bonus = -1
bare_wound_bonus = 2
+/datum/embed_data/janicart_key
+ pain_mult = 1
+ embed_chance = 30
+ fall_chance = 70
+
/obj/item/key/janitor/suicide_act(mob/living/carbon/user)
switch(user.mind?.get_skill_level(/datum/skill/cleaning))
if(SKILL_LEVEL_NONE to SKILL_LEVEL_NOVICE) //Their mind is too weak to ascend as a janny
diff --git a/code/modules/vending/_vending.dm b/code/modules/vending/_vending.dm
index 9ca0d41d85367..737a2993d9bef 100644
--- a/code/modules/vending/_vending.dm
+++ b/code/modules/vending/_vending.dm
@@ -1074,11 +1074,9 @@ GLOBAL_LIST_EMPTY(vending_machines_to_restock)
var/mob/living/carbon/carbon_target = atom_target
for(var/i in 1 to num_shards)
var/obj/item/shard/shard = new /obj/item/shard(get_turf(carbon_target))
- shard.embedding = list(embed_chance = 100, ignore_throwspeed_threshold = TRUE, impact_pain_mult = 1, pain_chance = 5)
- shard.updateEmbedding()
+ shard.set_embed(/datum/embed_data/glass_candy)
carbon_target.hitby(shard, skipcatch = TRUE, hitpush = FALSE)
- shard.embedding = list()
- shard.updateEmbedding()
+ shard.set_embed(initial(shard.embed_type))
return TRUE
if (VENDOR_CRUSH_CRIT_PIN) // pin them beneath the machine until someone untilts it
if (!isliving(atom_target))
diff --git a/code/modules/vending/subtype.dm b/code/modules/vending/subtype.dm
new file mode 100644
index 0000000000000..9b4f212224ea7
--- /dev/null
+++ b/code/modules/vending/subtype.dm
@@ -0,0 +1,51 @@
+
+/obj/machinery/vending/subtype_vendor
+ name = "\improper subtype vendor"
+ desc = "A vending machine that vends all subtypes of a specific type."
+ color = COLOR_ADMIN_PINK
+ verb_say = "codes"
+ verb_ask = "queries"
+ verb_exclaim = "compiles"
+ armor_type = /datum/armor/machinery_vending
+ circuit = null
+ product_slogans = "Spawn \" too annoying? Too lazy to open game panel? This one's for you!;Subtype vendor, for all your debugging woes!"
+ default_price = 0
+ /// Spawns coders by default
+ var/type_to_vend = /obj/item/food/grown/citrus
+
+/obj/machinery/vending/subtype_vendor/Initialize(mapload, type_to_vend)
+ . = ..()
+ if(type_to_vend)
+ src.type_to_vend = type_to_vend
+ load_subtypes()
+
+/obj/machinery/vending/subtype_vendor/proc/load_subtypes()
+ products = list()
+ product_records = list()
+
+ for(var/type in typesof(type_to_vend))
+ LAZYADDASSOC(products, type, 50)
+
+ build_inventories()
+
+/obj/machinery/vending/subtype_vendor/attack_hand_secondary(mob/user, list/modifiers)
+ . = ..()
+ if(. == SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN)
+ return
+
+ if(!can_interact(user) || !user.can_perform_action(src, ALLOW_SILICON_REACH|FORBID_TELEKINESIS_REACH))
+ return
+
+ if(!user.client?.holder?.check_for_rights(R_SERVER|R_DEBUG))
+ speak("Hey! You can't use this! Get outta here!")
+ return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
+
+ var/type_to_vend_now = tgui_input_text(user, "What type to set it to?", "Set type to vend", "/obj/item/toy/plush")
+ type_to_vend_now = text2path(type_to_vend_now)
+ if(!ispath(type_to_vend_now))
+ speak("That's not a real path, dumbass! Try again!")
+ return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
+
+ type_to_vend = type_to_vend_now
+ load_subtypes()
+ return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
diff --git a/code/modules/wiremod/core/marker.dm b/code/modules/wiremod/core/marker.dm
index 31821c6d8c3a7..65ed029213e30 100644
--- a/code/modules/wiremod/core/marker.dm
+++ b/code/modules/wiremod/core/marker.dm
@@ -3,6 +3,7 @@
desc = "A circuit multitool. Used to mark entities which can then be uploaded to components by pressing the upload button on a port. \
Acts as a normal multitool otherwise. Use in hand to clear marked entity so that you can mark another entity."
icon_state = "multitool_circuit"
+ apc_scanner = FALSE // would conflict with mark clearing
/// The marked atom of this multitool
var/atom/marked_atom
diff --git a/config/game_options.txt b/config/game_options.txt
index e89cbdb7d2b70..28a50266ff814 100644
--- a/config/game_options.txt
+++ b/config/game_options.txt
@@ -540,3 +540,8 @@ NEGATIVE_STATION_TRAITS 3 1
# If set to 0, then players won't be able to select any positive quirks.
# If commented-out or undefined, the maximum default is 6.
MAX_POSITIVE_QUIRKS 6
+
+# A config that skews with the random spawners weights
+# If the value is lower than 1, it'll tend to even out the odds
+# If higher than 1, it'll lean toward common spawns even more.
+RANDOM_LOOT_WEIGHT_MODIFIER 1
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-84011.yml b/html/changelogs/AutoChangeLog-pr-84011.yml
deleted file mode 100644
index 12b730fefdcdf..0000000000000
--- a/html/changelogs/AutoChangeLog-pr-84011.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-author: "Pickle-Coding"
-delete-after: True
-changes:
- - code_imp: "Supermatter zap power generation takes perspective of the machines subsystem."
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-84230.yml b/html/changelogs/AutoChangeLog-pr-84230.yml
deleted file mode 100644
index 165b6de5e2c7a..0000000000000
--- a/html/changelogs/AutoChangeLog-pr-84230.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-author: "SmArtKar"
-delete-after: True
-changes:
- - bugfix: "Vent-born wendigos no longer create one-way portals"
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-84374.yml b/html/changelogs/AutoChangeLog-pr-84374.yml
deleted file mode 100644
index 5b4a368896c57..0000000000000
--- a/html/changelogs/AutoChangeLog-pr-84374.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-author: "ShizCalev"
-delete-after: True
-changes:
- - bugfix: "The power for all science burn chambers across all maps now works properly."
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-84439.yml b/html/changelogs/AutoChangeLog-pr-84439.yml
deleted file mode 100644
index fd4591d8f8792..0000000000000
--- a/html/changelogs/AutoChangeLog-pr-84439.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-author: "Melbert"
-delete-after: True
-changes:
- - rscadd: "Humanizing a monkey removes undergarments such as socks"
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-84448.yml b/html/changelogs/AutoChangeLog-pr-84448.yml
deleted file mode 100644
index b579cc85ac2e3..0000000000000
--- a/html/changelogs/AutoChangeLog-pr-84448.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-author: "thegrb93"
-delete-after: True
-changes:
- - bugfix: "Game not refocusing after closing a TGUI"
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-84667.yml b/html/changelogs/AutoChangeLog-pr-84667.yml
new file mode 100644
index 0000000000000..56ec74adf6c7f
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-84667.yml
@@ -0,0 +1,6 @@
+author: "aaaa1023"
+delete-after: True
+changes:
+ - rscdel: "Removed the shuttle manipulator entries for the following shuttles: Northstar ferry, Omegastation arrivals shuttle, and Donutstation cargo ferry. (These shuttles didn't actually exist but they still had entries in the manipulator.)"
+ - bugfix: "Fixed the following shuttles flying in unexpected directions: Basic CC Ferry, Meat Ferry, Lighthouse Ferry, ERT bounty shuttle, Kilo cargo shuttle, Pubby cargo shuttle, and Delta cargo shuttle."
+ - bugfix: "Fixed the ERT bounty shuttle having incorrect offsets in the shuttle navigation console."
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-84675.yml b/html/changelogs/AutoChangeLog-pr-84675.yml
new file mode 100644
index 0000000000000..8f342cfa69513
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-84675.yml
@@ -0,0 +1,4 @@
+author: "SmArtKar"
+delete-after: True
+changes:
+ - refactor: "Refactored a lot of speech modifiers to use a component instead of copied over code."
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-84736.yml b/html/changelogs/AutoChangeLog-pr-84736.yml
new file mode 100644
index 0000000000000..8f71ff39204f8
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-84736.yml
@@ -0,0 +1,4 @@
+author: "grungussuss"
+delete-after: True
+changes:
+ - sound: "Zippos, Lighters and cigarettes now have sound"
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-84749.yml b/html/changelogs/AutoChangeLog-pr-84749.yml
new file mode 100644
index 0000000000000..0e3b49a139446
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-84749.yml
@@ -0,0 +1,5 @@
+author: "SmArtKar"
+delete-after: True
+changes:
+ - bugfix: "Living limbs can no longer make you touch ghosts or abstract concepts of start, landmark, influence or job"
+ - spellcheck: "Fixed improper word usage and improved grammar for living limbs"
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-84776.yml b/html/changelogs/AutoChangeLog-pr-84776.yml
new file mode 100644
index 0000000000000..b56c51cc767e3
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-84776.yml
@@ -0,0 +1,4 @@
+author: "SmArtKar"
+delete-after: True
+changes:
+ - bugfix: "Exosuit Stress Failure experiment now works"
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-84781.yml b/html/changelogs/AutoChangeLog-pr-84781.yml
new file mode 100644
index 0000000000000..b59fcad5de6bf
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-84781.yml
@@ -0,0 +1,4 @@
+author: "zoomachina"
+delete-after: True
+changes:
+ - bugfix: "fixed dubious chem dispenser feedback when the beaker is full"
\ No newline at end of file
diff --git a/html/changelogs/archive/2024-07.yml b/html/changelogs/archive/2024-07.yml
new file mode 100644
index 0000000000000..d64be8b671a22
--- /dev/null
+++ b/html/changelogs/archive/2024-07.yml
@@ -0,0 +1,581 @@
+2024-07-01:
+ Ben10Omintrix:
+ - balance: raptors will now knock off their rider and disable them if hit by any
+ energy projectiles or if they recieve any stamina damage
+ Chestlet:
+ - bugfix: Nanotrasen sent us a batch of faulty canisters. They've been recalled
+ and replaced with less faulty canisters.
+ - bugfix: Zauker SM interaction works correctly now.
+ DaCoolBoss:
+ - image: Relics ("strange objects") now have unique sprites.
+ Iajret:
+ - bugfix: fixed blood loss knocking you down at somewhat safe (~80%) blood levels
+ LemonInTheDark:
+ - rscadd: Shield generators and shield gen walls now glow a light blue. Pretty!
+ MTandi:
+ - qol: Made light tiles available in the crafting menu
+ - qol: Circuit tile variants can be cycled in-hand
+ - rscadd: Moved loose tiles and wall frames from lathe designs to other tiles and
+ frames in the crafting menu
+ Melbert:
+ - rscadd: Humanizing a monkey removes undergarments such as socks
+ - qol: Handheld Genetic Scanners fit in Geneticist equipment suit storage
+ Pickle-Coding:
+ - code_imp: Supermatter zap power generation takes perspective of the machines subsystem.
+ Rhials:
+ - rscadd: Bounty Hunter teams now have personalized announcements for when they
+ are spawned in.
+ ShizCalev:
+ - bugfix: The power for all science burn chambers across all maps now works properly.
+ SmArtKar:
+ - image: Captain's spare safe received a new texture
+ - bugfix: Vent-born wendigos no longer create one-way portals
+ - image: Decluttered card textures
+ - bugfix: You can grind slime extracts in reagent grinders once more.
+ - bugfix: Pyrokinesis bolts no longer have infinite range and create trails of fiery
+ doom.
+ - qol: RD's labcoat is now classified as an actual labcoat instead of a glorified
+ jacket, allowing them to put science-related stuffs into it
+ - image: You can now toggle RD's labcoat
+ ViktorKoL:
+ - rscadd: Added a new UI for heretic research.
+ Wallem:
+ - rscadd: The detective now starts with the DET.ekt Skillchip, which allows them
+ to identify chemicals and bloodtypes by taste.
+ YesterdaysPromise:
+ - image: updated halloween metoer sprites.
+ aaaa1023:
+ - qol: 'Increased the viewrange in the navigation camera console on the following
+ shuttles: Silverscale pirate shuttle, the Flying Dutchman pirate shuttle, the
+ IRS pirate shuttle, and the Greytide pirate shuttle.'
+ - bugfix: Fixed the Battlecruiser corvette, Silverscale pirate shuttle, and the
+ Greytide pirate shuttle flying in incorrect directions in hyperspace.
+ - bugfix: Fixed the Navigation console camera eye on various pirate ships being
+ off centre.
+ carlarctg:
+ - spellcheck: Heretic spell invocations now use one dead language per path. Altered
+ a few invocation types.
+ grungussuss:
+ - qol: Windoors now stay open for 8 seconds instead of 5
+ - qol: Secure windoors now stay open for 5 seconds instead of 2
+ mc-oofert:
+ - bugfix: brig cell timer ui works properly now
+ - bugfix: wawastation arrivals has firealarms so you may now leave
+ necromanceranne:
+ - bugfix: Mecha weaponry is capable, for the first time ever, of experiencing recoil.
+ This was an intended mechanic, I promise. The code just literally never worked.
+ - bugfix: Mecha bump melee attacks and click melee attacks are now on the same cooldown,
+ but have varying cooldown timers. You will always bump attack faster than you
+ will click.
+ - bugfix: You must be in combat mode to punch objects and to bumpsmash into objects.
+ - bugfix: Stops mecha being able to punch literally any object and damage them.
+ - code_imp: Tidies up some of the autodoc comments for mech weapons.
+ spockye:
+ - bugfix: fixed Wawastation areas
+ - bugfix: fixed wawastation disposals
+ thegrb93:
+ - bugfix: Game not refocusing after closing a TGUI
+ xXPawnStarrXx:
+ - rscdel: Removed cordons on deathmatch maps, since they're autoadded now.
+2024-07-02:
+ 00-Steven:
+ - code_imp: Moved bedsheet bin interactions to the item interaction code. Please
+ report any issues.
+ - qol: Made bedsheet bin tool interactions right click, such that left click is
+ consistently for putting in items.
+ - qol: Added usage screentips to bedsheet bins.
+ - qol: Added more feedback to failing to hide items in bedsheet bins.
+ - sound: Made putting items in bedsheet bins not silent (If the items have associated
+ pickup/drop sounds).
+ - code_imp: Deconstructing light switches now uses the proper tool action and tool
+ usage code, please report any issues.
+ - bugfix: Attempting to deconstruct a light switch by unscrewing it no longer makes
+ you hit it even on a success.
+ - sound: Deconstructing a light switch actually plays tool usage and deconstruction
+ sounds.
+ - qol: Deconstructing light switches is now a left click with a screwdriver parallel
+ to other unscrewing actions.
+ - bugfix: Screentips for deconstructing a light switch no longer show up on every
+ item EXCEPT screwdrivers.
+ - qol: Added an examine hint denoting light switches are screwed to the wall.
+ - qol: Added visible messages for someone deconstructing a light switch parallel
+ to deconstructing intercoms.
+ Bisar:
+ - qol: Loot panels should update more predictably and informatively now.
+ - code_imp: Lootpanels have more consistent logic on when they automatically update.
+ Jolly:
+ - code_imp: Behind the scenes, maps had a little bit of tweaking. If you see things
+ rendering in weird ways, or they don't look like how they used to/supposed to,
+ please report them on GitHub!
+ Kapu (ported by StrangeWeirdKitten):
+ - bugfix: Ambience buzz will now respect ship ambience prefrences for observers.
+ - sound: Ambience buzz requires APC enviorment power to function
+ Kocma-san:
+ - qol: you can now swap pens in pdas
+ LemonInTheDark:
+ - bugfix: Some varieties of snow now visually melt properly again when burned
+ ShizCalev:
+ - bugfix: Fixed a number of shuttles having parts (such as lattices) completely
+ disappearing.
+ - bugfix: Fixed the ceilings above shuttles on station maps being full-bright.
+ - bugfix: Fixed lattices sometimes appearing at random locations in space on station
+ maps.
+ - bugfix: Cleaned up a number of accidentally placed objects in space across all
+ station maps.
+ - bugfix: Fixed a false positive with the mapload_space_verification unit test failing
+ on turfs that weren't actually part of shuttles.
+ - code_imp: Added a unit test that automatically finds all base space turfs with
+ objects on them, as well as non-space turfs that are set to space areas (meaning
+ that these squares weren't lit properly.)
+ SmArtKar:
+ - bugfix: You no longer get dusted upon trying to store your supermatter sliver.
+ - bugfix: Fulton animation is no longer sideways.
+ - bugfix: Hand of Midas now works point-blank.
+ TheBoondock:
+ - sound: added pickup and drop sound for beakers
+ carlarctg:
+ - bugfix: Fix /datum/weakref appearing when linking airlock heretic portals
+ hyperjll:
+ - bugfix: 'Hostile plant monsters (EX: Killer Tomatoes) no longer act hostile toward
+ Seedlings.'
+ jlsnow301:
+ - bugfix: Fixed some UI bugs in the power monitor screen.
+ lorwp:
+ - bugfix: extinguishers now can be filled with subtypes of water again (Namely,
+ holy water)
+ mc-oofert:
+ - balance: Facehuggers dont make people go to sleep but muffles speech
+ - code_imp: Very very minor xenomorph code cleanup
+ - refactor: Muzzles are now an element
+ - balance: portable air scrubbers scrub in a 3x3 square + hold as much gas as a
+ canister
+ nikothedude:
+ - rscadd: Being cursed now enables disembowlements/cranial fissures outside of hardcrit
+2024-07-03:
+ 00-Steven:
+ - bugfix: Fixed telekinesis letting you grab people at a distance, and teleport
+ them to you by strangling them.
+ DaCoolBoss:
+ - bugfix: Removed three traitor posters from Cargo Warehouse's walls. (Metastation)
+ - balance: Added one traitor poster each to the QM's Office, Vault and Evidence
+ Storage to compensate. (Metastation)
+ - rscadd: Ghost role food truck merchants may occasionally turn up at the station.
+ - rscadd: Adds a new fugitive hunter type, MI13 secret agents.
+ DrDiasyl aka DrTuxedo:
+ - sound: Shoves now produce more meaty sound!
+ GremlinSeeker:
+ - bugfix: fixed Birdshot bar and surrounding areas not properly connected to the
+ power grid.
+ LT3:
+ - qol: Crew monitor defaults to sort by vitals
+ MTandi:
+ - image: New linen bin / basket sprites
+ - qol: It is easier to do some required techweb experiments on Charlie station now
+ - bugfix: Fixed experimental dissection surgeries giving too many points
+ - balance: 'Techweb: Moved NTNet relay back to tcomms node'
+ - balance: 'Techweb: Moved blood pack and chem pack to the starting node'
+ MrDas:
+ - bugfix: Bolas now slowdown properly.
+ - bugfix: Cult bolas no longer leave in-hand sprite when they ensnare to non-cult
+ user.
+ OrbisAnima:
+ - bugfix: Fixed the basic Sandwich recipe and tags, now it matches the description
+ and original intent.
+ - bugfix: Brought the fishing rewards experiments to normal values.
+ - bugfix: Bio Emergency crates now bring Bio Suits and Hoods compatible with the
+ Security Hoods and Suits Schematics.
+ Rhials:
+ - balance: All cameras in bathrooms and showers have been removed.
+ SmArtKar:
+ - bugfix: Dark matteors no longer claim that they have missed when they actually
+ impacted the station
+ - image: Fixed shading on some metal ingots.
+ Sosmaster9000:
+ - balance: 'Xenobiologists now have a BZ-filled containment pen. Don''t breathe
+ that!
+
+ balance:Some items which either cannot be printed elsewhere or are part of game
+ progression (e.g. teleporter endpoints and, of course, Nanners) have been moved.
+ Alien Spawnpoints are in the same spot.'
+ bob-b-b:
+ - rscadd: Added flatpacker & multitool to all R&D labs
+ carlarctg:
+ - rscdel: Removed Trichromatic Larynx per @mothblocks decision that it looks uggo.
+ - rscdel: Replaced heckacious laryncks' color and size changes with random bolding,
+ italics, and underlining.
+ - rscadd: Stoner has been un-locked and replaces TL in the above's recipe.
+ - bugfix: Fixed elastic arms users being unable to use abstract items like mending
+ touch or shock touch.
+ - bugfix: Fixed mending touch being bad
+ mc-oofert:
+ - bugfix: portascrubbers also scrub the tile theyre on
+2024-07-04:
+ 00-Steven:
+ - code_imp: Moved bedsheet interactions to the item interaction code. Please report
+ any issues.
+ - bugfix: Bedsheets adjust their offset to match that of the living they're tucking
+ in.
+ - sound: Bedsheets use the cloth drop/pickup sounds instead of being silent.
+ - qol: You can tuck someone in telekinetically.
+ ArcaneDefence:
+ - rscadd: You can now microwave station pets that you can pick up, with predictable
+ outcomes
+ Ben10Omintrix:
+ - bugfix: fixes mansus grasp not clearing runes
+ - bugfix: fixes not being able to clean microwaves
+ Bisar:
+ - bugfix: Dastardly clowns will no longer be able to get counted for three times
+ the steps by moving diagonally.
+ Ghommie:
+ - bugfix: Fixed toggleable screen colors for glasses.
+ - code_imp: De-hardcoded, refactored the above, now-fixed feature.
+ - rscadd: Pumpkin hardhats and the hood of the flash suit now affect the color of
+ your screen.
+ - rscadd: Prism glasses, obtainable through xenobiology crossbreeding, now also
+ affect the color of your screen.
+ - qol: You no longer need to reach the very edge of the game screen to reach the
+ maximum zoom range when scoped.
+ LT3:
+ - sound: Changed blood filter cycle sound
+ MTandi:
+ - image: Fixed the cargo crate having wrong stripe width in open state
+ - rscadd: Added Pipe Scrubber portable atmos machine
+ Melbert:
+ - rscadd: Humonkeys and random corpse spawns now look more... human.
+ Paxilmaniac:
+ - image: The sprites for the projectile dampener field have been updated
+ Profakos:
+ - bugfix: Instruments enhanced with portable tuning can perform the rites that have
+ been granted
+ SmArtKar:
+ - image: Some gloves have received new sprites
+ - bugfix: Mending touch no longer damages non-humans
+ - rscdel: You can no longer get turned into a dullahan by a genetic meltdown
+ - refactor: Refactored genetic meltdown code into datums, making it more extendable.
+ - image: Updated diamond stacks to fit its "unstacked" version.
+ - refactor: Bayonet attachment is now a component.
+ SyncIt21:
+ - bugfix: omni crowbar tool interaction for replacing tiles has been fixed
+ - bugfix: techfab screentip does not runtime when you hover over it with an omnitool
+ multitool
+ - bugfix: medi borgs can do brain surgery again
+ - code_imp: improved multitool & general tool code for some machines
+ - bugfix: you can hold your wound while resting via ctrl click
+ - bugfix: material container won't consume the contents of an item if that item
+ itself is rejected for any reason
+ - qol: you can use any storage medium & not just bags/boxes to dump stuff into material
+ containers
+ - code_imp: improved mouse drag & drop code
+ - code_imp: Improves cpu performance of transferring & removing reagents
+ - bugfix: plumbing machinery should have consistent volumes throughout the course
+ of its operations
+ - bugfix: plumbing iv drop now only accepts reagents from ducts but won't put reagents
+ back into it i.e. it only has a input pipe
+ Thunder12345:
+ - bugfix: Birdshot's AI core no longer has windoors concealed under open shutters.
+ ViktorKoL:
+ - rscadd: the Feast of Owls now leaves more dramatic messages in your chat
+ - sound: the Feast of Owls now has a little theme
+ carlarctg:
+ - qol: Transhumanist and prosthetic limb no longer conflict. If you pick the same
+ limb for both it uses the weaker prosthetic (dumbass)
+ - code_imp: Made the name for prosthetic limb global list more intelligible
+ jlsnow301:
+ - bugfix: Reverts the fully interactive lootpanel, please just refresh it if you
+ want to see new contents
+ - rscadd: 'Added a bitrunning deathmatch map: Island Brawl. Both ghosts and runners
+ get many more spawns than normal.'
+ - bugfix: Lowered the static vision time in domain load in.
+ - bugfix: Bitrunning hacker aliases are now much more permissive
+ mc-oofert:
+ - rscadd: 254 new vox lines
+ - balance: removed tasers, the unfirable turret gun, death wand, polymorph wands
+ and the enchanted modsuit from lootbox loot tables
+ - bugfix: lootbox guns should now mostly not have syndicate firing pins so you can
+ actually use them
+ - bugfix: deathmatch OSHA Violator map has actually functioning field gens now
+ - bugfix: being polymorphed in deathmatch does not count you dead
+ - bugfix: deathmatch cyborgs may not talk in binary
+ - qol: deathmatch map names are sorted alphabetically
+ - spellcheck: deathmatch final destination clown and chef loadouts have been renamed
+ to avoid confusion
+ - rscadd: mecha wire that shoots you or anyone nearby if pulsed or cut
+ - bugfix: mecha shock wire shocks you
+2024-07-05:
+ Absolucy:
+ - qol: Linked speech now prevents messages from highlighting if its a message you
+ sent, or if you are the owner of said link..
+ - qol: Linked speech messages are now marked as radio messages, for chat tab purposes.
+ ArcaneMusic:
+ - rscadd: The Illumination Applications technode now includes a gunkit for a new
+ gun, the Photon Cannon.
+ DATA_, with sounds made by Beebblie:
+ - sound: You will now be able to hear your muffled breathing while using internals.
+ This can be turned off in the game settings.
+ Ghommie:
+ - rscadd: A station trait that fills maintenance with glowsticks.
+ Iamgoofball:
+ - rscadd: You can now craft a singularity hammer using all nine anomaly cores and
+ the supermatter.
+ LemonInTheDark:
+ - bugfix: Grass turfs will render properly now. Reworked how floors render, please
+ report any bugs!
+ - bugfix: Cameras now properly capture lighting
+ - bugfix: The layering seen in photos should better match the actual game
+ MTandi:
+ - bugfix: 'Techweb: Moved upgraded cyber organs to tier 3 from tier 4'
+ - qol: 'Techweb: Added fully augmented android scan discount experiment for Advanced
+ Cybernetic Organs techweb node'
+ - qol: Data disks are now printed in lathes instead of circuit imprinter
+ - balance: 'Techweb: High yield explosive is now a discount experiment for Exotic
+ Ammo node, giving more free points'
+ - balance: 'Techweb: Replaced the botany/xenobio discount experiments with a new
+ mutant scan experiment for the Gene Engineering node'
+ - balance: 'Techweb: Damaged mech scan is a discount experiment for tier 4 Mech
+ Energy Guns node, giving twice more free points'
+ - balance: 'Techweb: Added a new anomaly scan experiment that gives full discount
+ for the Advanced Anomaly Shells, also moving the node to tier 5'
+ - balance: 'Techweb: Moved mech RCD to the Experimental Tools node'
+ - balance: 'Techweb: Moved handcuffs to T2 security node, leaving only zipties in
+ T1'
+ - balance: 'Techweb: Non-human Autopsy is a discount experiment instead of a required
+ one now'
+ - balance: 'Techweb: Parts scan experiments require 6 machines instead of 4'
+ - rscdel: 'Techweb: Removed the New Toys node moving contents to sec and medbay
+ trees'
+ - qol: Made air horn craftable from a spraycan and a bikehorn
+ - qol: 'Techweb: Better wording on the slime scan experiment'
+ - refactor: Vat grower is a normal machine now that doesn't need plumbing
+ - qol: Cytology petri dish smartfridge starts with 3 random samples
+ - rscadd: Added protein powder condiment bottle, available in the cytology supplies
+ locker and the cargo pack
+ - rscdel: Removed science variant of plumbing RCD
+ - bugfix: Fixed a bug when machines without stock parts didn't spawn frames on deconstruction
+ - bugfix: Fixed vat grower not growing
+ - qol: portable scubber, pump and pipe scrubber are buildable as any other machines
+ OrionTheFox:
+ - rscadd: updated Icebox's lavaland ruin. The incursion grows...
+ Rhials:
+ - admin: Quiet-announce random events still announce to admins and can therefore
+ be rerolled.
+ Xackii:
+ - rscadd: 'New traitor mech: Justice. Emag fabricator to get it.'
+ Zergspower:
+ - bugfix: fixes borgs ability to climb
+ aaaa1023:
+ - qol: Increased view_range on the Psyker Bounty Hunters' Shuttle navigation console.
+ - bugfix: 'Fixed the shuttle navigation console camera eye being incorrectly offset
+ on: the SpacePol van, the Russian bounty hunters, the default bounty hunters,
+ the Psyker bounty hunters, and the MI13 Foodtruck.'
+ - bugfix: Fixed the SpacePol van, Russian bounty hunters, default bounty hunters,
+ and Psyker bounty hunters' shuttles all flying in incorrect directions.
+ - bugfix: 'Fixed the following shuttles flying in unexpected directions: Pubby escape
+ pod, Humpback emergency shuttle, Pubby monastery shuttle, Birdboat emergency
+ shuttle, Birdshot emergency shuttle'
+2024-07-06:
+ 00-Steven:
+ - image: Goody case locked/unlocked sprites have been swapped again, having a gap
+ for unlocked and no gap for locked.
+ ArcaneMusic:
+ - qol: Examining a windoor will now provide the construction steps, like other standard
+ buildable objects.
+ DaCoolBoss:
+ - code_imp: Cleaned up relic code.
+ - image: Added necrotech themed relics.
+ GoblinBackwards:
+ - bugfix: Firefighting backpack tank nozzle can be used to hit objects and destroy
+ atmos resin again.
+ MTandi:
+ - bugfix: Binary devices no longer ignore the volume of the output pipe network
+ - bugfix: Fixed the augmented organs experiment
+ OrionTheFox:
+ - image: fixed and touched up the icons for the Doorjack and Cryptographic Sequencer
+ (EMAG)
+ Seven:
+ - balance: Bloody footprint trails go a longer distance now.
+ SyncIt21:
+ - bugfix: Autolathe won't eat the borg omni tool
+ carlarctg:
+ - bugfix: Fixed temp n space adaptations not conflicting
+ - rscadd: Projectile Dampener field now reduces stuns and stamina damage from incoming
+ projectiles
+ hyperjll:
+ - bugfix: Reagent containers are no longer mysteriously fireproof.
+ mc-oofert:
+ - qol: CE and Engineer start with their hardhat as the respective welding hard hat
+ variant
+ necromanceranne:
+ - balance: Settlers are a bit slower on their steeds.
+2024-07-07:
+ Holoo-1:
+ - bugfix: fixed ninja's adrenaline boost module not being rechargable
+ SyncIt21:
+ - bugfix: you can craft the singulo hammer with either a supermatter engine/shard
+ again
+2024-07-08:
+ 00-Steven:
+ - bugfix: Fixes hacker alias name preference not working.
+ Ben10Omintrix:
+ - bugfix: raptors don't easily stress out in lavaland environments
+ Ghommie:
+ - rscadd: Night vision goggles now subtly tint your screen.
+ - rscadd: You can turn your night vision goggles off. Doing so removes the tint
+ and the eye protection malus.
+ - bugfix: Hoverboards properly dysfunction in space without any kind of support
+ underneath them.
+ - qol: Mood more promptly reacts to the beauty of an area as it changes, and not
+ just when changing areas.
+ - rscadd: Coroners (and potentially other mobs with the morbid trait), now like
+ degradated-looking rooms.
+ - rscadd: Aquariums now influence the beauty score of an area based on their contents.
+ - rscadd: Morbid people now get a positive moodlet when admiring an aquarium full
+ of dead fish, and a negative one when fish are alive.
+ - admin: Added a config that regulares random spawners weights.
+ - rscadd: 12% chance of a ouija board spawning in the chaplain office. Wawastation
+ always has one.
+ GremlinSeeker:
+ - bugfix: ' Birdshot botany is resized. bar is also smaller.'
+ - bugfix: Birdshot public shrine now uses the correct dirt. (Goodbye chasms)
+ LT3:
+ - balance: Advanced Medbay Equipment research node lowered to tier 3
+ - balance: Advanced Medbay Equipment now requires haloperidol scan experiment
+ - balance: Haloperidol and cryostylane experiments can be performed roundstart
+ - balance: Cryostasis research node raised to tier 4
+ - balance: Cryostasis now has Fusion instead of Controlled Plasma as a prerequisite
+ - balance: Cryostasis cryostylane scan is now a discount experiment
+ - balance: Crystallizer moved from Controlled Plasma to Fusion
+ Loafin34:
+ - rscadd: There's a previously undiscovered variant of a nullrod, recently revealed
+ from the depths of the skating rink..
+ Melbert:
+ - qol: Vending Machines have a search bar
+ - qol: Random player characters now look less like rainbow vomit and more like some
+ of the most average people you know.
+ SmArtKar:
+ - bugfix: Added a forgotten firelock on second floor of wawa
+ - bugfix: Fixed glass shards generated from falling vending machines or tackling
+ windows not being able to embed into anyone.
+ - refactor: Refactored embedding code to use datums instead of bespoke elements
+ and ugly associated lists.
+ - bugfix: You require breathing once more.
+ Toastgoats:
+ - rscadd: Added the designer EVA suit, a unique pirate hardsuit for Silverscales.
+ - balance: Increased the equipment available to Silverscales
+ - image: Sprites for the designer EVA suit
+ carlarctg:
+ - spellcheck: Renamed the organ thrower module to the 'organizer'
+ - spellcheck: Made its description more accurate. Did you know it instantly replaces
+ up to 5 organs in active surgery?
+ homexp13:
+ - bugfix: ' Examining a fishing spot twice with sufficiently high fishing skill
+ (or the skillchip) will get you a list of fishes that can ACTUALLY be caught'
+ jlsnow301:
+ - bugfix: Fixed the bluescreen while setting a custom font in tgui panel
+ - bugfix: Fixed the orbit icons scrolling sometimes
+ lorwp:
+ - rscadd: using 'skibidi' as a chaplain deity now gives you brain damage
+ mc-oofert:
+ - bugfix: defibrillator no longer has an abstract cell
+ - code_imp: defibrillator attackby replaced with item_interaction
+ - balance: All jetpacks are now modsuit jetpack speed
+ - rscdel: Adv. Ion Jetpack module has been removed
+ - bugfix: you may now use a legion core on an elite tumor
+ necromanceranne:
+ - balance: You can only stun someone with a shove if they have been shoved into
+ objects or people, or have been hit by telescopic batons, contractor batons
+ or the Mansus Grasp.
+ - balance: If someone is knocked to the floor, however, you can still disarm them
+ of any object with a shove.
+ - bugfix: Knight helmets no longer burn as though made of cloth.
+ - rscadd: Fletching starter kit! Make your own bow, shoot your friends in an unfortunate
+ workplace accident. Replace all those holy arrows you lost.
+ - bugfix: Bows now properly undraw once they have fired an arrow.
+ - code_imp: Bows now utilize overlays in order to display loaded arrows. Unique
+ overlays per arrow.
+2024-07-09:
+ 00-Steven:
+ - qol: You can now copy blast door controller IDs directly onto shutters/blast doors,
+ avoiding the need to open a menu for each one. Additionally, this lets you fix
+ sets of roundstart shutters without needing to change the IDs on all of the
+ ones in that set.
+ ArcaneMusic:
+ - bugfix: Ore vents that are caught in the cross-fire of a lavaland tendril collapsing
+ will now spare the ore vent and it's associated turf.
+ Astrogem2:
+ - sound: Added audible zipping to winter coats.
+ DGamerL:
+ - refactor: refactored `GetExactComponent` to be 1641 compatible
+ Derpguy3:
+ - bugfix: Missing janitor access restrictions have been added to Birdshot's custodial
+ closet doors.
+ EnterTheJake:
+ - balance: Jetpacks are now briefly disabled by EMP.
+ - balance: Mirage module has been removed from Sec modsuits.
+ - balance: Security modsuits now spawn with a better cell and the jetpack module
+ preinstalled.
+ FernandoJ8:
+ - bugfix: humanoid species featured are randomized correctly once again
+ Ghommie:
+ - balance: Bait quality now influences the probabilities of getting a rarer fish
+ compared to the most common one(s)
+ GoblinBackwards:
+ - bugfix: Fixed missing wires leading to the port hallway APC on Birdshot.
+ Inari-Whitebear:
+ - bugfix: Fixed space heater heating power and power consumption
+ JackEnoff:
+ - bugfix: Welding helmet and hardhat will now properly protect against pepperspray
+ while its down
+ JohnFulpWillard:
+ - qol: You can now use a spoon or ladle on an ice cream vat to spill a reagent,
+ ridding the machine of all reagent of that type.
+ Jolly:
+ - code_imp: Behind the scenes, atmos machines (freezers/mixers) in maps were tweaked
+ a bit. If you see them no longer connected to specific pipenets, please make
+ an issue report, this is not intended behavior!!
+ Kocma-san:
+ - qol: the disposal unit has been added to the cargo bay and miner's office.
+ - bugfix: missed disposal pipe returned at QM's office
+ LemonInTheDark:
+ - rscadd: Most door animations now better line up with when they are/are not passable.
+ Rhials:
+ - admin: Narrate verbs will now allow you to pick what text formatting span you
+ want to use before you send them, if any.
+ - bugfix: Certain items can no longer be purchased or be offered with a discount
+ on a loneop uplink. This is stuff like base cards (functionally useless) or
+ Overwatch Intel Agents (which break the role).
+ - balance: Loneops may now purchase chameleon noslips, station blackout triggers,
+ and station comms failure triggers.
+ SmArtKar:
+ - bugfix: Blood filters should filter out reagents completely now instead of leaving
+ a small amount no matter what.
+ - image: You can now flip your security helmet's visor up with alt-click! However,
+ doing so will (obviously) expose your eyes.
+ - bugfix: Embedding now properly changes its values.
+ - bugfix: Bloody footprints no longer bloody your shoes even more when walked over.
+ SyncIt21:
+ - bugfix: paintings can be sponsored again
+ - bugfix: hydroponic trays take in all reagents "proportionally" from plumbing ducts
+ without leaving any behind
+ - bugfix: plumbing bottler pumps out all reagents "proportionally" into output beakers
+ Xackii:
+ - balance: 'Demon: bloodcrawl now deal damage when you using it.'
+ - balance: 'Demon: demon can eat only carbon human beings.'
+ Xander3359:
+ - bugfix: fixed CNS rebooter/Changeling adrenaline not preventing/fixing stamina
+ crit
+ carlarctg:
+ - qol: Using a multitool inhand tells you where the area APC is
+ - code_imp: Added the subtype vendor which lets admins and coders vend subtypes
+ of a path
+ - code_imp: Added the vendor and both varieties of omnitool to runtime station
+ grungussuss:
+ - sound: pen click now has a proper sound
+ - sound: scalpel cutting sound now has a clean sound.
+ - bugfix: fixed an unrestricted airlock in metastation brig maints
+ grungussuss, ported from Beestation:
+ - sound: abductee, hypnotized and brainwashed now have sound when becoming one.
+ mc-oofert:
+ - qol: blood bros get a hud to see eachother
+ - code_imp: teambased antagonists may not see the HUDs of other teams
+ rageguy505:
+ - bugfix: no more xenobio active turfs
diff --git a/icons/effects/96x96.dmi b/icons/effects/96x96.dmi
index 31f26c3e6e11e..38921a6e48402 100644
Binary files a/icons/effects/96x96.dmi and b/icons/effects/96x96.dmi differ
diff --git a/icons/effects/beam.dmi b/icons/effects/beam.dmi
index fd7bee8ed6075..ae668b0495a56 100644
Binary files a/icons/effects/beam.dmi and b/icons/effects/beam.dmi differ
diff --git a/icons/effects/bitrunning.dmi b/icons/effects/bitrunning.dmi
index 8efa429389c3a..9a2e9c0228a57 100644
Binary files a/icons/effects/bitrunning.dmi and b/icons/effects/bitrunning.dmi differ
diff --git a/icons/effects/effects.dmi b/icons/effects/effects.dmi
index 67b8ea51c1850..1b6e8d68e3682 100644
Binary files a/icons/effects/effects.dmi and b/icons/effects/effects.dmi differ
diff --git a/icons/effects/fields.dmi b/icons/effects/fields.dmi
index f87e1f3975e86..1e42870787674 100644
Binary files a/icons/effects/fields.dmi and b/icons/effects/fields.dmi differ
diff --git a/icons/effects/mapping_helpers.dmi b/icons/effects/mapping_helpers.dmi
index ad09351e787f6..47684f4664e89 100644
Binary files a/icons/effects/mapping_helpers.dmi and b/icons/effects/mapping_helpers.dmi differ
diff --git a/icons/effects/random_spawners.dmi b/icons/effects/random_spawners.dmi
index ed6c0c8702e45..4e5608330030b 100644
Binary files a/icons/effects/random_spawners.dmi and b/icons/effects/random_spawners.dmi differ
diff --git a/icons/mob/actions/actions_mecha.dmi b/icons/mob/actions/actions_mecha.dmi
index 7c659ca3b573c..60df1b5ed1ae5 100644
Binary files a/icons/mob/actions/actions_mecha.dmi and b/icons/mob/actions/actions_mecha.dmi differ
diff --git a/icons/mob/clothing/back.dmi b/icons/mob/clothing/back.dmi
index eb3f87d9c71f0..be83d83d4228c 100644
Binary files a/icons/mob/clothing/back.dmi and b/icons/mob/clothing/back.dmi differ
diff --git a/icons/mob/clothing/eyes.dmi b/icons/mob/clothing/eyes.dmi
index 20bf4d18381fd..92b155f62d547 100644
Binary files a/icons/mob/clothing/eyes.dmi and b/icons/mob/clothing/eyes.dmi differ
diff --git a/icons/mob/clothing/hands.dmi b/icons/mob/clothing/hands.dmi
index a670c33b7dcf6..ded01542e31b6 100644
Binary files a/icons/mob/clothing/hands.dmi and b/icons/mob/clothing/hands.dmi differ
diff --git a/icons/mob/clothing/head/helmet.dmi b/icons/mob/clothing/head/helmet.dmi
index 8b29c935f5ea7..116f0256a6276 100644
Binary files a/icons/mob/clothing/head/helmet.dmi and b/icons/mob/clothing/head/helmet.dmi differ
diff --git a/icons/mob/clothing/head/spacehelm.dmi b/icons/mob/clothing/head/spacehelm.dmi
index 0b9f3d4a36eb4..d9a634a63c771 100644
Binary files a/icons/mob/clothing/head/spacehelm.dmi and b/icons/mob/clothing/head/spacehelm.dmi differ
diff --git a/icons/mob/clothing/suits/jacket.dmi b/icons/mob/clothing/suits/jacket.dmi
index fa93a85c7c6f1..869966528b2e9 100644
Binary files a/icons/mob/clothing/suits/jacket.dmi and b/icons/mob/clothing/suits/jacket.dmi differ
diff --git a/icons/mob/clothing/suits/labcoat.dmi b/icons/mob/clothing/suits/labcoat.dmi
index 37cb0e8696bdb..7edbf91840518 100644
Binary files a/icons/mob/clothing/suits/labcoat.dmi and b/icons/mob/clothing/suits/labcoat.dmi differ
diff --git a/icons/mob/clothing/suits/spacesuit.dmi b/icons/mob/clothing/suits/spacesuit.dmi
index 0067c4bf36a8c..1f691729a3031 100644
Binary files a/icons/mob/clothing/suits/spacesuit.dmi and b/icons/mob/clothing/suits/spacesuit.dmi differ
diff --git a/icons/mob/inhands/clothing/gloves_lefthand.dmi b/icons/mob/inhands/clothing/gloves_lefthand.dmi
index 6277d146ffc3d..4d191e42939b7 100644
Binary files a/icons/mob/inhands/clothing/gloves_lefthand.dmi and b/icons/mob/inhands/clothing/gloves_lefthand.dmi differ
diff --git a/icons/mob/inhands/clothing/gloves_righthand.dmi b/icons/mob/inhands/clothing/gloves_righthand.dmi
index 7479dd8c505cc..f8ce306cc9850 100644
Binary files a/icons/mob/inhands/clothing/gloves_righthand.dmi and b/icons/mob/inhands/clothing/gloves_righthand.dmi differ
diff --git a/icons/mob/inhands/items_lefthand.dmi b/icons/mob/inhands/items_lefthand.dmi
index 5515ad69c3486..86bd5020f4f32 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 fdad955fd9a6f..a6de8b974644c 100644
Binary files a/icons/mob/inhands/items_righthand.dmi and b/icons/mob/inhands/items_righthand.dmi differ
diff --git a/icons/mob/rideables/mech_construct.dmi b/icons/mob/rideables/mech_construct.dmi
index c25eb971f2e36..656322b8187a7 100644
Binary files a/icons/mob/rideables/mech_construct.dmi and b/icons/mob/rideables/mech_construct.dmi differ
diff --git a/icons/mob/rideables/mech_construction.dmi b/icons/mob/rideables/mech_construction.dmi
index f26dbe17fd036..6e1cdbc3a2d4a 100644
Binary files a/icons/mob/rideables/mech_construction.dmi and b/icons/mob/rideables/mech_construction.dmi differ
diff --git a/icons/mob/rideables/mecha.dmi b/icons/mob/rideables/mecha.dmi
index 0c0f62de4d26e..5960d05413781 100644
Binary files a/icons/mob/rideables/mecha.dmi and b/icons/mob/rideables/mecha.dmi differ
diff --git a/icons/mob/rideables/vehicles.dmi b/icons/mob/rideables/vehicles.dmi
index 7e2b8a05f204c..08923668d2663 100644
Binary files a/icons/mob/rideables/vehicles.dmi and b/icons/mob/rideables/vehicles.dmi differ
diff --git a/icons/obj/antags/eldritch.dmi b/icons/obj/antags/eldritch.dmi
index 7f6af6bfe2e65..664311e5c7c47 100644
Binary files a/icons/obj/antags/eldritch.dmi and b/icons/obj/antags/eldritch.dmi differ
diff --git a/icons/obj/canisters.dmi b/icons/obj/canisters.dmi
index 436467648880b..1555cf0a4782e 100644
Binary files a/icons/obj/canisters.dmi and b/icons/obj/canisters.dmi differ
diff --git a/icons/obj/card.dmi b/icons/obj/card.dmi
index 95453cb46edc8..e26731f384667 100644
Binary files a/icons/obj/card.dmi and b/icons/obj/card.dmi differ
diff --git a/icons/obj/clothing/glasses.dmi b/icons/obj/clothing/glasses.dmi
index fd898d3105fd8..97f692551f9e3 100644
Binary files a/icons/obj/clothing/glasses.dmi and b/icons/obj/clothing/glasses.dmi differ
diff --git a/icons/obj/clothing/gloves.dmi b/icons/obj/clothing/gloves.dmi
index d5099c64e7f65..465340870dd48 100644
Binary files a/icons/obj/clothing/gloves.dmi and b/icons/obj/clothing/gloves.dmi differ
diff --git a/icons/obj/clothing/head/helmet.dmi b/icons/obj/clothing/head/helmet.dmi
index 4d43b542051fc..fe52d505e439d 100644
Binary files a/icons/obj/clothing/head/helmet.dmi and b/icons/obj/clothing/head/helmet.dmi differ
diff --git a/icons/obj/clothing/head/spacehelm.dmi b/icons/obj/clothing/head/spacehelm.dmi
index c2830a9d9335f..980d2a07a50be 100644
Binary files a/icons/obj/clothing/head/spacehelm.dmi and b/icons/obj/clothing/head/spacehelm.dmi differ
diff --git a/icons/obj/clothing/modsuit/mod_modules.dmi b/icons/obj/clothing/modsuit/mod_modules.dmi
index 8b4b549eda2ae..6ec143a849e1c 100644
Binary files a/icons/obj/clothing/modsuit/mod_modules.dmi and b/icons/obj/clothing/modsuit/mod_modules.dmi differ
diff --git a/icons/obj/clothing/suits/jacket.dmi b/icons/obj/clothing/suits/jacket.dmi
index 30e1a99d9eed4..b867e7da3a710 100644
Binary files a/icons/obj/clothing/suits/jacket.dmi and b/icons/obj/clothing/suits/jacket.dmi differ
diff --git a/icons/obj/clothing/suits/labcoat.dmi b/icons/obj/clothing/suits/labcoat.dmi
index 430d11d5f96ab..4d5796a12b856 100644
Binary files a/icons/obj/clothing/suits/labcoat.dmi and b/icons/obj/clothing/suits/labcoat.dmi differ
diff --git a/icons/obj/clothing/suits/spacesuit.dmi b/icons/obj/clothing/suits/spacesuit.dmi
index 922ad05c35e7a..7fe01246352d6 100644
Binary files a/icons/obj/clothing/suits/spacesuit.dmi and b/icons/obj/clothing/suits/spacesuit.dmi differ
diff --git a/icons/obj/devices/artefacts.dmi b/icons/obj/devices/artefacts.dmi
new file mode 100644
index 0000000000000..75a6f754fbbed
Binary files /dev/null and b/icons/obj/devices/artefacts.dmi differ
diff --git a/icons/obj/doors/blastdoor.dmi b/icons/obj/doors/blastdoor.dmi
index 9457690cda365..9c7bec103cefa 100644
Binary files a/icons/obj/doors/blastdoor.dmi and b/icons/obj/doors/blastdoor.dmi differ
diff --git a/icons/obj/doors/doorfireglass.dmi b/icons/obj/doors/doorfireglass.dmi
index 250b5ca833a89..83156ae4351e1 100644
Binary files a/icons/obj/doors/doorfireglass.dmi and b/icons/obj/doors/doorfireglass.dmi differ
diff --git a/icons/obj/doors/doormorgue.dmi b/icons/obj/doors/doormorgue.dmi
index c0ed1147325ce..381e5c3385ef3 100644
Binary files a/icons/obj/doors/doormorgue.dmi and b/icons/obj/doors/doormorgue.dmi differ
diff --git a/icons/obj/doors/puzzledoor/danger.dmi b/icons/obj/doors/puzzledoor/danger.dmi
index 89d19131cc189..ed78272037e37 100644
Binary files a/icons/obj/doors/puzzledoor/danger.dmi and b/icons/obj/doors/puzzledoor/danger.dmi differ
diff --git a/icons/obj/doors/puzzledoor/default.dmi b/icons/obj/doors/puzzledoor/default.dmi
index 49a9206139580..685fc448bdee0 100644
Binary files a/icons/obj/doors/puzzledoor/default.dmi and b/icons/obj/doors/puzzledoor/default.dmi differ
diff --git a/icons/obj/doors/puzzledoor/wood.dmi b/icons/obj/doors/puzzledoor/wood.dmi
index f2b53e857393b..58717aacf1e65 100644
Binary files a/icons/obj/doors/puzzledoor/wood.dmi and b/icons/obj/doors/puzzledoor/wood.dmi differ
diff --git a/icons/obj/doors/shutters.dmi b/icons/obj/doors/shutters.dmi
index 1cc727cdbf7a4..5fd2b2a8213d5 100644
Binary files a/icons/obj/doors/shutters.dmi and b/icons/obj/doors/shutters.dmi differ
diff --git a/icons/obj/doors/shutters_radiation.dmi b/icons/obj/doors/shutters_radiation.dmi
index 2e70b24a4aa2a..37b8cf72cbca0 100644
Binary files a/icons/obj/doors/shutters_radiation.dmi and b/icons/obj/doors/shutters_radiation.dmi differ
diff --git a/icons/obj/doors/shutters_window.dmi b/icons/obj/doors/shutters_window.dmi
index fcf8af5b94fd7..b86fe675366c1 100644
Binary files a/icons/obj/doors/shutters_window.dmi and b/icons/obj/doors/shutters_window.dmi differ
diff --git a/icons/obj/doors/windoor.dmi b/icons/obj/doors/windoor.dmi
index 86893eb9c453c..117cd78e1348b 100644
Binary files a/icons/obj/doors/windoor.dmi and b/icons/obj/doors/windoor.dmi differ
diff --git a/icons/obj/food/containers.dmi b/icons/obj/food/containers.dmi
index 6c2eda1986633..f20b54d9c31f0 100644
Binary files a/icons/obj/food/containers.dmi and b/icons/obj/food/containers.dmi differ
diff --git a/icons/obj/meteor.dmi b/icons/obj/meteor.dmi
index 9fde3f3ef68ea..6b47fe485c34b 100644
Binary files a/icons/obj/meteor.dmi and b/icons/obj/meteor.dmi differ
diff --git a/icons/obj/meteor_spooky.dmi b/icons/obj/meteor_spooky.dmi
deleted file mode 100644
index 287d5b47bd0f7..0000000000000
Binary files a/icons/obj/meteor_spooky.dmi and /dev/null differ
diff --git a/icons/obj/pipes_n_cables/atmos.dmi b/icons/obj/pipes_n_cables/atmos.dmi
index 91badbf3ccf9b..fc67ff54158f9 100644
Binary files a/icons/obj/pipes_n_cables/atmos.dmi and b/icons/obj/pipes_n_cables/atmos.dmi differ
diff --git a/icons/obj/pipes_n_cables/hydrochem/plumbers.dmi b/icons/obj/pipes_n_cables/hydrochem/plumbers.dmi
index 555b6c6328b02..2f10725a0a8f3 100644
Binary files a/icons/obj/pipes_n_cables/hydrochem/plumbers.dmi and b/icons/obj/pipes_n_cables/hydrochem/plumbers.dmi differ
diff --git a/icons/obj/science/vatgrowing.dmi b/icons/obj/science/vatgrowing.dmi
index 646aad055cc3c..ec8b5dcb6acd9 100644
Binary files a/icons/obj/science/vatgrowing.dmi and b/icons/obj/science/vatgrowing.dmi differ
diff --git a/icons/obj/signs.dmi b/icons/obj/signs.dmi
index a2069ba5d9d3b..050a609470421 100644
Binary files a/icons/obj/signs.dmi and b/icons/obj/signs.dmi differ
diff --git a/icons/obj/stack_objects.dmi b/icons/obj/stack_objects.dmi
index b4617915e86ed..f13b10bdf4a8a 100644
Binary files a/icons/obj/stack_objects.dmi and b/icons/obj/stack_objects.dmi differ
diff --git a/icons/obj/storage/case.dmi b/icons/obj/storage/case.dmi
index a47c86eea9fb3..a29fd3a6f37c0 100644
Binary files a/icons/obj/storage/case.dmi and b/icons/obj/storage/case.dmi differ
diff --git a/icons/obj/storage/crates.dmi b/icons/obj/storage/crates.dmi
index 0a8640860eced..34d5db6f3e898 100644
Binary files a/icons/obj/storage/crates.dmi and b/icons/obj/storage/crates.dmi differ
diff --git a/icons/obj/structures.dmi b/icons/obj/structures.dmi
index c7b38cb6d1aab..becab20e591de 100644
Binary files a/icons/obj/structures.dmi and b/icons/obj/structures.dmi differ
diff --git a/icons/obj/weapons/bows/arrows.dmi b/icons/obj/weapons/bows/arrows.dmi
index 4453d4ca5f247..956a82dd9bf1f 100644
Binary files a/icons/obj/weapons/bows/arrows.dmi and b/icons/obj/weapons/bows/arrows.dmi differ
diff --git a/icons/obj/weapons/bows/bows.dmi b/icons/obj/weapons/bows/bows.dmi
index 81b2fc4822058..4de9d4fca35ab 100644
Binary files a/icons/obj/weapons/bows/bows.dmi and b/icons/obj/weapons/bows/bows.dmi differ
diff --git a/icons/obj/weapons/guns/energy.dmi b/icons/obj/weapons/guns/energy.dmi
index 5d607026f0133..6e8e5c60f684a 100644
Binary files a/icons/obj/weapons/guns/energy.dmi and b/icons/obj/weapons/guns/energy.dmi differ
diff --git a/icons/obj/weapons/guns/projectiles.dmi b/icons/obj/weapons/guns/projectiles.dmi
index a13ebcc636009..3be26b2c43fad 100644
Binary files a/icons/obj/weapons/guns/projectiles.dmi and b/icons/obj/weapons/guns/projectiles.dmi differ
diff --git a/icons/ui_icons/antags/heretic/knowledge.dmi b/icons/ui_icons/antags/heretic/knowledge.dmi
new file mode 100644
index 0000000000000..d24de1a5f0e81
Binary files /dev/null and b/icons/ui_icons/antags/heretic/knowledge.dmi differ
diff --git a/modular_bandastation/translations/code/moustache.dm b/modular_bandastation/translations/code/moustache.dm
index b18ca9708971d..d6b52a275885f 100644
--- a/modular_bandastation/translations/code/moustache.dm
+++ b/modular_bandastation/translations/code/moustache.dm
@@ -6,32 +6,12 @@
name = "итальянские усы"
desc = "Изготовлен из настоящих итальянских волосков для усов. Дает владельцу непреодолимое желание дико жестикулировать."
-/obj/item/clothing/mask/fakemoustache/italian/handle_speech(datum/source, list/speech_args)
- var/message = speech_args[SPEECH_MESSAGE]
- if(message[1] != "*")
- var/static/regex/words = new(@"(?
+
+
+ {THEMES.map((THEME) => (
+
+ ))}
+
+
+
+ {!freeFont ? (
+ {
+ setFreeFont(!freeFont);
+ }}
+ >
+ Custom font
+
+ }
+ >
+ {FONTS.map((FONT) => (
+
+ ))}
+
+ ) : (
+
+
+ dispatch(
+ updateSettings({
+ fontFamily: value,
+ }),
+ )
+ }
+ />
+
+
+ )}
+
+
+
+ toFixed(value)}
+ onChange={(value) =>
+ dispatch(
+ updateSettings({
+ fontSize: value,
+ }),
+ )
+ }
+ />
+
+
+ toFixed(value, 2)}
+ onDrag={(value) =>
+ dispatch(
+ updateSettings({
+ lineHeight: value,
+ }),
+ )
+ }
+ />
+
+
+
+
+
+
+
+
+ dispatch(clearChat())}
+ >
+ Clear chat
+
+
+
+
+ );
+}
diff --git a/tgui/packages/tgui-panel/settings/SettingsPanel.jsx b/tgui/packages/tgui-panel/settings/SettingsPanel.jsx
deleted file mode 100644
index e5691037bba68..0000000000000
--- a/tgui/packages/tgui-panel/settings/SettingsPanel.jsx
+++ /dev/null
@@ -1,371 +0,0 @@
-/**
- * @file
- * @copyright 2020 Aleksej Komarov
- * @license MIT
- */
-
-import { toFixed } from 'common/math';
-import { capitalize } from 'common/string';
-import { useLocalState } from 'tgui/backend';
-import { useDispatch, useSelector } from 'tgui/backend';
-import {
- Box,
- Button,
- Collapsible,
- ColorBox,
- Divider,
- Input,
- LabeledList,
- NumberInput,
- Section,
- Stack,
- Tabs,
- TextArea,
-} from 'tgui/components';
-
-import { ChatPageSettings } from '../chat';
-import { clearChat, rebuildChat, saveChatToDisk } from '../chat/actions';
-import { THEMES } from '../themes';
-import {
- addHighlightSetting,
- changeSettingsTab,
- removeHighlightSetting,
- updateHighlightSetting,
- updateSettings,
-} from './actions';
-import { FONTS, MAX_HIGHLIGHT_SETTINGS, SETTINGS_TABS } from './constants';
-import {
- selectActiveTab,
- selectHighlightSettingById,
- selectHighlightSettings,
- selectSettings,
-} from './selectors';
-
-export const SettingsPanel = (props) => {
- const activeTab = useSelector(selectActiveTab);
- const dispatch = useDispatch();
- return (
-
-
-
-
- {SETTINGS_TABS.map((tab) => (
-
- dispatch(
- changeSettingsTab({
- tabId: tab.id,
- }),
- )
- }
- >
- {tab.name}
-
- ))}
-
-
-
-
- {activeTab === 'general' && }
- {activeTab === 'chatPage' && }
- {activeTab === 'textHighlight' && }
-
-
- );
-};
-
-export const SettingsGeneral = (props) => {
- const { theme, fontFamily, fontSize, lineHeight } =
- useSelector(selectSettings);
- const dispatch = useDispatch();
- const [freeFont, setFreeFont] = useLocalState('freeFont', false);
- return (
-
-
-
- {THEMES.map((THEME) => (
-
-
-
- {(!freeFont && (
- {
- setFreeFont(!freeFont);
- }}
- />
- }
- >
- {FONTS.map((FONT) => (
-
- )) || (
-
-
- dispatch(
- updateSettings({
- fontFamily: value,
- }),
- )
- }
- />
-
- )}
-
-
-
- toFixed(value)}
- onChange={(value) =>
- dispatch(
- updateSettings({
- fontSize: value,
- }),
- )
- }
- />
-
-
- toFixed(value, 2)}
- onDrag={(value) =>
- dispatch(
- updateSettings({
- lineHeight: value,
- }),
- )
- }
- />
-
-
-
-
-
-
-
- dispatch(clearChat())}
- />
-
-
-
- );
-};
-
-const TextHighlightSettings = (props) => {
- const highlightSettings = useSelector(selectHighlightSettings);
- const dispatch = useDispatch();
- return (
-
-
- {highlightSettings.map((id, i) => (
-
- ))}
- {highlightSettings.length < MAX_HIGHLIGHT_SETTINGS && (
-
-
- )}
-
-
-
-
-
- Can freeze the chat for a while.
-
-
-
- );
-};
-
-const TextHighlightSetting = (props) => {
- const { id, ...rest } = props;
- const highlightSettingById = useSelector(selectHighlightSettingById);
- const dispatch = useDispatch();
- const {
- highlightColor,
- highlightText,
- highlightWholeMessage,
- matchWord,
- matchCase,
- } = highlightSettingById[id];
- return (
-
-
-
-
-
-
- dispatch(
- updateHighlightSetting({
- id: id,
- highlightWholeMessage: !highlightWholeMessage,
- }),
- )
- }
- />
-
-
-
- dispatch(
- updateHighlightSetting({
- id: id,
- matchWord: !matchWord,
- }),
- )
- }
- />
-
-
-
- dispatch(
- updateHighlightSetting({
- id: id,
- matchCase: !matchCase,
- }),
- )
- }
- />
-
-
-
-
- dispatch(
- updateHighlightSetting({
- id: id,
- highlightColor: value,
- }),
- )
- }
- />
-
-
-
- );
-};
diff --git a/tgui/packages/tgui-panel/settings/SettingsPanel.tsx b/tgui/packages/tgui-panel/settings/SettingsPanel.tsx
new file mode 100644
index 0000000000000..abb0ef86f6335
--- /dev/null
+++ b/tgui/packages/tgui-panel/settings/SettingsPanel.tsx
@@ -0,0 +1,51 @@
+/**
+ * @file
+ * @copyright 2020 Aleksej Komarov
+ * @license MIT
+ */
+
+import { useDispatch, useSelector } from 'tgui/backend';
+import { Section, Stack, Tabs } from 'tgui/components';
+
+import { ChatPageSettings } from '../chat';
+import { changeSettingsTab } from './actions';
+import { SETTINGS_TABS } from './constants';
+import { selectActiveTab } from './selectors';
+import { SettingsGeneral } from './SettingsGeneral';
+import { TextHighlightSettings } from './TextHighlight';
+
+export function SettingsPanel(props) {
+ const activeTab = useSelector(selectActiveTab);
+ const dispatch = useDispatch();
+
+ return (
+
+
+
+
+ {SETTINGS_TABS.map((tab) => (
+
+ dispatch(
+ changeSettingsTab({
+ tabId: tab.id,
+ }),
+ )
+ }
+ >
+ {tab.name}
+
+ ))}
+
+
+
+
+ {activeTab === 'general' && }
+ {activeTab === 'chatPage' && }
+ {activeTab === 'textHighlight' && }
+
+
+ );
+}
diff --git a/tgui/packages/tgui-panel/settings/TextHighlight.tsx b/tgui/packages/tgui-panel/settings/TextHighlight.tsx
new file mode 100644
index 0000000000000..76512cf3770f6
--- /dev/null
+++ b/tgui/packages/tgui-panel/settings/TextHighlight.tsx
@@ -0,0 +1,178 @@
+import { useDispatch, useSelector } from 'tgui/backend';
+import {
+ Box,
+ Button,
+ ColorBox,
+ Divider,
+ Input,
+ Section,
+ Stack,
+ TextArea,
+} from 'tgui/components';
+
+import { rebuildChat } from '../chat/actions';
+import {
+ addHighlightSetting,
+ removeHighlightSetting,
+ updateHighlightSetting,
+} from './actions';
+import { MAX_HIGHLIGHT_SETTINGS } from './constants';
+import {
+ selectHighlightSettingById,
+ selectHighlightSettings,
+} from './selectors';
+
+export function TextHighlightSettings(props) {
+ const highlightSettings = useSelector(selectHighlightSettings);
+ const dispatch = useDispatch();
+
+ return (
+
+
+ {highlightSettings.map((id, i) => (
+
+ ))}
+ {highlightSettings.length < MAX_HIGHLIGHT_SETTINGS && (
+
+
+
+ )}
+
+
+
+
+
+ Can freeze the chat for a while.
+
+
+
+ );
+}
+
+function TextHighlightSetting(props) {
+ const { id, ...rest } = props;
+ const highlightSettingById = useSelector(selectHighlightSettingById);
+ const dispatch = useDispatch();
+ const {
+ highlightColor,
+ highlightText,
+ highlightWholeMessage,
+ matchWord,
+ matchCase,
+ } = highlightSettingById[id];
+
+ return (
+
+
+
+
+
+
+
+ dispatch(
+ updateHighlightSetting({
+ id: id,
+ highlightWholeMessage: !highlightWholeMessage,
+ }),
+ )
+ }
+ >
+ Whole Message
+
+
+
+
+ dispatch(
+ updateHighlightSetting({
+ id: id,
+ matchWord: !matchWord,
+ }),
+ )
+ }
+ >
+ Exact
+
+
+
+
+ dispatch(
+ updateHighlightSetting({
+ id: id,
+ matchCase: !matchCase,
+ }),
+ )
+ }
+ >
+ Case
+
+
+
+
+
+ dispatch(
+ updateHighlightSetting({
+ id: id,
+ highlightColor: value,
+ }),
+ )
+ }
+ />
+
+
+
+ );
+}
diff --git a/tgui/packages/tgui-panel/settings/middleware.js b/tgui/packages/tgui-panel/settings/middleware.js
deleted file mode 100644
index edb16a51c4c41..0000000000000
--- a/tgui/packages/tgui-panel/settings/middleware.js
+++ /dev/null
@@ -1,90 +0,0 @@
-/**
- * @file
- * @copyright 2020 Aleksej Komarov
- * @license MIT
- */
-
-import { storage } from 'common/storage';
-
-import { setClientTheme } from '../themes';
-import {
- addHighlightSetting,
- loadSettings,
- removeHighlightSetting,
- updateHighlightSetting,
- updateSettings,
-} from './actions';
-import { FONTS_DISABLED } from './constants';
-import { selectSettings } from './selectors';
-
-let overrideRule = null;
-let overrideFontFamily = null;
-let overrideFontSize = null;
-
-const updateGlobalOverrideRule = () => {
- let fontFamily = '';
-
- if (overrideFontFamily !== null) {
- fontFamily = `font-family: ${overrideFontFamily} !important;`;
- }
-
- const constructedRule = `body * :not(.Icon) {
- ${fontFamily}
- }`;
-
- if (overrideRule === null) {
- overrideRule = document.createElement('style');
- document.querySelector('head').append(overrideRule);
- }
-
- // no other way to force a CSS refresh other than to update its innerText
- overrideRule.innerText = constructedRule;
-
- document.body.style.setProperty('font-size', overrideFontSize);
-};
-
-const setGlobalFontSize = (fontSize) => {
- overrideFontSize = `${fontSize}px`;
-};
-
-const setGlobalFontFamily = (fontFamily) => {
- if (fontFamily === FONTS_DISABLED) fontFamily = null;
- overrideFontFamily = fontFamily;
-};
-
-export const settingsMiddleware = (store) => {
- let initialized = false;
- return (next) => (action) => {
- const { type, payload } = action;
- if (!initialized) {
- initialized = true;
- storage.get('panel-settings').then((settings) => {
- store.dispatch(loadSettings(settings));
- });
- }
- if (
- type === updateSettings.type ||
- type === loadSettings.type ||
- type === addHighlightSetting.type ||
- type === removeHighlightSetting.type ||
- type === updateHighlightSetting.type
- ) {
- // Set client theme
- const theme = payload?.theme;
- if (theme) {
- setClientTheme(theme);
- }
- // Pass action to get an updated state
- next(action);
- const settings = selectSettings(store.getState());
- // Update global UI font size
- setGlobalFontSize(settings.fontSize);
- setGlobalFontFamily(settings.fontFamily);
- updateGlobalOverrideRule();
- // Save settings to the web storage
- storage.set('panel-settings', settings);
- return;
- }
- return next(action);
- };
-};
diff --git a/tgui/packages/tgui-panel/settings/middleware.ts b/tgui/packages/tgui-panel/settings/middleware.ts
new file mode 100644
index 0000000000000..01ad88561904f
--- /dev/null
+++ b/tgui/packages/tgui-panel/settings/middleware.ts
@@ -0,0 +1,98 @@
+/**
+ * @file
+ * @copyright 2020 Aleksej Komarov
+ * @license MIT
+ */
+
+import { storage } from 'common/storage';
+
+import { setClientTheme } from '../themes';
+import {
+ addHighlightSetting,
+ loadSettings,
+ removeHighlightSetting,
+ updateHighlightSetting,
+ updateSettings,
+} from './actions';
+import { FONTS_DISABLED } from './constants';
+import { selectSettings } from './selectors';
+
+let overrideRule: HTMLStyleElement;
+let overrideFontFamily: string | undefined;
+let overrideFontSize: string;
+
+/** Updates the global CSS rule to override the font family and size. */
+function updateGlobalOverrideRule() {
+ let fontFamily = '';
+
+ if (overrideFontFamily !== undefined) {
+ fontFamily = `font-family: ${overrideFontFamily} !important;`;
+ }
+
+ const constructedRule = `body * :not(.Icon) {
+ ${fontFamily}
+ }`;
+
+ if (overrideRule === undefined) {
+ overrideRule = document.createElement('style');
+ document.querySelector('head')!.append(overrideRule);
+ }
+
+ // no other way to force a CSS refresh other than to update its innerText
+ overrideRule.innerText = constructedRule;
+
+ document.body.style.setProperty('font-size', overrideFontSize);
+}
+
+function setGlobalFontSize(fontSize: string) {
+ overrideFontSize = `${fontSize}px`;
+}
+
+function setGlobalFontFamily(fontFamily: string) {
+ overrideFontFamily = fontFamily === FONTS_DISABLED ? undefined : fontFamily;
+}
+
+export function settingsMiddleware(store) {
+ let initialized = false;
+
+ return (next) => (action) => {
+ const { type, payload } = action;
+
+ if (!initialized) {
+ initialized = true;
+ storage.get('panel-settings').then((settings) => {
+ store.dispatch(loadSettings(settings));
+ });
+ }
+ if (
+ type !== updateSettings.type &&
+ type !== loadSettings.type &&
+ type !== addHighlightSetting.type &&
+ type !== removeHighlightSetting.type &&
+ type !== updateHighlightSetting.type
+ ) {
+ return next(action);
+ }
+
+ // Set client theme
+ const theme = payload?.theme;
+ if (theme) {
+ setClientTheme(theme);
+ }
+
+ // Pass action to get an updated state
+ next(action);
+
+ const settings = selectSettings(store.getState());
+
+ // Update global UI font size
+ setGlobalFontSize(settings.fontSize);
+ setGlobalFontFamily(settings.fontFamily);
+ updateGlobalOverrideRule();
+
+ // Save settings to the web storage
+ storage.set('panel-settings', settings);
+
+ return;
+ };
+}
diff --git a/tgui/packages/tgui-panel/settings/reducer.js b/tgui/packages/tgui-panel/settings/reducer.js
deleted file mode 100644
index 1240198df8a3d..0000000000000
--- a/tgui/packages/tgui-panel/settings/reducer.js
+++ /dev/null
@@ -1,184 +0,0 @@
-/**
- * @file
- * @copyright 2020 Aleksej Komarov
- * @license MIT
- */
-
-import {
- addHighlightSetting,
- changeSettingsTab,
- loadSettings,
- openChatSettings,
- removeHighlightSetting,
- toggleSettings,
- updateHighlightSetting,
- updateSettings,
-} from './actions';
-import { FONTS, MAX_HIGHLIGHT_SETTINGS, SETTINGS_TABS } from './constants';
-import { createDefaultHighlightSetting } from './model';
-
-const defaultHighlightSetting = createDefaultHighlightSetting();
-
-const initialState = {
- version: 1,
- fontSize: 13,
- fontFamily: FONTS[0],
- lineHeight: 1.2,
- theme: 'light',
- adminMusicVolume: 0.5,
- // Keep these two state vars for compatibility with other servers
- highlightText: '',
- highlightColor: '#ffdd44',
- // END compatibility state vars
- highlightSettings: [defaultHighlightSetting.id],
- highlightSettingById: {
- [defaultHighlightSetting.id]: defaultHighlightSetting,
- },
- view: {
- visible: false,
- activeTab: SETTINGS_TABS[0].id,
- },
-};
-
-export const settingsReducer = (state = initialState, action) => {
- const { type, payload } = action;
- if (type === updateSettings.type) {
- return {
- ...state,
- ...payload,
- };
- }
- if (type === loadSettings.type) {
- // Validate version and/or migrate state
- if (!payload?.version) {
- return state;
- }
-
- delete payload.view;
- const nextState = {
- ...state,
- ...payload,
- };
- // Lazy init the list for compatibility reasons
- if (!nextState.highlightSettings) {
- nextState.highlightSettings = [defaultHighlightSetting.id];
- nextState.highlightSettingById[defaultHighlightSetting.id] =
- defaultHighlightSetting;
- }
- // Compensating for mishandling of default highlight settings
- else if (!nextState.highlightSettingById[defaultHighlightSetting.id]) {
- nextState.highlightSettings = [
- defaultHighlightSetting.id,
- ...nextState.highlightSettings,
- ];
- nextState.highlightSettingById[defaultHighlightSetting.id] =
- defaultHighlightSetting;
- }
- // Update the highlight settings for default highlight
- // settings compatibility
- const highlightSetting =
- nextState.highlightSettingById[defaultHighlightSetting.id];
- highlightSetting.highlightColor = nextState.highlightColor;
- highlightSetting.highlightText = nextState.highlightText;
- return nextState;
- }
- if (type === toggleSettings.type) {
- return {
- ...state,
- view: {
- ...state.view,
- visible: !state.view.visible,
- },
- };
- }
- if (type === openChatSettings.type) {
- return {
- ...state,
- view: {
- ...state.view,
- visible: true,
- activeTab: 'chatPage',
- },
- };
- }
- if (type === changeSettingsTab.type) {
- const { tabId } = payload;
- return {
- ...state,
- view: {
- ...state.view,
- activeTab: tabId,
- },
- };
- }
- if (type === addHighlightSetting.type) {
- const highlightSetting = payload;
- if (state.highlightSettings.length >= MAX_HIGHLIGHT_SETTINGS) {
- return state;
- }
- return {
- ...state,
- highlightSettings: [...state.highlightSettings, highlightSetting.id],
- highlightSettingById: {
- ...state.highlightSettingById,
- [highlightSetting.id]: highlightSetting,
- },
- };
- }
- if (type === removeHighlightSetting.type) {
- const { id } = payload;
- const nextState = {
- ...state,
- highlightSettings: [...state.highlightSettings],
- highlightSettingById: {
- ...state.highlightSettingById,
- },
- };
- if (id === defaultHighlightSetting.id) {
- nextState.highlightSettings[defaultHighlightSetting.id] =
- defaultHighlightSetting;
- } else {
- delete nextState.highlightSettingById[id];
- nextState.highlightSettings = nextState.highlightSettings.filter(
- (sid) => sid !== id,
- );
- if (!nextState.highlightSettings.length) {
- nextState.highlightSettings.push(defaultHighlightSetting.id);
- nextState.highlightSettingById[defaultHighlightSetting.id] =
- defaultHighlightSetting;
- }
- }
- return nextState;
- }
- if (type === updateHighlightSetting.type) {
- const { id, ...settings } = payload;
- const nextState = {
- ...state,
- highlightSettings: [...state.highlightSettings],
- highlightSettingById: {
- ...state.highlightSettingById,
- },
- };
-
- // Transfer this data from the default highlight setting
- // so they carry over to other servers
- if (id === defaultHighlightSetting.id) {
- if (settings.highlightText) {
- nextState.highlightText = settings.highlightText;
- }
- if (settings.highlightColor) {
- nextState.highlightColor = settings.highlightColor;
- }
- }
-
- if (nextState.highlightSettingById[id]) {
- nextState.highlightSettingById[id] = {
- ...nextState.highlightSettingById[id],
- ...settings,
- };
- }
-
- return nextState;
- }
- return state;
-};
diff --git a/tgui/packages/tgui-panel/settings/reducer.ts b/tgui/packages/tgui-panel/settings/reducer.ts
new file mode 100644
index 0000000000000..62d08986a1479
--- /dev/null
+++ b/tgui/packages/tgui-panel/settings/reducer.ts
@@ -0,0 +1,208 @@
+/**
+ * @file
+ * @copyright 2020 Aleksej Komarov
+ * @license MIT
+ */
+
+import {
+ addHighlightSetting,
+ changeSettingsTab,
+ loadSettings,
+ openChatSettings,
+ removeHighlightSetting,
+ toggleSettings,
+ updateHighlightSetting,
+ updateSettings,
+} from './actions';
+import { FONTS, MAX_HIGHLIGHT_SETTINGS, SETTINGS_TABS } from './constants';
+import { createDefaultHighlightSetting } from './model';
+
+const defaultHighlightSetting = createDefaultHighlightSetting();
+
+const initialState = {
+ version: 1,
+ fontSize: 13,
+ fontFamily: FONTS[0],
+ lineHeight: 1.2,
+ theme: 'light',
+ adminMusicVolume: 0.5,
+ // Keep these two state vars for compatibility with other servers
+ highlightText: '',
+ highlightColor: '#ffdd44',
+ // END compatibility state vars
+ highlightSettings: [defaultHighlightSetting.id],
+ highlightSettingById: {
+ [defaultHighlightSetting.id]: defaultHighlightSetting,
+ },
+ view: {
+ visible: false,
+ activeTab: SETTINGS_TABS[0].id,
+ },
+} as const;
+
+export function settingsReducer(
+ state = initialState,
+ action: { type: string; payload: any },
+) {
+ const { type, payload } = action;
+
+ switch (type) {
+ case updateSettings.type:
+ return {
+ ...state,
+ ...payload,
+ };
+
+ case loadSettings.type: {
+ {
+ // Validate version and/or migrate state
+ if (!payload?.version) {
+ return state;
+ }
+
+ delete payload.view;
+ const nextState = {
+ ...state,
+ ...payload,
+ };
+ // Lazy init the list for compatibility reasons
+ if (!nextState.highlightSettings) {
+ nextState.highlightSettings = [defaultHighlightSetting.id];
+ nextState.highlightSettingById[defaultHighlightSetting.id] =
+ defaultHighlightSetting;
+ }
+ // Compensating for mishandling of default highlight settings
+ else if (!nextState.highlightSettingById[defaultHighlightSetting.id]) {
+ nextState.highlightSettings = [
+ defaultHighlightSetting.id,
+ ...nextState.highlightSettings,
+ ];
+ nextState.highlightSettingById[defaultHighlightSetting.id] =
+ defaultHighlightSetting;
+ }
+
+ // Update the highlight settings for default highlight
+ // settings compatibility
+ const highlightSetting =
+ nextState.highlightSettingById[defaultHighlightSetting.id];
+ highlightSetting.highlightColor = nextState.highlightColor;
+ highlightSetting.highlightText = nextState.highlightText;
+
+ return nextState;
+ }
+ }
+
+ case toggleSettings.type: {
+ return {
+ ...state,
+ view: {
+ ...state.view,
+ visible: !state.view.visible,
+ },
+ };
+ }
+
+ case openChatSettings.type: {
+ return {
+ ...state,
+ view: {
+ ...state.view,
+ visible: true,
+ activeTab: 'chatPage',
+ },
+ };
+ }
+
+ case changeSettingsTab.type: {
+ const { tabId } = payload;
+
+ return {
+ ...state,
+ view: {
+ ...state.view,
+ activeTab: tabId,
+ },
+ };
+ }
+
+ case addHighlightSetting.type: {
+ const highlightSetting = payload;
+
+ if (state.highlightSettings.length >= MAX_HIGHLIGHT_SETTINGS) {
+ return state;
+ }
+
+ return {
+ ...state,
+ highlightSettings: [...state.highlightSettings, highlightSetting.id],
+ highlightSettingById: {
+ ...state.highlightSettingById,
+ [highlightSetting.id]: highlightSetting,
+ },
+ };
+ }
+
+ case removeHighlightSetting.type: {
+ const { id } = payload;
+
+ const nextState = {
+ ...state,
+ highlightSettings: [...state.highlightSettings],
+ highlightSettingById: {
+ ...state.highlightSettingById,
+ },
+ };
+
+ if (id === defaultHighlightSetting.id) {
+ nextState.highlightSettings[defaultHighlightSetting.id] =
+ defaultHighlightSetting;
+ } else {
+ delete nextState.highlightSettingById[id];
+ nextState.highlightSettings = nextState.highlightSettings.filter(
+ (sid) => sid !== id,
+ );
+ if (!nextState.highlightSettings.length) {
+ nextState.highlightSettings.push(defaultHighlightSetting.id);
+ nextState.highlightSettingById[defaultHighlightSetting.id] =
+ defaultHighlightSetting;
+ }
+ }
+
+ return nextState;
+ }
+
+ case updateHighlightSetting.type: {
+ const { id, ...settings } = payload;
+
+ const nextState = {
+ ...state,
+ highlightSettings: [...state.highlightSettings],
+ highlightSettingById: {
+ ...state.highlightSettingById,
+ },
+ };
+
+ // Transfer this data from the default highlight setting
+ // so they carry over to other servers
+ if (id === defaultHighlightSetting.id) {
+ if (settings.highlightText) {
+ nextState.highlightText = settings.highlightText;
+ }
+ if (settings.highlightColor) {
+ nextState.highlightColor = settings.highlightColor;
+ }
+ }
+
+ if (nextState.highlightSettingById[id]) {
+ nextState.highlightSettingById[id] = {
+ ...nextState.highlightSettingById[id],
+ ...settings,
+ };
+ }
+
+ return nextState;
+ }
+ }
+
+ return state;
+}
diff --git a/tgui/packages/tgui/components/DmIcon.tsx b/tgui/packages/tgui/components/DmIcon.tsx
index fb6816576ac0c..bb785fc6e3968 100644
--- a/tgui/packages/tgui/components/DmIcon.tsx
+++ b/tgui/packages/tgui/components/DmIcon.tsx
@@ -29,7 +29,7 @@ type Props = {
/** Frame number. Default is 1 */
frame: number;
/** Movement state. Default is false */
- movement: boolean;
+ movement: any;
}> &
BoxProps;
@@ -49,7 +49,7 @@ export function DmIcon(props: Props) {
const [iconRef, setIconRef] = useState('');
- const query = `${iconRef}?state=${icon_state}&dir=${direction}&movement=${movement}&frame=${frame}`;
+ const query = `${iconRef}?state=${icon_state}&dir=${direction}&movement=${!!movement}&frame=${frame}`;
useEffect(() => {
async function fetchRefMap() {
diff --git a/tgui/packages/tgui/interfaces/AbductorConsole.jsx b/tgui/packages/tgui/interfaces/AbductorConsole.jsx
index e870089a1bcf4..bb2f1dffccfe3 100644
--- a/tgui/packages/tgui/interfaces/AbductorConsole.jsx
+++ b/tgui/packages/tgui/interfaces/AbductorConsole.jsx
@@ -1,5 +1,12 @@
+import {
+ Button,
+ LabeledList,
+ NoticeBox,
+ Section,
+ Tabs,
+} from 'tgui-core/components';
+
import { useBackend, useSharedState } from '../backend';
-import { Button, LabeledList, NoticeBox, Section, Tabs } from '../components';
import { Window } from '../layouts';
import { GenericUplink } from './Uplink/GenericUplink';
diff --git a/tgui/packages/tgui/interfaces/AccountingConsole.tsx b/tgui/packages/tgui/interfaces/AccountingConsole.tsx
index 92124bc38ef02..726abb78ece78 100644
--- a/tgui/packages/tgui/interfaces/AccountingConsole.tsx
+++ b/tgui/packages/tgui/interfaces/AccountingConsole.tsx
@@ -1,4 +1,3 @@
-import { BooleanLike } from 'common/react';
import { useState } from 'react';
import {
BlockQuote,
@@ -8,6 +7,7 @@ import {
Stack,
Tabs,
} from 'tgui-core/components';
+import { BooleanLike } from 'tgui-core/react';
import { useBackend } from '../backend';
import { Window } from '../layouts';
diff --git a/tgui/packages/tgui/interfaces/Achievements.jsx b/tgui/packages/tgui/interfaces/Achievements.jsx
index a9b6b914b9ee9..ccb7c2807f698 100644
--- a/tgui/packages/tgui/interfaces/Achievements.jsx
+++ b/tgui/packages/tgui/interfaces/Achievements.jsx
@@ -1,7 +1,7 @@
import { useState } from 'react';
+import { Box, Flex, Icon, Table, Tabs, Tooltip } from 'tgui-core/components';
import { useBackend } from '../backend';
-import { Box, Flex, Icon, Table, Tabs, Tooltip } from '../components';
import { Window } from '../layouts';
export const Achievements = (props) => {
diff --git a/tgui/packages/tgui/interfaces/AdminFax.jsx b/tgui/packages/tgui/interfaces/AdminFax.jsx
index 3e6026d4ce46e..37bdd523c4196 100644
--- a/tgui/packages/tgui/interfaces/AdminFax.jsx
+++ b/tgui/packages/tgui/interfaces/AdminFax.jsx
@@ -1,6 +1,4 @@
import { useState } from 'react';
-
-import { useBackend } from '../backend';
import {
Box,
Button,
@@ -12,7 +10,9 @@ import {
Section,
TextArea,
Tooltip,
-} from '../components';
+} from 'tgui-core/components';
+
+import { useBackend } from '../backend';
import { Window } from '../layouts';
export const AdminFax = (props) => {
diff --git a/tgui/packages/tgui/interfaces/AdminPDA.jsx b/tgui/packages/tgui/interfaces/AdminPDA.jsx
index fa83f200e52c7..da06e7d294d68 100644
--- a/tgui/packages/tgui/interfaces/AdminPDA.jsx
+++ b/tgui/packages/tgui/interfaces/AdminPDA.jsx
@@ -1,5 +1,6 @@
+import { Box, Dropdown, Input, Section, TextArea } from 'tgui-core/components';
+
import { useBackend, useLocalState } from '../backend';
-import { Box, Dropdown, Input, Section, TextArea } from '../components';
import { Button } from '../components/Button';
import { Window } from '../layouts';
diff --git a/tgui/packages/tgui/interfaces/Adminhelp.tsx b/tgui/packages/tgui/interfaces/Adminhelp.tsx
index 7e29a807f1d4e..af1e273b68523 100644
--- a/tgui/packages/tgui/interfaces/Adminhelp.tsx
+++ b/tgui/packages/tgui/interfaces/Adminhelp.tsx
@@ -1,8 +1,15 @@
-import { BooleanLike } from 'common/react';
import { useState } from 'react';
+import {
+ Box,
+ Button,
+ Input,
+ NoticeBox,
+ Stack,
+ TextArea,
+} from 'tgui-core/components';
+import { BooleanLike } from 'tgui-core/react';
import { useBackend } from '../backend';
-import { Box, Button, Input, NoticeBox, Stack, TextArea } from '../components';
import { Window } from '../layouts';
type AdminhelpData = {
diff --git a/tgui/packages/tgui/interfaces/AdventureBrowser.tsx b/tgui/packages/tgui/interfaces/AdventureBrowser.tsx
index cb43f6f319d47..86e05231952a6 100644
--- a/tgui/packages/tgui/interfaces/AdventureBrowser.tsx
+++ b/tgui/packages/tgui/interfaces/AdventureBrowser.tsx
@@ -1,5 +1,6 @@
+import { Box, Button, NoticeBox, Section, Table } from 'tgui-core/components';
+
import { useBackend, useLocalState } from '../backend';
-import { Box, Button, NoticeBox, Section, Table } from '../components';
import { formatTime } from '../format';
import { Window } from '../layouts';
import { AdventureDataProvider, AdventureScreen } from './ExodroneConsole';
diff --git a/tgui/packages/tgui/interfaces/AiAirlock.jsx b/tgui/packages/tgui/interfaces/AiAirlock.jsx
index b74735d8e6a12..bf9cdf4a7ffe5 100644
--- a/tgui/packages/tgui/interfaces/AiAirlock.jsx
+++ b/tgui/packages/tgui/interfaces/AiAirlock.jsx
@@ -1,5 +1,6 @@
+import { Button, LabeledList, Section } from 'tgui-core/components';
+
import { useBackend } from '../backend';
-import { Button, LabeledList, Section } from '../components';
import { Window } from '../layouts';
const dangerMap = {
diff --git a/tgui/packages/tgui/interfaces/AiRestorer.tsx b/tgui/packages/tgui/interfaces/AiRestorer.tsx
index 1add6b5e934bb..a7987145a2c77 100644
--- a/tgui/packages/tgui/interfaces/AiRestorer.tsx
+++ b/tgui/packages/tgui/interfaces/AiRestorer.tsx
@@ -1,6 +1,3 @@
-import { BooleanLike } from 'common/react';
-
-import { useBackend } from '../backend';
import {
Box,
Button,
@@ -8,7 +5,10 @@ import {
NoticeBox,
ProgressBar,
Section,
-} from '../components';
+} from 'tgui-core/components';
+import { BooleanLike } from 'tgui-core/react';
+
+import { useBackend } from '../backend';
import { Window } from '../layouts';
type Data = {
diff --git a/tgui/packages/tgui/interfaces/AiVoiceChanger.tsx b/tgui/packages/tgui/interfaces/AiVoiceChanger.tsx
index 09c1aa04e57f1..a9bd489c7b0b1 100644
--- a/tgui/packages/tgui/interfaces/AiVoiceChanger.tsx
+++ b/tgui/packages/tgui/interfaces/AiVoiceChanger.tsx
@@ -1,7 +1,13 @@
-import { BooleanLike } from 'common/react';
+import {
+ Button,
+ Dropdown,
+ Input,
+ LabeledList,
+ Section,
+} from 'tgui-core/components';
+import { BooleanLike } from 'tgui-core/react';
import { useBackend } from '../backend';
-import { Button, Dropdown, Input, LabeledList, Section } from '../components';
import { Window } from '../layouts';
type Data = {
diff --git a/tgui/packages/tgui/interfaces/AirAlarm.tsx b/tgui/packages/tgui/interfaces/AirAlarm.tsx
index 448d233d36839..f5d439915e991 100644
--- a/tgui/packages/tgui/interfaces/AirAlarm.tsx
+++ b/tgui/packages/tgui/interfaces/AirAlarm.tsx
@@ -1,4 +1,3 @@
-import { BooleanLike } from 'common/react';
import { Fragment } from 'react';
import {
Box,
@@ -10,6 +9,7 @@ import {
Table,
VirtualList,
} from 'tgui-core/components';
+import { BooleanLike } from 'tgui-core/react';
import { useBackend, useLocalState } from '../backend';
import { Window } from '../layouts';
diff --git a/tgui/packages/tgui/interfaces/AirlockButtonController.tsx b/tgui/packages/tgui/interfaces/AirlockButtonController.tsx
index 98a3f7c91135d..83f984974896c 100644
--- a/tgui/packages/tgui/interfaces/AirlockButtonController.tsx
+++ b/tgui/packages/tgui/interfaces/AirlockButtonController.tsx
@@ -1,7 +1,7 @@
-import { BooleanLike } from 'common/react';
+import { Button, NoticeBox, Section, Stack } from 'tgui-core/components';
+import { BooleanLike } from 'tgui-core/react';
import { useBackend } from '../backend';
-import { Button, NoticeBox, Section, Stack } from '../components';
import { Window } from '../layouts';
type Data = {
diff --git a/tgui/packages/tgui/interfaces/AirlockController.tsx b/tgui/packages/tgui/interfaces/AirlockController.tsx
index ca3551c3415a4..81a10f75bc52d 100644
--- a/tgui/packages/tgui/interfaces/AirlockController.tsx
+++ b/tgui/packages/tgui/interfaces/AirlockController.tsx
@@ -1,5 +1,6 @@
+import { Box, Button, Icon, LabeledList, Section } from 'tgui-core/components';
+
import { useBackend } from '../backend';
-import { Box, Button, Icon, LabeledList, Section } from '../components';
import { Window } from '../layouts';
type AirlockControllerData = {
diff --git a/tgui/packages/tgui/interfaces/AirlockElectronics.tsx b/tgui/packages/tgui/interfaces/AirlockElectronics.tsx
index b90e8ac3b578f..2a0e535ac8842 100644
--- a/tgui/packages/tgui/interfaces/AirlockElectronics.tsx
+++ b/tgui/packages/tgui/interfaces/AirlockElectronics.tsx
@@ -1,7 +1,13 @@
-import { BooleanLike } from 'common/react';
+import {
+ Button,
+ Input,
+ LabeledList,
+ Section,
+ Stack,
+} from 'tgui-core/components';
+import { BooleanLike } from 'tgui-core/react';
import { useBackend } from '../backend';
-import { Button, Input, LabeledList, Section, Stack } from '../components';
import { Window } from '../layouts';
import { AccessConfig, Region } from './common/AccessConfig';
diff --git a/tgui/packages/tgui/interfaces/AlertModal.tsx b/tgui/packages/tgui/interfaces/AlertModal.tsx
index 5924dc7ae7cf2..8341d5dbd66c0 100644
--- a/tgui/packages/tgui/interfaces/AlertModal.tsx
+++ b/tgui/packages/tgui/interfaces/AlertModal.tsx
@@ -1,9 +1,9 @@
-import { isEscape, KEY } from 'common/keys';
-import { BooleanLike } from 'common/react';
import { KeyboardEvent, useState } from 'react';
+import { Autofocus, Box, Button, Section, Stack } from 'tgui-core/components';
+import { isEscape, KEY } from 'tgui-core/keys';
+import { BooleanLike } from 'tgui-core/react';
import { useBackend } from '../backend';
-import { Autofocus, Box, Button, Section, Stack } from '../components';
import { Window } from '../layouts';
import { Loader } from './common/Loader';
diff --git a/tgui/packages/tgui/interfaces/AnomalyRefinery.jsx b/tgui/packages/tgui/interfaces/AnomalyRefinery.jsx
index 89f95e28c4a64..870bb5c264649 100644
--- a/tgui/packages/tgui/interfaces/AnomalyRefinery.jsx
+++ b/tgui/packages/tgui/interfaces/AnomalyRefinery.jsx
@@ -1,4 +1,3 @@
-import { useBackend, useSharedState } from '../backend';
import {
Box,
Button,
@@ -7,7 +6,9 @@ import {
Modal,
Section,
Stack,
-} from '../components';
+} from 'tgui-core/components';
+
+import { useBackend, useSharedState } from '../backend';
import { Window } from '../layouts';
import { GasmixParser } from './common/GasmixParser';
diff --git a/tgui/packages/tgui/interfaces/AntagInfoBlob.tsx b/tgui/packages/tgui/interfaces/AntagInfoBlob.tsx
index fccf02a8b1c77..dfb2bd5bb2c89 100644
--- a/tgui/packages/tgui/interfaces/AntagInfoBlob.tsx
+++ b/tgui/packages/tgui/interfaces/AntagInfoBlob.tsx
@@ -1,4 +1,3 @@
-import { useBackend } from '../backend';
import {
Box,
Collapsible,
@@ -6,7 +5,9 @@ import {
LabeledList,
Section,
Stack,
-} from '../components';
+} from 'tgui-core/components';
+
+import { useBackend } from '../backend';
import { Window } from '../layouts';
import { Objective } from './common/Objectives';
diff --git a/tgui/packages/tgui/interfaces/AntagInfoBrainwashed.tsx b/tgui/packages/tgui/interfaces/AntagInfoBrainwashed.tsx
index a506171504a9b..dfa05895bd047 100644
--- a/tgui/packages/tgui/interfaces/AntagInfoBrainwashed.tsx
+++ b/tgui/packages/tgui/interfaces/AntagInfoBrainwashed.tsx
@@ -1,5 +1,6 @@
+import { Box, Icon, Section, Stack } from 'tgui-core/components';
+
import { useBackend } from '../backend';
-import { Box, Icon, Section, Stack } from '../components';
import { Window } from '../layouts';
import { Objective, ObjectivePrintout } from './common/Objectives';
diff --git a/tgui/packages/tgui/interfaces/AntagInfoBrother.tsx b/tgui/packages/tgui/interfaces/AntagInfoBrother.tsx
index 294fc10113590..731e24e485fe5 100644
--- a/tgui/packages/tgui/interfaces/AntagInfoBrother.tsx
+++ b/tgui/packages/tgui/interfaces/AntagInfoBrother.tsx
@@ -1,5 +1,6 @@
+import { Section, Stack } from 'tgui-core/components';
+
import { useBackend } from '../backend';
-import { Section, Stack } from '../components';
import { Window } from '../layouts';
import { Objective, ObjectivePrintout } from './common/Objectives';
diff --git a/tgui/packages/tgui/interfaces/AntagInfoChangeling.tsx b/tgui/packages/tgui/interfaces/AntagInfoChangeling.tsx
index 2b57824bdd33a..755cb4ee6935a 100644
--- a/tgui/packages/tgui/interfaces/AntagInfoChangeling.tsx
+++ b/tgui/packages/tgui/interfaces/AntagInfoChangeling.tsx
@@ -1,7 +1,4 @@
-import { BooleanLike } from 'common/react';
import { useState } from 'react';
-
-import { useBackend } from '../backend';
import {
Button,
Dimmer,
@@ -9,7 +6,10 @@ import {
NoticeBox,
Section,
Stack,
-} from '../components';
+} from 'tgui-core/components';
+import { BooleanLike } from 'tgui-core/react';
+
+import { useBackend } from '../backend';
import { Window } from '../layouts';
import {
Objective,
diff --git a/tgui/packages/tgui/interfaces/AntagInfoDemon.tsx b/tgui/packages/tgui/interfaces/AntagInfoDemon.tsx
index 1dceb6a60a6c1..aaa02521167c8 100644
--- a/tgui/packages/tgui/interfaces/AntagInfoDemon.tsx
+++ b/tgui/packages/tgui/interfaces/AntagInfoDemon.tsx
@@ -1,7 +1,7 @@
-import { BooleanLike } from 'common/react';
+import { Box, Section, Stack } from 'tgui-core/components';
+import { BooleanLike } from 'tgui-core/react';
import { useBackend } from '../backend';
-import { Box, Section, Stack } from '../components';
import { Window } from '../layouts';
import { Objective, ObjectivePrintout } from './common/Objectives';
diff --git a/tgui/packages/tgui/interfaces/AntagInfoGeneric.tsx b/tgui/packages/tgui/interfaces/AntagInfoGeneric.tsx
index 8aeae3510c4f8..c4a81426585d8 100644
--- a/tgui/packages/tgui/interfaces/AntagInfoGeneric.tsx
+++ b/tgui/packages/tgui/interfaces/AntagInfoGeneric.tsx
@@ -1,5 +1,6 @@
+import { Section, Stack } from 'tgui-core/components';
+
import { useBackend } from '../backend';
-import { Section, Stack } from '../components';
import { Window } from '../layouts';
import { Objective, ObjectivePrintout } from './common/Objectives';
diff --git a/tgui/packages/tgui/interfaces/AntagInfoGlitch.tsx b/tgui/packages/tgui/interfaces/AntagInfoGlitch.tsx
index 0fd7d18a47c56..469215a7f4595 100644
--- a/tgui/packages/tgui/interfaces/AntagInfoGlitch.tsx
+++ b/tgui/packages/tgui/interfaces/AntagInfoGlitch.tsx
@@ -1,5 +1,6 @@
+import { Divider, Section, Stack } from 'tgui-core/components';
+
import { useBackend } from '../backend';
-import { Divider, Section, Stack } from '../components';
import { Window } from '../layouts';
import { Objective } from './common/Objectives';
diff --git a/tgui/packages/tgui/interfaces/AntagInfoHeretic.tsx b/tgui/packages/tgui/interfaces/AntagInfoHeretic.tsx
index bd05662cddbb9..935104c7c2c58 100644
--- a/tgui/packages/tgui/interfaces/AntagInfoHeretic.tsx
+++ b/tgui/packages/tgui/interfaces/AntagInfoHeretic.tsx
@@ -1,9 +1,16 @@
-import { BooleanLike } from 'common/react';
import { useState } from 'react';
+import {
+ Box,
+ Button,
+ DmIcon,
+ Flex,
+ Section,
+ Stack,
+ Tabs,
+} from 'tgui-core/components';
+import { BooleanLike } from 'tgui-core/react';
import { useBackend } from '../backend';
-import { BlockQuote, Box, Button, Section, Stack, Tabs } from '../components';
-import { CssColor } from '../constants';
import { Window } from '../layouts';
import {
Objective,
@@ -35,20 +42,33 @@ const hereticYellow = {
color: 'yellow',
};
+type IconParams = {
+ icon: string;
+ state: string;
+ frame: number;
+ dir: number;
+ moving: BooleanLike;
+};
+
type Knowledge = {
path: string;
+ icon_params: IconParams;
name: string;
desc: string;
gainFlavor: string;
cost: number;
- disabled: boolean;
- hereticPath: string;
- color: CssColor;
+ bgr: string;
+ disabled: BooleanLike;
+ finished: BooleanLike;
+ ascension: BooleanLike;
};
type KnowledgeInfo = {
- learnableKnowledge: Knowledge[];
- learnedKnowledge: Knowledge[];
+ knowledge_tiers: KnowledgeTier[];
+};
+
+type KnowledgeTier = {
+ nodes: Knowledge[];
};
type Info = {
@@ -215,63 +235,96 @@ const InformationSection = (props) => {
);
};
-const ResearchedKnowledge = (props) => {
- const { data } = useBackend();
- const { learnedKnowledge } = data;
+const KnowledgeTree = (props) => {
+ const { data, act } = useBackend();
+ const { knowledge_tiers } = data;
return (
-
-
-
- {(!learnedKnowledge.length && 'Пусто!') ||
- learnedKnowledge.map((learned) => (
-
-
+
+
+ DAWN
+
+
+ {knowledge_tiers.length === 0
+ ? 'None!'
+ : knowledge_tiers.map((tier, i) => (
+
+
+ {tier.nodes.map((node) => (
+
+
+ {!!node.ascension && (
+
+ DUSK
+
+ )}
+
+ ))}
+
+
))}
-
-
-
- );
-};
-
-const KnowledgeShop = (props) => {
- const { data, act } = useBackend();
- const { learnableKnowledge } = data;
-
- return (
-
-
- {(!learnableKnowledge.length && 'Пусто!') ||
- learnableKnowledge.map((toLearn) => (
-
-
- ))}
-
-
+
+
);
};
@@ -280,20 +333,13 @@ const ResearchInfo = (props) => {
const { charges } = data;
return (
-
+
+
+ Доступные очки знаний :{' '}
+ {charges || 0} .
+
-
-
- Доступные очки знаний :{' '}
- {charges || 0} .
-
-
-
-
-
-
-
-
+
);
diff --git a/tgui/packages/tgui/interfaces/AntagInfoMalf.tsx b/tgui/packages/tgui/interfaces/AntagInfoMalf.tsx
index a996899c5288f..e64673ed5a596 100644
--- a/tgui/packages/tgui/interfaces/AntagInfoMalf.tsx
+++ b/tgui/packages/tgui/interfaces/AntagInfoMalf.tsx
@@ -1,8 +1,8 @@
-import { BooleanLike } from 'common/react';
import { useState } from 'react';
+import { BlockQuote, Button, Section, Stack, Tabs } from 'tgui-core/components';
+import { BooleanLike } from 'tgui-core/react';
import { useBackend } from '../backend';
-import { BlockQuote, Button, Section, Stack, Tabs } from '../components';
import { Window } from '../layouts';
import {
Objective,
diff --git a/tgui/packages/tgui/interfaces/AntagInfoMorph.tsx b/tgui/packages/tgui/interfaces/AntagInfoMorph.tsx
index e6ce213d3e72d..c4f8e5a442fec 100644
--- a/tgui/packages/tgui/interfaces/AntagInfoMorph.tsx
+++ b/tgui/packages/tgui/interfaces/AntagInfoMorph.tsx
@@ -1,4 +1,5 @@
-import { BlockQuote, Stack } from '../components';
+import { BlockQuote, Stack } from 'tgui-core/components';
+
import { Window } from '../layouts';
const goodstyle = {
diff --git a/tgui/packages/tgui/interfaces/AntagInfoNightmare.tsx b/tgui/packages/tgui/interfaces/AntagInfoNightmare.tsx
index f0b897e764545..3c27598101f89 100644
--- a/tgui/packages/tgui/interfaces/AntagInfoNightmare.tsx
+++ b/tgui/packages/tgui/interfaces/AntagInfoNightmare.tsx
@@ -1,4 +1,5 @@
-import { BlockQuote, LabeledList, Section, Stack } from '../components';
+import { BlockQuote, LabeledList, Section, Stack } from 'tgui-core/components';
+
import { Window } from '../layouts';
const tipstyle = {
diff --git a/tgui/packages/tgui/interfaces/AntagInfoNinja.tsx b/tgui/packages/tgui/interfaces/AntagInfoNinja.tsx
index 7ffc1b8413e87..c846b748b491b 100644
--- a/tgui/packages/tgui/interfaces/AntagInfoNinja.tsx
+++ b/tgui/packages/tgui/interfaces/AntagInfoNinja.tsx
@@ -1,7 +1,7 @@
-import { BooleanLike } from 'common/react';
+import { Icon, Section, Stack } from 'tgui-core/components';
+import { BooleanLike } from 'tgui-core/react';
import { useBackend } from '../backend';
-import { Icon, Section, Stack } from '../components';
import { Window } from '../layouts';
import {
Objective,
diff --git a/tgui/packages/tgui/interfaces/AntagInfoSentient.jsx b/tgui/packages/tgui/interfaces/AntagInfoSentient.jsx
index d3d939b50c62d..5d3c901c9fc4a 100644
--- a/tgui/packages/tgui/interfaces/AntagInfoSentient.jsx
+++ b/tgui/packages/tgui/interfaces/AntagInfoSentient.jsx
@@ -1,5 +1,6 @@
+import { BlockQuote, Section, Stack } from 'tgui-core/components';
+
import { useBackend } from '../backend';
-import { BlockQuote, Section, Stack } from '../components';
import { Window } from '../layouts';
export const AntagInfoSentient = (props) => {
diff --git a/tgui/packages/tgui/interfaces/AntagInfoSeparatist.tsx b/tgui/packages/tgui/interfaces/AntagInfoSeparatist.tsx
index 1a14c6203c6f7..2999024659d35 100644
--- a/tgui/packages/tgui/interfaces/AntagInfoSeparatist.tsx
+++ b/tgui/packages/tgui/interfaces/AntagInfoSeparatist.tsx
@@ -1,5 +1,6 @@
+import { Icon, Section, Stack } from 'tgui-core/components';
+
import { useBackend } from '../backend';
-import { Icon, Section, Stack } from '../components';
import { Window } from '../layouts';
import { Objective, ObjectivePrintout } from './common/Objectives';
diff --git a/tgui/packages/tgui/interfaces/AntagInfoShade.jsx b/tgui/packages/tgui/interfaces/AntagInfoShade.jsx
index 4a44a323f7d92..3929d0ca68ad5 100644
--- a/tgui/packages/tgui/interfaces/AntagInfoShade.jsx
+++ b/tgui/packages/tgui/interfaces/AntagInfoShade.jsx
@@ -1,5 +1,6 @@
+import { Icon, Section, Stack } from 'tgui-core/components';
+
import { useBackend } from '../backend';
-import { Icon, Section, Stack } from '../components';
import { Window } from '../layouts';
export const AntagInfoShade = (props) => {
diff --git a/tgui/packages/tgui/interfaces/AntagInfoSpy.tsx b/tgui/packages/tgui/interfaces/AntagInfoSpy.tsx
index c6f1f716ad9a2..a6a049f137e4d 100644
--- a/tgui/packages/tgui/interfaces/AntagInfoSpy.tsx
+++ b/tgui/packages/tgui/interfaces/AntagInfoSpy.tsx
@@ -1,5 +1,6 @@
+import { Section, Stack } from 'tgui-core/components';
+
import { useBackend } from '../backend';
-import { Section, Stack } from '../components';
import { Window } from '../layouts';
import { Objective, ObjectivePrintout } from './common/Objectives';
diff --git a/tgui/packages/tgui/interfaces/AntagInfoTraitor.tsx b/tgui/packages/tgui/interfaces/AntagInfoTraitor.tsx
index d757b0c3402c5..a807f771f95d1 100644
--- a/tgui/packages/tgui/interfaces/AntagInfoTraitor.tsx
+++ b/tgui/packages/tgui/interfaces/AntagInfoTraitor.tsx
@@ -1,7 +1,13 @@
-import { BooleanLike } from 'common/react';
+import {
+ BlockQuote,
+ Button,
+ Dimmer,
+ Section,
+ Stack,
+} from 'tgui-core/components';
+import { BooleanLike } from 'tgui-core/react';
import { useBackend } from '../backend';
-import { BlockQuote, Button, Dimmer, Section, Stack } from '../components';
import { Window } from '../layouts';
import { Objective, ObjectivePrintout } from './common/Objectives';
diff --git a/tgui/packages/tgui/interfaces/AntagInfoWizard.tsx b/tgui/packages/tgui/interfaces/AntagInfoWizard.tsx
index abcc007019015..4c251cc39f4e5 100644
--- a/tgui/packages/tgui/interfaces/AntagInfoWizard.tsx
+++ b/tgui/packages/tgui/interfaces/AntagInfoWizard.tsx
@@ -1,7 +1,7 @@
-import { BooleanLike } from 'common/react';
+import { Box, Section, Stack } from 'tgui-core/components';
+import { BooleanLike } from 'tgui-core/react';
import { useBackend } from '../backend';
-import { Box, Section, Stack } from '../components';
import { Window } from '../layouts';
import {
Objective,
diff --git a/tgui/packages/tgui/interfaces/Apc.jsx b/tgui/packages/tgui/interfaces/Apc.jsx
index 13a8984f9d4c0..e4f3ebe94b92a 100644
--- a/tgui/packages/tgui/interfaces/Apc.jsx
+++ b/tgui/packages/tgui/interfaces/Apc.jsx
@@ -1,4 +1,3 @@
-import { useBackend } from '../backend';
import {
Box,
Button,
@@ -6,7 +5,9 @@ import {
NoticeBox,
ProgressBar,
Section,
-} from '../components';
+} from 'tgui-core/components';
+
+import { useBackend } from '../backend';
import { Window } from '../layouts';
import { InterfaceLockNoticeBox } from './common/InterfaceLockNoticeBox';
diff --git a/tgui/packages/tgui/interfaces/ApcControl.jsx b/tgui/packages/tgui/interfaces/ApcControl.jsx
index fb29c78032d8a..caeb5ee1bed6e 100644
--- a/tgui/packages/tgui/interfaces/ApcControl.jsx
+++ b/tgui/packages/tgui/interfaces/ApcControl.jsx
@@ -1,8 +1,6 @@
import { map, sortBy } from 'common/collections';
import { flow } from 'common/fp';
import { useState } from 'react';
-
-import { useBackend, useLocalState } from '../backend';
import {
Box,
Button,
@@ -12,7 +10,9 @@ import {
Stack,
Table,
Tabs,
-} from '../components';
+} from 'tgui-core/components';
+
+import { useBackend, useLocalState } from '../backend';
import { Window } from '../layouts';
import { AreaCharge, powerRank } from './PowerMonitor';
diff --git a/tgui/packages/tgui/interfaces/ApprenticeContract.tsx b/tgui/packages/tgui/interfaces/ApprenticeContract.tsx
index 392f087b9bfef..38d3698919845 100644
--- a/tgui/packages/tgui/interfaces/ApprenticeContract.tsx
+++ b/tgui/packages/tgui/interfaces/ApprenticeContract.tsx
@@ -1,6 +1,14 @@
+import {
+ BlockQuote,
+ Box,
+ Button,
+ Icon,
+ Section,
+ Stack,
+} from 'tgui-core/components';
+
import { resolveAsset } from '../assets';
import { useBackend } from '../backend';
-import { BlockQuote, Box, Button, Icon, Section, Stack } from '../components';
import { Window } from '../layouts';
export const ApprenticeContract = (props) => {
diff --git a/tgui/packages/tgui/interfaces/Aquarium.tsx b/tgui/packages/tgui/interfaces/Aquarium.tsx
index 259844d58a8d7..caa0c65d75f65 100644
--- a/tgui/packages/tgui/interfaces/Aquarium.tsx
+++ b/tgui/packages/tgui/interfaces/Aquarium.tsx
@@ -1,6 +1,3 @@
-import { BooleanLike } from 'common/react';
-
-import { useBackend } from '../backend';
import {
Button,
Flex,
@@ -8,7 +5,10 @@ import {
LabeledControls,
NumberInput,
Section,
-} from '../components';
+} from 'tgui-core/components';
+import { BooleanLike } from 'tgui-core/react';
+
+import { useBackend } from '../backend';
import { Window } from '../layouts';
type Data = {
diff --git a/tgui/packages/tgui/interfaces/AtmosAlertConsole.tsx b/tgui/packages/tgui/interfaces/AtmosAlertConsole.tsx
index 1202c5d73bd5f..e27ddaa139655 100644
--- a/tgui/packages/tgui/interfaces/AtmosAlertConsole.tsx
+++ b/tgui/packages/tgui/interfaces/AtmosAlertConsole.tsx
@@ -1,5 +1,6 @@
+import { Button, Section } from 'tgui-core/components';
+
import { useBackend } from '../backend';
-import { Button, Section } from '../components';
import { Window } from '../layouts';
type Data = {
diff --git a/tgui/packages/tgui/interfaces/AtmosControlConsole.tsx b/tgui/packages/tgui/interfaces/AtmosControlConsole.tsx
index 52852e908e243..0fb88d386f964 100644
--- a/tgui/packages/tgui/interfaces/AtmosControlConsole.tsx
+++ b/tgui/packages/tgui/interfaces/AtmosControlConsole.tsx
@@ -1,6 +1,4 @@
import { useState } from 'react';
-
-import { useBackend } from '../backend';
import {
Box,
Button,
@@ -9,7 +7,9 @@ import {
NumberInput,
Section,
Stack,
-} from '../components';
+} from 'tgui-core/components';
+
+import { useBackend } from '../backend';
import { Window } from '../layouts';
import {
AtmosHandbookContent,
diff --git a/tgui/packages/tgui/interfaces/AtmosControlPanel.jsx b/tgui/packages/tgui/interfaces/AtmosControlPanel.jsx
index c39778c410f28..36d0b40e0eb29 100644
--- a/tgui/packages/tgui/interfaces/AtmosControlPanel.jsx
+++ b/tgui/packages/tgui/interfaces/AtmosControlPanel.jsx
@@ -1,7 +1,7 @@
import { map, sortBy } from 'common/collections';
+import { Box, Button, Flex, Section, Table } from 'tgui-core/components';
import { useBackend } from '../backend';
-import { Box, Button, Flex, Section, Table } from '../components';
import { Window } from '../layouts';
export const AtmosControlPanel = (props) => {
diff --git a/tgui/packages/tgui/interfaces/AtmosFilter.tsx b/tgui/packages/tgui/interfaces/AtmosFilter.tsx
index e99c09c6695c1..1a4134e615d16 100644
--- a/tgui/packages/tgui/interfaces/AtmosFilter.tsx
+++ b/tgui/packages/tgui/interfaces/AtmosFilter.tsx
@@ -1,7 +1,12 @@
-import { BooleanLike } from 'common/react';
+import {
+ Button,
+ LabeledList,
+ NumberInput,
+ Section,
+} from 'tgui-core/components';
+import { BooleanLike } from 'tgui-core/react';
import { useBackend } from '../backend';
-import { Button, LabeledList, NumberInput, Section } from '../components';
import { getGasLabel } from '../constants';
import { Window } from '../layouts';
diff --git a/tgui/packages/tgui/interfaces/AtmosMixer.tsx b/tgui/packages/tgui/interfaces/AtmosMixer.tsx
index 5f1578a69e7e4..87643c4dd596c 100644
--- a/tgui/packages/tgui/interfaces/AtmosMixer.tsx
+++ b/tgui/packages/tgui/interfaces/AtmosMixer.tsx
@@ -1,7 +1,12 @@
-import { BooleanLike } from 'common/react';
+import {
+ Button,
+ LabeledList,
+ NumberInput,
+ Section,
+} from 'tgui-core/components';
+import { BooleanLike } from 'tgui-core/react';
import { useBackend } from '../backend';
-import { Button, LabeledList, NumberInput, Section } from '../components';
import { Window } from '../layouts';
type Data = {
diff --git a/tgui/packages/tgui/interfaces/AtmosPump.tsx b/tgui/packages/tgui/interfaces/AtmosPump.tsx
index 002af582fb5d9..5cdd8764a6c39 100644
--- a/tgui/packages/tgui/interfaces/AtmosPump.tsx
+++ b/tgui/packages/tgui/interfaces/AtmosPump.tsx
@@ -1,7 +1,12 @@
-import { BooleanLike } from 'common/react';
+import {
+ Button,
+ LabeledList,
+ NumberInput,
+ Section,
+} from 'tgui-core/components';
+import { BooleanLike } from 'tgui-core/react';
import { useBackend } from '../backend';
-import { Button, LabeledList, NumberInput, Section } from '../components';
import { Window } from '../layouts';
type Data = {
diff --git a/tgui/packages/tgui/interfaces/AtmosTempGate.tsx b/tgui/packages/tgui/interfaces/AtmosTempGate.tsx
index 11c1356b7cce3..049adbdc49af5 100644
--- a/tgui/packages/tgui/interfaces/AtmosTempGate.tsx
+++ b/tgui/packages/tgui/interfaces/AtmosTempGate.tsx
@@ -1,7 +1,12 @@
-import { BooleanLike } from 'common/react';
+import {
+ Button,
+ LabeledList,
+ NumberInput,
+ Section,
+} from 'tgui-core/components';
+import { BooleanLike } from 'tgui-core/react';
import { useBackend } from '../backend';
-import { Button, LabeledList, NumberInput, Section } from '../components';
import { Window } from '../layouts';
type Data = {
diff --git a/tgui/packages/tgui/interfaces/AtmosTempPump.tsx b/tgui/packages/tgui/interfaces/AtmosTempPump.tsx
index 48d7234fd8713..f398f9969ccff 100644
--- a/tgui/packages/tgui/interfaces/AtmosTempPump.tsx
+++ b/tgui/packages/tgui/interfaces/AtmosTempPump.tsx
@@ -1,7 +1,12 @@
-import { BooleanLike } from 'common/react';
+import {
+ Button,
+ LabeledList,
+ NumberInput,
+ Section,
+} from 'tgui-core/components';
+import { BooleanLike } from 'tgui-core/react';
import { useBackend } from '../backend';
-import { Button, LabeledList, NumberInput, Section } from '../components';
import { Window } from '../layouts';
type Data = {
diff --git a/tgui/packages/tgui/interfaces/Autolathe.tsx b/tgui/packages/tgui/interfaces/Autolathe.tsx
index 67f707476fe83..26b5254072684 100644
--- a/tgui/packages/tgui/interfaces/Autolathe.tsx
+++ b/tgui/packages/tgui/interfaces/Autolathe.tsx
@@ -1,5 +1,3 @@
-import { BooleanLike, classes } from 'common/react';
-import { capitalize } from 'common/string';
import {
Box,
Button,
@@ -11,6 +9,8 @@ import {
Stack,
Tooltip,
} from 'tgui-core/components';
+import { BooleanLike, classes } from 'tgui-core/react';
+import { capitalize } from 'tgui-core/string';
import { useBackend } from '../backend';
import { Window } from '../layouts';
diff --git a/tgui/packages/tgui/interfaces/AutomatedAnnouncement.tsx b/tgui/packages/tgui/interfaces/AutomatedAnnouncement.tsx
index 6446ac5f6de34..f08ee83fd4ffd 100644
--- a/tgui/packages/tgui/interfaces/AutomatedAnnouncement.tsx
+++ b/tgui/packages/tgui/interfaces/AutomatedAnnouncement.tsx
@@ -1,7 +1,7 @@
-import { BooleanLike } from 'common/react';
+import { Button, Input, LabeledList, Section } from 'tgui-core/components';
+import { BooleanLike } from 'tgui-core/react';
import { useBackend } from '../backend';
-import { Button, Input, LabeledList, Section } from '../components';
import { Window } from '../layouts';
const TOOLTIP_TEXT = `
diff --git a/tgui/packages/tgui/interfaces/AuxBaseConsole.tsx b/tgui/packages/tgui/interfaces/AuxBaseConsole.tsx
index c1b966674a78d..baae00676f591 100644
--- a/tgui/packages/tgui/interfaces/AuxBaseConsole.tsx
+++ b/tgui/packages/tgui/interfaces/AuxBaseConsole.tsx
@@ -1,8 +1,8 @@
-import { BooleanLike } from 'common/react';
import { useState } from 'react';
+import { Button, NoticeBox, Section, Table, Tabs } from 'tgui-core/components';
+import { BooleanLike } from 'tgui-core/react';
import { useBackend } from '../backend';
-import { Button, NoticeBox, Section, Table, Tabs } from '../components';
import { Window } from '../layouts';
import { ShuttleConsoleContent } from './ShuttleConsole';
diff --git a/tgui/packages/tgui/interfaces/AvatarHelp.tsx b/tgui/packages/tgui/interfaces/AvatarHelp.tsx
index 6821152f51cbd..37a53591cf2ab 100644
--- a/tgui/packages/tgui/interfaces/AvatarHelp.tsx
+++ b/tgui/packages/tgui/interfaces/AvatarHelp.tsx
@@ -1,5 +1,6 @@
+import { Box, Icon, Section, Stack } from 'tgui-core/components';
+
import { useBackend } from '../backend';
-import { Box, Icon, Section, Stack } from '../components';
import { Window } from '../layouts';
type Data = {
diff --git a/tgui/packages/tgui/interfaces/BankMachine.tsx b/tgui/packages/tgui/interfaces/BankMachine.tsx
index c45cc424fa6b7..005b94da0aad9 100644
--- a/tgui/packages/tgui/interfaces/BankMachine.tsx
+++ b/tgui/packages/tgui/interfaces/BankMachine.tsx
@@ -1,13 +1,13 @@
-import { BooleanLike } from 'common/react';
-
-import { useBackend } from '../backend';
import {
AnimatedNumber,
Button,
LabeledList,
NoticeBox,
Section,
-} from '../components';
+} from 'tgui-core/components';
+import { BooleanLike } from 'tgui-core/react';
+
+import { useBackend } from '../backend';
import { formatMoney } from '../format';
import { Window } from '../layouts';
diff --git a/tgui/packages/tgui/interfaces/BasketballPanel.tsx b/tgui/packages/tgui/interfaces/BasketballPanel.tsx
index 6de429e923464..d5d2865a81aa7 100644
--- a/tgui/packages/tgui/interfaces/BasketballPanel.tsx
+++ b/tgui/packages/tgui/interfaces/BasketballPanel.tsx
@@ -1,5 +1,6 @@
+import { Button, NoticeBox, Section, Stack } from 'tgui-core/components';
+
import { useBackend } from '../backend';
-import { Button, NoticeBox, Section, Stack } from '../components';
import { Window } from '../layouts';
type BasketballPanelData = {
diff --git a/tgui/packages/tgui/interfaces/BattleArcade.tsx b/tgui/packages/tgui/interfaces/BattleArcade.tsx
index a400628575f8b..d455bfe8e528e 100644
--- a/tgui/packages/tgui/interfaces/BattleArcade.tsx
+++ b/tgui/packages/tgui/interfaces/BattleArcade.tsx
@@ -1,6 +1,7 @@
+import { Box, Button, Image, Section } from 'tgui-core/components';
+
import { resolveAsset } from '../assets';
import { useBackend } from '../backend';
-import { Box, Button, Image, Section } from '../components';
import { Window } from '../layouts';
type Data = {
diff --git a/tgui/packages/tgui/interfaces/Biogenerator.tsx b/tgui/packages/tgui/interfaces/Biogenerator.tsx
index 2459740439627..f87eb73d903f9 100644
--- a/tgui/packages/tgui/interfaces/Biogenerator.tsx
+++ b/tgui/packages/tgui/interfaces/Biogenerator.tsx
@@ -1,8 +1,4 @@
-import { BooleanLike } from 'common/react';
-import { classes } from 'common/react';
import { useState } from 'react';
-
-import { useBackend } from '../backend';
import {
Box,
Button,
@@ -15,7 +11,11 @@ import {
Stack,
Table,
Tabs,
-} from '../components';
+} from 'tgui-core/components';
+import { BooleanLike } from 'tgui-core/react';
+import { classes } from 'tgui-core/react';
+
+import { useBackend } from '../backend';
import { Window } from '../layouts';
type Data = {
diff --git a/tgui/packages/tgui/interfaces/BlackMarketUplink.tsx b/tgui/packages/tgui/interfaces/BlackMarketUplink.tsx
index 0f2915f5a0df6..51d397d500cd8 100644
--- a/tgui/packages/tgui/interfaces/BlackMarketUplink.tsx
+++ b/tgui/packages/tgui/interfaces/BlackMarketUplink.tsx
@@ -1,4 +1,3 @@
-import { useBackend } from '../backend';
import {
AnimatedNumber,
Box,
@@ -7,7 +6,9 @@ import {
Section,
Stack,
Tabs,
-} from '../components';
+} from 'tgui-core/components';
+
+import { useBackend } from '../backend';
import { formatMoney } from '../format';
import { Window } from '../layouts';
diff --git a/tgui/packages/tgui/interfaces/BloodFilter.tsx b/tgui/packages/tgui/interfaces/BloodFilter.tsx
index 4e1d553edfd0f..57ac279428731 100644
--- a/tgui/packages/tgui/interfaces/BloodFilter.tsx
+++ b/tgui/packages/tgui/interfaces/BloodFilter.tsx
@@ -1,5 +1,6 @@
+import { Stack } from 'tgui-core/components';
+
import { useBackend } from '../backend';
-import { Stack } from '../components';
import { Window } from '../layouts';
import { ChemFilterPane } from './ChemFilter';
diff --git a/tgui/packages/tgui/interfaces/Blueprints.tsx b/tgui/packages/tgui/interfaces/Blueprints.tsx
index 644412a9a777e..2a2369188c445 100644
--- a/tgui/packages/tgui/interfaces/Blueprints.tsx
+++ b/tgui/packages/tgui/interfaces/Blueprints.tsx
@@ -1,7 +1,7 @@
-import { BooleanLike } from 'common/react';
+import { Box, Button, Divider, Section, Stack } from 'tgui-core/components';
+import { BooleanLike } from 'tgui-core/react';
import { useBackend } from '../backend';
-import { Box, Button, Divider, Section, Stack } from '../components';
import { Window } from '../layouts';
type Data = {
diff --git a/tgui/packages/tgui/interfaces/BluespaceArtillery.tsx b/tgui/packages/tgui/interfaces/BluespaceArtillery.tsx
index dbaf0caa0b264..004407ed30ce2 100644
--- a/tgui/packages/tgui/interfaces/BluespaceArtillery.tsx
+++ b/tgui/packages/tgui/interfaces/BluespaceArtillery.tsx
@@ -1,7 +1,13 @@
-import { BooleanLike } from 'common/react';
+import {
+ Box,
+ Button,
+ LabeledList,
+ NoticeBox,
+ Section,
+} from 'tgui-core/components';
+import { BooleanLike } from 'tgui-core/react';
import { useBackend } from '../backend';
-import { Box, Button, LabeledList, NoticeBox, Section } from '../components';
import { Window } from '../layouts';
type Data = {
diff --git a/tgui/packages/tgui/interfaces/BluespaceLocator.tsx b/tgui/packages/tgui/interfaces/BluespaceLocator.tsx
index 9b08accd34124..5d8f177b4adee 100644
--- a/tgui/packages/tgui/interfaces/BluespaceLocator.tsx
+++ b/tgui/packages/tgui/interfaces/BluespaceLocator.tsx
@@ -1,7 +1,7 @@
import { useState } from 'react';
+import { Icon, ProgressBar, Tabs } from 'tgui-core/components';
import { useBackend } from '../backend';
-import { Icon, ProgressBar, Tabs } from '../components';
import { Window } from '../layouts';
type Data = {
diff --git a/tgui/packages/tgui/interfaces/BluespaceSender.tsx b/tgui/packages/tgui/interfaces/BluespaceSender.tsx
index 2f94af4cfaa9d..823090eeb4eb6 100644
--- a/tgui/packages/tgui/interfaces/BluespaceSender.tsx
+++ b/tgui/packages/tgui/interfaces/BluespaceSender.tsx
@@ -1,8 +1,4 @@
import { filter, sortBy } from 'common/collections';
-import { toFixed } from 'common/math';
-import { BooleanLike } from 'common/react';
-
-import { useBackend } from '../backend';
import {
Box,
Button,
@@ -12,7 +8,11 @@ import {
ProgressBar,
Section,
Stack,
-} from '../components';
+} from 'tgui-core/components';
+import { toFixed } from 'tgui-core/math';
+import { BooleanLike } from 'tgui-core/react';
+
+import { useBackend } from '../backend';
import { getGasColor } from '../constants';
import { Window } from '../layouts';
diff --git a/tgui/packages/tgui/interfaces/BluespaceVendor.tsx b/tgui/packages/tgui/interfaces/BluespaceVendor.tsx
index 7b9619de26e55..76f6594e45565 100644
--- a/tgui/packages/tgui/interfaces/BluespaceVendor.tsx
+++ b/tgui/packages/tgui/interfaces/BluespaceVendor.tsx
@@ -1,15 +1,15 @@
import { filter, sortBy } from 'common/collections';
-import { toFixed } from 'common/math';
-import { BooleanLike } from 'common/react';
-
-import { useBackend } from '../backend';
import {
Button,
NumberInput,
ProgressBar,
Section,
Stack,
-} from '../components';
+} from 'tgui-core/components';
+import { toFixed } from 'tgui-core/math';
+import { BooleanLike } from 'tgui-core/react';
+
+import { useBackend } from '../backend';
import { Table, TableCell, TableRow } from '../components/Table';
import { getGasColor } from '../constants';
import { Window } from '../layouts';
diff --git a/tgui/packages/tgui/interfaces/BorgHypo.tsx b/tgui/packages/tgui/interfaces/BorgHypo.tsx
index 8fa8d2c8131b3..bafa2fc302c5d 100644
--- a/tgui/packages/tgui/interfaces/BorgHypo.tsx
+++ b/tgui/packages/tgui/interfaces/BorgHypo.tsx
@@ -1,7 +1,13 @@
-import { toFixed } from 'common/math';
+import {
+ Button,
+ Flex,
+ NoticeBox,
+ ProgressBar,
+ Section,
+} from 'tgui-core/components';
+import { toFixed } from 'tgui-core/math';
import { useBackend } from '../backend';
-import { Button, Flex, NoticeBox, ProgressBar, Section } from '../components';
import { Window } from '../layouts';
type BorgHypoContext = {
diff --git a/tgui/packages/tgui/interfaces/BorgPanel.jsx b/tgui/packages/tgui/interfaces/BorgPanel.jsx
index 96b536af0aa81..b9ef7ed45c5b6 100644
--- a/tgui/packages/tgui/interfaces/BorgPanel.jsx
+++ b/tgui/packages/tgui/interfaces/BorgPanel.jsx
@@ -1,5 +1,12 @@
+import {
+ Box,
+ Button,
+ LabeledList,
+ ProgressBar,
+ Section,
+} from 'tgui-core/components';
+
import { useBackend } from '../backend';
-import { Box, Button, LabeledList, ProgressBar, Section } from '../components';
import { Window } from '../layouts';
export const BorgPanel = (props) => {
diff --git a/tgui/packages/tgui/interfaces/BorgShaker.tsx b/tgui/packages/tgui/interfaces/BorgShaker.tsx
index c77bf32ea8c38..b16a68a39181c 100644
--- a/tgui/packages/tgui/interfaces/BorgShaker.tsx
+++ b/tgui/packages/tgui/interfaces/BorgShaker.tsx
@@ -1,5 +1,6 @@
+import { Button, NoticeBox, Section } from 'tgui-core/components';
+
import { useBackend } from '../backend';
-import { Button, NoticeBox, Section } from '../components';
import { Window } from '../layouts';
type BorgShakerContext = {
diff --git a/tgui/packages/tgui/interfaces/BotAnnouncement.tsx b/tgui/packages/tgui/interfaces/BotAnnouncement.tsx
index 3292e81a0e75f..4064df6ffd6f0 100644
--- a/tgui/packages/tgui/interfaces/BotAnnouncement.tsx
+++ b/tgui/packages/tgui/interfaces/BotAnnouncement.tsx
@@ -1,7 +1,4 @@
-import { createSearch } from 'common/string';
import { useState } from 'react';
-
-import { useBackend } from '../backend';
import {
Box,
Button,
@@ -11,7 +8,10 @@ import {
Section,
Stack,
Tabs,
-} from '../components';
+} from 'tgui-core/components';
+import { createSearch } from 'tgui-core/string';
+
+import { useBackend } from '../backend';
import { RADIO_CHANNELS } from '../constants';
import { Window } from '../layouts';
diff --git a/tgui/packages/tgui/interfaces/BountyBoard.tsx b/tgui/packages/tgui/interfaces/BountyBoard.tsx
index 2f271bfb4f9de..9b892b3ca7023 100644
--- a/tgui/packages/tgui/interfaces/BountyBoard.tsx
+++ b/tgui/packages/tgui/interfaces/BountyBoard.tsx
@@ -1,4 +1,3 @@
-import { useBackend } from '../backend';
import {
BlockQuote,
Box,
@@ -9,7 +8,9 @@ import {
Section,
Stack,
TextArea,
-} from '../components';
+} from 'tgui-core/components';
+
+import { useBackend } from '../backend';
import { formatMoney } from '../format';
import { Window } from '../layouts';
import { UserDetails } from './Vending';
diff --git a/tgui/packages/tgui/interfaces/BrigTimer.tsx b/tgui/packages/tgui/interfaces/BrigTimer.tsx
index 18527b9aab470..2069b5a8626e5 100644
--- a/tgui/packages/tgui/interfaces/BrigTimer.tsx
+++ b/tgui/packages/tgui/interfaces/BrigTimer.tsx
@@ -1,7 +1,7 @@
-import { BooleanLike } from 'common/react';
+import { Button, Section } from 'tgui-core/components';
+import { BooleanLike } from 'tgui-core/react';
import { useBackend } from '../backend';
-import { Button, Section } from '../components';
import { Window } from '../layouts';
type Data = {
diff --git a/tgui/packages/tgui/interfaces/CTFPanel.tsx b/tgui/packages/tgui/interfaces/CTFPanel.tsx
index b4760bb619d87..7dbfa75296401 100644
--- a/tgui/packages/tgui/interfaces/CTFPanel.tsx
+++ b/tgui/packages/tgui/interfaces/CTFPanel.tsx
@@ -1,6 +1,7 @@
+import { Box, Button, Flex, Section, Stack } from 'tgui-core/components';
+
import { BooleanLike } from '../../common/react';
import { useBackend } from '../backend';
-import { Box, Button, Flex, Section, Stack } from '../components';
import { Window } from '../layouts';
type CTFPanelData =
diff --git a/tgui/packages/tgui/interfaces/CameraConsole.tsx b/tgui/packages/tgui/interfaces/CameraConsole.tsx
index f28c308f1b5b5..b47f043e3eb5f 100644
--- a/tgui/packages/tgui/interfaces/CameraConsole.tsx
+++ b/tgui/packages/tgui/interfaces/CameraConsole.tsx
@@ -1,6 +1,4 @@
import { filter, sort } from 'common/collections';
-import { BooleanLike, classes } from 'common/react';
-import { createSearch } from 'common/string';
import { useState } from 'react';
import {
Button,
@@ -10,6 +8,8 @@ import {
Section,
Stack,
} from 'tgui-core/components';
+import { BooleanLike, classes } from 'tgui-core/react';
+import { createSearch } from 'tgui-core/string';
import { useBackend } from '../backend';
import { Window } from '../layouts';
diff --git a/tgui/packages/tgui/interfaces/Canister.tsx b/tgui/packages/tgui/interfaces/Canister.tsx
index f650085df463e..7a2d63f734542 100644
--- a/tgui/packages/tgui/interfaces/Canister.tsx
+++ b/tgui/packages/tgui/interfaces/Canister.tsx
@@ -1,7 +1,3 @@
-import { toFixed } from 'common/math';
-import { BooleanLike } from 'common/react';
-
-import { useBackend } from '../backend';
import {
Box,
Button,
@@ -13,7 +9,11 @@ import {
RoundGauge,
Section,
Tooltip,
-} from '../components';
+} from 'tgui-core/components';
+import { toFixed } from 'tgui-core/math';
+import { BooleanLike } from 'tgui-core/react';
+
+import { useBackend } from '../backend';
import { formatSiUnit } from '../format';
import { Window } from '../layouts';
diff --git a/tgui/packages/tgui/interfaces/Canvas.tsx b/tgui/packages/tgui/interfaces/Canvas.tsx
index 648418e7abd06..3621c12eb3cb7 100644
--- a/tgui/packages/tgui/interfaces/Canvas.tsx
+++ b/tgui/packages/tgui/interfaces/Canvas.tsx
@@ -1,9 +1,9 @@
-import { Color } from 'common/color';
-import { decodeHtmlEntities } from 'common/string';
import { Component, createRef, RefObject } from 'react';
+import { Color } from 'tgui-core/color';
+import { Box, Button, Flex, Icon, Tooltip } from 'tgui-core/components';
+import { decodeHtmlEntities } from 'tgui-core/string';
import { useBackend } from '../backend';
-import { Box, Button, Flex, Icon, Tooltip } from '../components';
import { Window } from '../layouts';
const LEFT_CLICK = 0;
diff --git a/tgui/packages/tgui/interfaces/CargoExpress.tsx b/tgui/packages/tgui/interfaces/CargoExpress.tsx
index 83947b5adc473..9115695645516 100644
--- a/tgui/packages/tgui/interfaces/CargoExpress.tsx
+++ b/tgui/packages/tgui/interfaces/CargoExpress.tsx
@@ -1,13 +1,13 @@
-import { BooleanLike } from 'common/react';
-
-import { useBackend } from '../backend';
import {
AnimatedNumber,
Box,
Button,
LabeledList,
Section,
-} from '../components';
+} from 'tgui-core/components';
+import { BooleanLike } from 'tgui-core/react';
+
+import { useBackend } from '../backend';
import { Window } from '../layouts';
import { CargoCatalog } from './Cargo/CargoCatalog';
import { InterfaceLockNoticeBox } from './common/InterfaceLockNoticeBox';
diff --git a/tgui/packages/tgui/interfaces/CargoHoldTerminal.tsx b/tgui/packages/tgui/interfaces/CargoHoldTerminal.tsx
index 6a392a3840208..818706d57e75b 100644
--- a/tgui/packages/tgui/interfaces/CargoHoldTerminal.tsx
+++ b/tgui/packages/tgui/interfaces/CargoHoldTerminal.tsx
@@ -1,13 +1,13 @@
-import { BooleanLike } from 'common/react';
-
-import { useBackend } from '../backend';
import {
AnimatedNumber,
Box,
Button,
LabeledList,
Section,
-} from '../components';
+} from 'tgui-core/components';
+import { BooleanLike } from 'tgui-core/react';
+
+import { useBackend } from '../backend';
import { Window } from '../layouts';
type Data = {
diff --git a/tgui/packages/tgui/interfaces/CellularEmporium.tsx b/tgui/packages/tgui/interfaces/CellularEmporium.tsx
index 7f12a5012cb78..41c5b8b3dae19 100644
--- a/tgui/packages/tgui/interfaces/CellularEmporium.tsx
+++ b/tgui/packages/tgui/interfaces/CellularEmporium.tsx
@@ -1,7 +1,4 @@
import { useState } from 'react';
-
-import { BooleanLike } from '../../common/react';
-import { useBackend } from '../backend';
import {
Box,
Button,
@@ -11,7 +8,10 @@ import {
NoticeBox,
Section,
Stack,
-} from '../components';
+} from 'tgui-core/components';
+
+import { BooleanLike } from '../../common/react';
+import { useBackend } from '../backend';
import { Window } from '../layouts';
type typePath = string;
diff --git a/tgui/packages/tgui/interfaces/Changelog.jsx b/tgui/packages/tgui/interfaces/Changelog.jsx
index 8af62faa84859..7c1ed8b933c47 100644
--- a/tgui/packages/tgui/interfaces/Changelog.jsx
+++ b/tgui/packages/tgui/interfaces/Changelog.jsx
@@ -1,10 +1,6 @@
-import { classes } from 'common/react';
import dateformat from 'dateformat';
import yaml from 'js-yaml';
import { Component, Fragment } from 'react';
-
-import { resolveAsset } from '../assets';
-import { useBackend } from '../backend';
import {
Box,
Button,
@@ -13,7 +9,11 @@ import {
Section,
Stack,
Table,
-} from '../components';
+} from 'tgui-core/components';
+import { classes } from 'tgui-core/react';
+
+import { resolveAsset } from '../assets';
+import { useBackend } from '../backend';
import { Window } from '../layouts';
const icons = {
diff --git a/tgui/packages/tgui/interfaces/CheckboxInput.tsx b/tgui/packages/tgui/interfaces/CheckboxInput.tsx
index ab98a8a1f02a2..6ff49847df41f 100644
--- a/tgui/packages/tgui/interfaces/CheckboxInput.tsx
+++ b/tgui/packages/tgui/interfaces/CheckboxInput.tsx
@@ -1,7 +1,4 @@
-import { createSearch, decodeHtmlEntities } from 'common/string';
import { useState } from 'react';
-
-import { useBackend } from '../backend';
import {
Button,
Icon,
@@ -11,7 +8,10 @@ import {
Stack,
Table,
Tooltip,
-} from '../components';
+} from 'tgui-core/components';
+import { createSearch, decodeHtmlEntities } from 'tgui-core/string';
+
+import { useBackend } from '../backend';
import { TableCell, TableRow } from '../components/Table';
import { Window } from '../layouts';
import { InputButtons } from './common/InputButtons';
diff --git a/tgui/packages/tgui/interfaces/ChemAcclimator.tsx b/tgui/packages/tgui/interfaces/ChemAcclimator.tsx
index 622b7055ddf1b..f400030bf23a8 100644
--- a/tgui/packages/tgui/interfaces/ChemAcclimator.tsx
+++ b/tgui/packages/tgui/interfaces/ChemAcclimator.tsx
@@ -1,7 +1,12 @@
-import { BooleanLike } from 'common/react';
+import {
+ Button,
+ LabeledList,
+ NumberInput,
+ Section,
+} from 'tgui-core/components';
+import { BooleanLike } from 'tgui-core/react';
import { useBackend } from '../backend';
-import { Button, LabeledList, NumberInput, Section } from '../components';
import { Window } from '../layouts';
type Data = {
diff --git a/tgui/packages/tgui/interfaces/ChemDebugSynthesizer.tsx b/tgui/packages/tgui/interfaces/ChemDebugSynthesizer.tsx
index 06af6c1727f03..b0a5b7a7681af 100644
--- a/tgui/packages/tgui/interfaces/ChemDebugSynthesizer.tsx
+++ b/tgui/packages/tgui/interfaces/ChemDebugSynthesizer.tsx
@@ -1,5 +1,6 @@
+import { Button, NumberInput, Section } from 'tgui-core/components';
+
import { useBackend } from '../backend';
-import { Button, NumberInput, Section } from '../components';
import { Window } from '../layouts';
import { Beaker, BeakerDisplay } from './common/BeakerDisplay';
diff --git a/tgui/packages/tgui/interfaces/ChemDispenser.tsx b/tgui/packages/tgui/interfaces/ChemDispenser.tsx
index 3e0174a3b2f54..421c9353e0983 100644
--- a/tgui/packages/tgui/interfaces/ChemDispenser.tsx
+++ b/tgui/packages/tgui/interfaces/ChemDispenser.tsx
@@ -1,5 +1,3 @@
-import { BooleanLike } from 'common/react';
-import { toTitleCase } from 'common/string';
import { useState } from 'react';
import {
Box,
@@ -9,6 +7,8 @@ import {
ProgressBar,
Section,
} from 'tgui-core/components';
+import { BooleanLike } from 'tgui-core/react';
+import { toTitleCase } from 'tgui-core/string';
import { useBackend } from '../backend';
import { Window } from '../layouts';
diff --git a/tgui/packages/tgui/interfaces/ChemFilter.tsx b/tgui/packages/tgui/interfaces/ChemFilter.tsx
index 7615ec7de7b6c..f9ccb78fcb912 100644
--- a/tgui/packages/tgui/interfaces/ChemFilter.tsx
+++ b/tgui/packages/tgui/interfaces/ChemFilter.tsx
@@ -1,7 +1,7 @@
import { Fragment } from 'react';
+import { Button, Section, Stack } from 'tgui-core/components';
import { useBackend } from '../backend';
-import { Button, Section, Stack } from '../components';
import { CssColor } from '../constants';
import { Window } from '../layouts';
diff --git a/tgui/packages/tgui/interfaces/ChemHeater.tsx b/tgui/packages/tgui/interfaces/ChemHeater.tsx
index adf8020ba21b5..08df90ad4bd28 100644
--- a/tgui/packages/tgui/interfaces/ChemHeater.tsx
+++ b/tgui/packages/tgui/interfaces/ChemHeater.tsx
@@ -1,6 +1,3 @@
-import { round, toFixed } from '../../common/math';
-import { BooleanLike } from '../../common/react';
-import { useBackend } from '../backend';
import {
AnimatedNumber,
Box,
@@ -12,7 +9,11 @@ import {
RoundGauge,
Section,
Table,
-} from '../components';
+} from 'tgui-core/components';
+
+import { round, toFixed } from '../../common/math';
+import { BooleanLike } from '../../common/react';
+import { useBackend } from '../backend';
import { COLORS } from '../constants';
import { Window } from '../layouts';
import { Beaker, BeakerSectionDisplay } from './common/BeakerDisplay';
diff --git a/tgui/packages/tgui/interfaces/ChemMaster.tsx b/tgui/packages/tgui/interfaces/ChemMaster.tsx
index c23ca29f8d9ec..62692588c6516 100644
--- a/tgui/packages/tgui/interfaces/ChemMaster.tsx
+++ b/tgui/packages/tgui/interfaces/ChemMaster.tsx
@@ -1,8 +1,4 @@
-import { BooleanLike, classes } from 'common/react';
-import { capitalize } from 'common/string';
import { useState } from 'react';
-
-import { useBackend } from '../backend';
import {
AnimatedNumber,
Box,
@@ -16,7 +12,11 @@ import {
Stack,
Table,
Tooltip,
-} from '../components';
+} from 'tgui-core/components';
+import { BooleanLike, classes } from 'tgui-core/react';
+import { capitalize } from 'tgui-core/string';
+
+import { useBackend } from '../backend';
import { Window } from '../layouts';
import { Beaker, BeakerReagent } from './common/BeakerDisplay';
diff --git a/tgui/packages/tgui/interfaces/ChemMixingChamber.tsx b/tgui/packages/tgui/interfaces/ChemMixingChamber.tsx
index a82fad9d465ea..765862ab4b86c 100644
--- a/tgui/packages/tgui/interfaces/ChemMixingChamber.tsx
+++ b/tgui/packages/tgui/interfaces/ChemMixingChamber.tsx
@@ -1,8 +1,4 @@
-import { round, toFixed } from 'common/math';
-import { BooleanLike } from 'common/react';
import { useState } from 'react';
-
-import { useBackend } from '../backend';
import {
AnimatedNumber,
Box,
@@ -10,7 +6,11 @@ import {
NumberInput,
Section,
Stack,
-} from '../components';
+} from 'tgui-core/components';
+import { round, toFixed } from 'tgui-core/math';
+import { BooleanLike } from 'tgui-core/react';
+
+import { useBackend } from '../backend';
import { Window } from '../layouts';
type Reagent = {
diff --git a/tgui/packages/tgui/interfaces/ChemPress.tsx b/tgui/packages/tgui/interfaces/ChemPress.tsx
index 58e578f8027a4..ce54f04c8a562 100644
--- a/tgui/packages/tgui/interfaces/ChemPress.tsx
+++ b/tgui/packages/tgui/interfaces/ChemPress.tsx
@@ -1,7 +1,4 @@
-import { capitalizeAll } from 'common/string';
import { useState } from 'react';
-
-import { useBackend } from '../backend';
import {
Box,
Button,
@@ -9,7 +6,10 @@ import {
LabeledList,
NumberInput,
Section,
-} from '../components';
+} from 'tgui-core/components';
+import { capitalizeAll } from 'tgui-core/string';
+
+import { useBackend } from '../backend';
import { Window } from '../layouts';
type Product = {
diff --git a/tgui/packages/tgui/interfaces/ChemReactionChamber.tsx b/tgui/packages/tgui/interfaces/ChemReactionChamber.tsx
index 8104065c88f12..cdc2b6fac97b9 100644
--- a/tgui/packages/tgui/interfaces/ChemReactionChamber.tsx
+++ b/tgui/packages/tgui/interfaces/ChemReactionChamber.tsx
@@ -1,7 +1,4 @@
-import { round, toFixed } from 'common/math';
import { useState } from 'react';
-
-import { useBackend } from '../backend';
import {
AnimatedNumber,
Box,
@@ -11,7 +8,10 @@ import {
RoundGauge,
Section,
Stack,
-} from '../components';
+} from 'tgui-core/components';
+import { round, toFixed } from 'tgui-core/math';
+
+import { useBackend } from '../backend';
import { Window } from '../layouts';
import { MixingData } from './ChemMixingChamber';
diff --git a/tgui/packages/tgui/interfaces/ChemRecipeDebug.tsx b/tgui/packages/tgui/interfaces/ChemRecipeDebug.tsx
index 5ebdd7d1e0832..4fe2340757e3c 100644
--- a/tgui/packages/tgui/interfaces/ChemRecipeDebug.tsx
+++ b/tgui/packages/tgui/interfaces/ChemRecipeDebug.tsx
@@ -1,7 +1,4 @@
-import { BooleanLike } from 'common/react';
import { useState } from 'react';
-
-import { useBackend } from '../backend';
import {
Box,
Button,
@@ -11,7 +8,10 @@ import {
Section,
Stack,
Tabs,
-} from '../components';
+} from 'tgui-core/components';
+import { BooleanLike } from 'tgui-core/react';
+
+import { useBackend } from '../backend';
import { Window } from '../layouts';
import { ActiveReaction, ReactionDisplay } from './ChemHeater';
import { Beaker, BeakerSectionDisplay } from './common/BeakerDisplay';
diff --git a/tgui/packages/tgui/interfaces/ChemSeparator.tsx b/tgui/packages/tgui/interfaces/ChemSeparator.tsx
index e6be430466f70..6a6a83a771baa 100644
--- a/tgui/packages/tgui/interfaces/ChemSeparator.tsx
+++ b/tgui/packages/tgui/interfaces/ChemSeparator.tsx
@@ -1,6 +1,3 @@
-import { BooleanLike } from 'common/react';
-
-import { useBackend } from '../backend';
import {
Box,
Button,
@@ -8,7 +5,10 @@ import {
LabeledList,
ProgressBar,
Stack,
-} from '../components';
+} from 'tgui-core/components';
+import { BooleanLike } from 'tgui-core/react';
+
+import { useBackend } from '../backend';
import { Window } from '../layouts';
type RegHolderData = {
diff --git a/tgui/packages/tgui/interfaces/ChemSplitter.tsx b/tgui/packages/tgui/interfaces/ChemSplitter.tsx
index 75089e9a96151..688cfa28020e9 100644
--- a/tgui/packages/tgui/interfaces/ChemSplitter.tsx
+++ b/tgui/packages/tgui/interfaces/ChemSplitter.tsx
@@ -1,7 +1,7 @@
-import { toFixed } from 'common/math';
+import { LabeledList, NumberInput, Section } from 'tgui-core/components';
+import { toFixed } from 'tgui-core/math';
import { useBackend } from '../backend';
-import { LabeledList, NumberInput, Section } from '../components';
import { Window } from '../layouts';
type Data = {
diff --git a/tgui/packages/tgui/interfaces/ChemSynthesizer.tsx b/tgui/packages/tgui/interfaces/ChemSynthesizer.tsx
index 898ac07b2928b..440460c5d1ce0 100644
--- a/tgui/packages/tgui/interfaces/ChemSynthesizer.tsx
+++ b/tgui/packages/tgui/interfaces/ChemSynthesizer.tsx
@@ -1,7 +1,7 @@
-import { toFixed } from 'common/math';
+import { Box, Button, Section } from 'tgui-core/components';
+import { toFixed } from 'tgui-core/math';
import { useBackend } from '../backend';
-import { Box, Button, Section } from '../components';
import { Window } from '../layouts';
type Data = {
diff --git a/tgui/packages/tgui/interfaces/CircuitAccessChecker.tsx b/tgui/packages/tgui/interfaces/CircuitAccessChecker.tsx
index f37414a86f52f..baddb0ea65efd 100644
--- a/tgui/packages/tgui/interfaces/CircuitAccessChecker.tsx
+++ b/tgui/packages/tgui/interfaces/CircuitAccessChecker.tsx
@@ -1,7 +1,7 @@
-import { BooleanLike } from 'common/react';
+import { Button, LabeledList } from 'tgui-core/components';
+import { BooleanLike } from 'tgui-core/react';
import { useBackend } from '../backend';
-import { Button, LabeledList } from '../components';
import { Window } from '../layouts';
import { AccessConfig, Region } from './common/AccessConfig';
diff --git a/tgui/packages/tgui/interfaces/CircuitAdminPanel.tsx b/tgui/packages/tgui/interfaces/CircuitAdminPanel.tsx
index e95c7714dcd7c..8b4628bb90005 100644
--- a/tgui/packages/tgui/interfaces/CircuitAdminPanel.tsx
+++ b/tgui/packages/tgui/interfaces/CircuitAdminPanel.tsx
@@ -1,7 +1,7 @@
-import { BooleanLike } from 'common/react';
+import { Button, Stack, Table } from 'tgui-core/components';
+import { BooleanLike } from 'tgui-core/react';
import { useBackend } from '../backend';
-import { Button, Stack, Table } from '../components';
import { Window } from '../layouts';
type CircuitAdminPanelData = {
diff --git a/tgui/packages/tgui/interfaces/CircuitModule.jsx b/tgui/packages/tgui/interfaces/CircuitModule.jsx
index bbb323e5e5a7e..9aa3a61664424 100644
--- a/tgui/packages/tgui/interfaces/CircuitModule.jsx
+++ b/tgui/packages/tgui/interfaces/CircuitModule.jsx
@@ -1,5 +1,6 @@
+import { Button, Dropdown, Input, Section, Stack } from 'tgui-core/components';
+
import { useBackend } from '../backend';
-import { Button, Dropdown, Input, Section, Stack } from '../components';
import { Window } from '../layouts';
export const CircuitModule = (props) => {
diff --git a/tgui/packages/tgui/interfaces/CircuitSignalHandler.tsx b/tgui/packages/tgui/interfaces/CircuitSignalHandler.tsx
index 66d04644391c3..43e4895d06bbe 100644
--- a/tgui/packages/tgui/interfaces/CircuitSignalHandler.tsx
+++ b/tgui/packages/tgui/interfaces/CircuitSignalHandler.tsx
@@ -1,8 +1,15 @@
-import { BooleanLike } from 'common/react';
import { Component, KeyboardEvent, MouseEvent } from 'react';
+import {
+ Box,
+ Button,
+ Dropdown,
+ Input,
+ Section,
+ Stack,
+} from 'tgui-core/components';
+import { BooleanLike } from 'tgui-core/react';
import { useBackend } from '../backend';
-import { Box, Button, Dropdown, Input, Section, Stack } from '../components';
import { Window } from '../layouts';
type Response = {
diff --git a/tgui/packages/tgui/interfaces/CivCargoHoldTerminal.jsx b/tgui/packages/tgui/interfaces/CivCargoHoldTerminal.jsx
index 438030e39472c..a1cceecc7d77f 100644
--- a/tgui/packages/tgui/interfaces/CivCargoHoldTerminal.jsx
+++ b/tgui/packages/tgui/interfaces/CivCargoHoldTerminal.jsx
@@ -1,4 +1,3 @@
-import { useBackend } from '../backend';
import {
Box,
Button,
@@ -6,7 +5,9 @@ import {
LabeledList,
NoticeBox,
Section,
-} from '../components';
+} from 'tgui-core/components';
+
+import { useBackend } from '../backend';
import { Window } from '../layouts';
export const CivCargoHoldTerminal = (props) => {
diff --git a/tgui/packages/tgui/interfaces/Clipboard.tsx b/tgui/packages/tgui/interfaces/Clipboard.tsx
index 67cb86a1a984a..119b0e800429d 100644
--- a/tgui/packages/tgui/interfaces/Clipboard.tsx
+++ b/tgui/packages/tgui/interfaces/Clipboard.tsx
@@ -1,6 +1,3 @@
-import { BooleanLike } from 'common/react';
-
-import { useBackend } from '../backend';
import {
Box,
Button,
@@ -8,7 +5,10 @@ import {
Flex,
LabeledList,
Section,
-} from '../components';
+} from 'tgui-core/components';
+import { BooleanLike } from 'tgui-core/react';
+
+import { useBackend } from '../backend';
import { Window } from '../layouts';
type Data = {
diff --git a/tgui/packages/tgui/interfaces/CodexGigas.tsx b/tgui/packages/tgui/interfaces/CodexGigas.tsx
index cd0f3dc55e0bf..581341fb67acb 100644
--- a/tgui/packages/tgui/interfaces/CodexGigas.tsx
+++ b/tgui/packages/tgui/interfaces/CodexGigas.tsx
@@ -1,5 +1,6 @@
+import { Button, LabeledList, Section } from 'tgui-core/components';
+
import { useBackend } from '../backend';
-import { Button, LabeledList, Section } from '../components';
import { Window } from '../layouts';
type Data = {
diff --git a/tgui/packages/tgui/interfaces/ColorBlindTester.tsx b/tgui/packages/tgui/interfaces/ColorBlindTester.tsx
index 3f33edcedc78b..be0b89cd0e6a1 100644
--- a/tgui/packages/tgui/interfaces/ColorBlindTester.tsx
+++ b/tgui/packages/tgui/interfaces/ColorBlindTester.tsx
@@ -1,5 +1,6 @@
+import { Box, Button, NoticeBox, Section } from 'tgui-core/components';
+
import { useBackend } from '../backend';
-import { Box, Button, NoticeBox, Section } from '../components';
import { Window } from '../layouts';
type Data = {
diff --git a/tgui/packages/tgui/interfaces/ColorMatrixEditor.tsx b/tgui/packages/tgui/interfaces/ColorMatrixEditor.tsx
index edf75dcf161d9..c0d515b4d2726 100644
--- a/tgui/packages/tgui/interfaces/ColorMatrixEditor.tsx
+++ b/tgui/packages/tgui/interfaces/ColorMatrixEditor.tsx
@@ -1,6 +1,3 @@
-import { toFixed } from 'common/math';
-
-import { useBackend } from '../backend';
import {
Box,
Button,
@@ -8,7 +5,10 @@ import {
NumberInput,
Section,
Stack,
-} from '../components';
+} from 'tgui-core/components';
+import { toFixed } from 'tgui-core/math';
+
+import { useBackend } from '../backend';
import { Window } from '../layouts';
type Data = {
diff --git a/tgui/packages/tgui/interfaces/CommandReport.tsx b/tgui/packages/tgui/interfaces/CommandReport.tsx
index 9185bebb5b4cd..8153ab81c7266 100644
--- a/tgui/packages/tgui/interfaces/CommandReport.tsx
+++ b/tgui/packages/tgui/interfaces/CommandReport.tsx
@@ -1,6 +1,4 @@
import { useState } from 'react';
-
-import { useBackend } from '../backend';
import {
Box,
Button,
@@ -9,7 +7,9 @@ import {
Section,
Stack,
TextArea,
-} from '../components';
+} from 'tgui-core/components';
+
+import { useBackend } from '../backend';
import { Window } from '../layouts';
type Data = {
diff --git a/tgui/packages/tgui/interfaces/CommunicationsConsole.jsx b/tgui/packages/tgui/interfaces/CommunicationsConsole.jsx
index 48a7d98a40155..7affe96bf198b 100644
--- a/tgui/packages/tgui/interfaces/CommunicationsConsole.jsx
+++ b/tgui/packages/tgui/interfaces/CommunicationsConsole.jsx
@@ -1,8 +1,5 @@
import { sortBy } from 'common/collections';
-import { capitalize } from 'common/string';
import { useState } from 'react';
-
-import { useBackend, useLocalState } from '../backend';
import {
Blink,
Box,
@@ -13,7 +10,10 @@ import {
Modal,
Section,
TextArea,
-} from '../components';
+} from 'tgui-core/components';
+import { capitalize } from 'tgui-core/string';
+
+import { useBackend, useLocalState } from '../backend';
import { Window } from '../layouts';
import { sanitizeText } from '../sanitize';
import { StatusDisplayControls } from './common/StatusDisplayControls';
diff --git a/tgui/packages/tgui/interfaces/ComponentPrinter.tsx b/tgui/packages/tgui/interfaces/ComponentPrinter.tsx
index 7c8f16a8c5350..9baf102e9513a 100644
--- a/tgui/packages/tgui/interfaces/ComponentPrinter.tsx
+++ b/tgui/packages/tgui/interfaces/ComponentPrinter.tsx
@@ -1,7 +1,7 @@
-import { classes } from 'common/react';
+import { Box, Icon, Section, Stack, Tooltip } from 'tgui-core/components';
+import { classes } from 'tgui-core/react';
import { useBackend } from '../backend';
-import { Box, Icon, Section, Stack, Tooltip } from '../components';
import { Window } from '../layouts';
import { DesignBrowser } from './Fabrication/DesignBrowser';
import { MaterialAccessBar } from './Fabrication/MaterialAccessBar';
diff --git a/tgui/packages/tgui/interfaces/Crayon.tsx b/tgui/packages/tgui/interfaces/Crayon.tsx
index 27a36f745cd39..333f02f47cd50 100644
--- a/tgui/packages/tgui/interfaces/Crayon.tsx
+++ b/tgui/packages/tgui/interfaces/Crayon.tsx
@@ -1,7 +1,7 @@
-import { BooleanLike } from 'common/react';
+import { Button, LabeledList, Section } from 'tgui-core/components';
+import { BooleanLike } from 'tgui-core/react';
import { useBackend } from '../backend';
-import { Button, LabeledList, Section } from '../components';
import { Window } from '../layouts';
import { ColorItem } from './RapidPipeDispenser';
diff --git a/tgui/packages/tgui/interfaces/CrewConsole.tsx b/tgui/packages/tgui/interfaces/CrewConsole.tsx
index 7394d9964fde6..9c15a551bb0ea 100644
--- a/tgui/packages/tgui/interfaces/CrewConsole.tsx
+++ b/tgui/packages/tgui/interfaces/CrewConsole.tsx
@@ -1,7 +1,7 @@
-import { BooleanLike } from 'common/react';
-import { createSearch } from 'common/string';
import { useState } from 'react';
import { Box, Button, Icon, Input, Section, Table } from 'tgui-core/components';
+import { BooleanLike } from 'tgui-core/react';
+import { createSearch } from 'tgui-core/string';
import { useBackend } from '../backend';
import { COLORS } from '../constants';
@@ -26,7 +26,7 @@ const SORT_NAMES = {
const STAT_LIVING = 0;
const STAT_DEAD = 4;
-const SORT_OPTIONS = ['ijob', 'name', 'area', 'health'];
+const SORT_OPTIONS = ['health', 'ijob', 'name', 'area'];
const jobIsHead = (jobId: number) => jobId % 10 === 0;
@@ -69,10 +69,10 @@ const statToIcon = (life_status: number) => {
};
const healthSort = (a: CrewSensor, b: CrewSensor) => {
- if (a.life_status < b.life_status) return -1;
- if (a.life_status > b.life_status) return 1;
- if (a.health > b.health) return -1;
- if (a.health < b.health) return 1;
+ if (a.life_status > b.life_status) return -1;
+ if (a.life_status < b.life_status) return 1;
+ if (a.health < b.health) return -1;
+ if (a.health > b.health) return 1;
return 0;
};
diff --git a/tgui/packages/tgui/interfaces/CrewManifest.jsx b/tgui/packages/tgui/interfaces/CrewManifest.jsx
index d550260087bea..4039eb8fc3a2f 100644
--- a/tgui/packages/tgui/interfaces/CrewManifest.jsx
+++ b/tgui/packages/tgui/interfaces/CrewManifest.jsx
@@ -1,7 +1,7 @@
-import { classes } from 'common/react';
+import { Icon, Section, Table, Tooltip } from 'tgui-core/components';
+import { classes } from 'tgui-core/react';
import { useBackend } from '../backend';
-import { Icon, Section, Table, Tooltip } from '../components';
import { Window } from '../layouts';
const commandJobs = [
diff --git a/tgui/packages/tgui/interfaces/CrossingSignal.tsx b/tgui/packages/tgui/interfaces/CrossingSignal.tsx
index e0585318649a0..ecdc52f6a1d72 100644
--- a/tgui/packages/tgui/interfaces/CrossingSignal.tsx
+++ b/tgui/packages/tgui/interfaces/CrossingSignal.tsx
@@ -1,7 +1,7 @@
-import { BooleanLike } from 'common/react';
+import { LabeledList, Section } from 'tgui-core/components';
+import { BooleanLike } from 'tgui-core/react';
import { useBackend } from '../backend';
-import { LabeledList, Section } from '../components';
import { Window } from '../layouts';
type Data = {
diff --git a/tgui/packages/tgui/interfaces/Cryo.tsx b/tgui/packages/tgui/interfaces/Cryo.tsx
index 2999f4c04d8de..a4c2d36801733 100644
--- a/tgui/packages/tgui/interfaces/Cryo.tsx
+++ b/tgui/packages/tgui/interfaces/Cryo.tsx
@@ -1,14 +1,14 @@
-import { round } from 'common/math';
-import { BooleanLike } from 'common/react';
-
-import { useBackend } from '../backend';
import {
AnimatedNumber,
Button,
LabeledList,
ProgressBar,
Section,
-} from '../components';
+} from 'tgui-core/components';
+import { round } from 'tgui-core/math';
+import { BooleanLike } from 'tgui-core/react';
+
+import { useBackend } from '../backend';
import { Window } from '../layouts';
import { Beaker, BeakerSectionDisplay } from './common/BeakerDisplay';
diff --git a/tgui/packages/tgui/interfaces/Crystallizer.tsx b/tgui/packages/tgui/interfaces/Crystallizer.tsx
index 6cdd0bb86a50b..213fef342a655 100644
--- a/tgui/packages/tgui/interfaces/Crystallizer.tsx
+++ b/tgui/packages/tgui/interfaces/Crystallizer.tsx
@@ -1,7 +1,3 @@
-import { toFixed } from 'common/math';
-import { BooleanLike } from 'common/react';
-
-import { useBackend } from '../backend';
import {
Box,
Button,
@@ -9,7 +5,11 @@ import {
NumberInput,
ProgressBar,
Section,
-} from '../components';
+} from 'tgui-core/components';
+import { toFixed } from 'tgui-core/math';
+import { BooleanLike } from 'tgui-core/react';
+
+import { useBackend } from '../backend';
import { getGasColor } from '../constants';
import { Window } from '../layouts';
diff --git a/tgui/packages/tgui/interfaces/CyborgBootDebug.jsx b/tgui/packages/tgui/interfaces/CyborgBootDebug.jsx
index 914d4373ea082..d023240c7e1fa 100644
--- a/tgui/packages/tgui/interfaces/CyborgBootDebug.jsx
+++ b/tgui/packages/tgui/interfaces/CyborgBootDebug.jsx
@@ -1,5 +1,6 @@
+import { Button, Input, LabeledList, Section } from 'tgui-core/components';
+
import { useBackend } from '../backend';
-import { Button, Input, LabeledList, Section } from '../components';
import { Window } from '../layouts';
const TOOLTIP_NAME = `
diff --git a/tgui/packages/tgui/interfaces/FishCatalog.tsx b/tgui/packages/tgui/interfaces/FishCatalog.tsx
index 7bb85054fb002..34ee7a629b2c8 100644
--- a/tgui/packages/tgui/interfaces/FishCatalog.tsx
+++ b/tgui/packages/tgui/interfaces/FishCatalog.tsx
@@ -27,6 +27,7 @@ type FishInfo = {
weight: string;
size: string;
icon: string;
+ beauty: string;
};
type FishCatalogData = {
@@ -93,6 +94,9 @@ export const FishCatalog = (props) => {
{currentFish.weight} g
+
+ {currentFish.beauty}
+
diff --git a/tgui/packages/tgui/interfaces/NtosPowerMonitor.tsx b/tgui/packages/tgui/interfaces/NtosPowerMonitor.tsx
index 295c41c3c72d3..bb7f7d1d527a2 100644
--- a/tgui/packages/tgui/interfaces/NtosPowerMonitor.tsx
+++ b/tgui/packages/tgui/interfaces/NtosPowerMonitor.tsx
@@ -4,7 +4,7 @@ import { PowerMonitorContent } from './PowerMonitor';
export const NtosPowerMonitor = (props) => {
return (
-
+
diff --git a/tgui/packages/tgui/interfaces/Orbit/JobIcon.tsx b/tgui/packages/tgui/interfaces/Orbit/JobIcon.tsx
index f102927e9bcba..2d584bfc6b13d 100644
--- a/tgui/packages/tgui/interfaces/Orbit/JobIcon.tsx
+++ b/tgui/packages/tgui/interfaces/Orbit/JobIcon.tsx
@@ -37,20 +37,15 @@ export function JobIcon(props: Props) {
return (